定期面对面

2024年2月27日

通信技术的改进导致越来越多的团队采用远程优先的工作方式,这一趋势在新冠肺炎疫情期间的强制隔离中得到了加强。但远程工作的团队仍然可以从面对面的聚会中获益,并且应该每隔几个月进行一次。

远程优先团队的成员都位于不同的地点,完全通过电子邮件、聊天、视频和其他通信工具进行沟通。这种方式有明显的优势:可以从世界各地招募团队成员,并且可以邀请有照护责任的人参与。浪费在令人沮丧的通勤上的时间可以转化为生产性或恢复性时间。

但是,无论人们在远程工作方面多么有能力,无论现代协作工具变得多么巧妙,都没有什么能比得上与团队其他成员身处同一地点。当面对面交流时,人际互动总是更加丰富。视频通话很容易变得像交易一样,很少有时间进行闲聊,而闲聊可以建立起真正的人际关系。如果没有这些更深层的纽带,误解就会滋生成为严重的关系问题,团队可能会陷入如果每个人都能当面交谈就能有效解决的困境。

我从那些在远程优先工作中卓有成效的人那里观察到一个规律,那就是他们确保定期进行面对面的会议。在这些会议中,他们安排那些更适合一起完成的工作内容。远程工作更适合需要单独集中精力的任务,现代工具可以使远程配对变得可行。但是,那些需要来自许多人大量输入并快速反馈的任务,在每个人都在同一个房间时更容易完成。没有任何视频会议系统可以创造出那种深度互动,盯着电脑屏幕看别人在做什么很累,而且没有机会一起出去喝咖啡来放松工作。关于产品策略的辩论、系统架构的探索、新领域的探索——这些都是团队集结时常见的任务。

为了让人们能够有效地一起工作,他们需要彼此信任,了解他们可以依靠彼此的程度。信任很难在网上建立,因为那里没有我们在同一个房间时可能出现的社交线索。因此,面对面聚会最有价值的部分不是安排好的工作,而是喝咖啡时的闲聊,以及午餐时的欢快气氛。非正式的谈话,大多与工作无关,可以建立起人际联系,使工作互动更加有效。

这些指南表明了面对面会议的内容应该是什么。一起工作本身很有价值,也是团队凝聚力的重要组成部分。因此,我们应该安排一整天的工作,重点关注那些从在一起带来的低延迟通信中受益的任务。然后,我们应该包括一些看似时间过长的休息时间、非正式的聊天时间,以及走出办公室的机会。我建议避免任何人工的“团队建设”活动,仅仅是因为我非常讨厌它们。那些进行这种聚会的人强调了每个人在聚会后精力充沛的价值,因此能够在接下来的几周内更加有效地工作。

远程团队可以形成于相距很远的地方,成员之间通常相隔数小时的旅程。对于这样的团队,我建议的经验法则是每两三个月聚会一次,为期一周。在团队变得成熟之后,他们可能会决定减少聚会的频率,但我担心如果一个团队一年没有至少两次面对面会议。如果一个团队都在同一个城市,但使用远程优先的方式来减少通勤,那么他们可以组织更短的聚会,并且更频繁地进行。

这种聚会可能会导致人们重新思考如何配置办公空间。自疫情以来,人们一直在谈论办公室的使用率大幅下降。办公室很可能不再是每天的工作场所,而是成为这类不定期团队聚会的场所。这需要灵活舒适的团队聚会空间。

一些组织可能会对团队集会所需的差旅和住宿费用望而却步,但他们应该将其视为对团队效率的投资。忽视这些面对面的会议会导致团队陷入困境,朝着错误的方向前进,充满冲突,人们失去动力。与之相比,节省飞机票和酒店费用是一种错误的经济行为。

进一步阅读

远程优先是远程工作的一种形式,我在远程工作与协同工作中探讨了不同远程工作方式及其权衡。

在Thoughtworks,当我们近二十年前首次开始设立海外开发中心时,我们了解到定期进行面对面聚会对于远程团队的重要性。这些经验产生了我在在海外开发中使用敏捷软件流程中描述的做法。

远程工作,尤其是在跨越时区的情况下,对异步协作模式提出了更高的要求。我的同事苏米特·莫格,一位产品经理,在他的书异步优先手册中深入探讨了如何做到这一点。

Atlassian,一家软件产品公司,最近完全转向远程工作,并发布了一份关于其经验的报告。他们已经了解到,团队每年大约进行三次面对面聚会是明智的。克莱尔·刘在2018年对远程优先团队进行了调查,发现四分之一的受访者每年进行几次“撤退”。37Signals 已经作为一家远程优先的公司运营了近二十年,并每年安排两次聚会

致谢

Alejandro Batanero、Andrew Thal、Chris Ford、Heiko Gerin、Kief Morris、Kuldeep Singh、Matt Newman、Michael Chaffee、Naval Prabhakar、Rafael Detoni 和 Ramki Sitaraman 在我们的内部邮件列表中讨论了本文的草稿。


遗留缝合

2024年1月4日

在处理遗留系统时,识别和创建缝合点非常有价值:这些地方可以让我们改变系统的行为,而无需编辑源代码。一旦我们找到了缝合点,就可以利用它来打破依赖关系,简化测试,插入探针以获得可观察性,以及将程序流重定向到新模块,作为遗留代码替换的一部分。

迈克尔·费瑟斯在他的书有效地处理遗留代码中,在遗留系统的背景下创造了“缝合点”一词。他的定义是:“缝合点是指你可以在程序中改变行为而无需在该位置进行编辑的地方”

以下是一个缝合点非常有用的例子。想象一下一些用于计算订单价格的代码。

// TypeScript
export async function calculatePrice(order:Order) {
  const itemPrices = order.items.map(i => calculateItemPrice(i))
  const basePrice = itemPrices.reduce((acc, i) => acc + i.price, 0)
  const discount = calculateDiscount(order)
  const shipping = await calculateShipping(order)
  const adjustedShipping = applyShippingDiscounts(order, shipping)
  return basePrice + discount + adjustedShipping
}

函数calculateShipping调用了一个外部服务,该服务速度很慢(而且很昂贵),因此我们不想在测试时调用它。相反,我们想引入一个,这样我们就可以为每个测试场景提供一个预先准备好的确定性响应。不同的测试可能需要函数的不同响应,但我们不能在测试内部编辑calculatePrice的代码。因此,我们需要在调用calculateShipping的地方引入一个缝合点,这样我们的测试就可以将调用重定向到桩。

一种方法是将calculateShipping的函数作为参数传递

export async function calculatePrice(order:Order, shippingFn: (o:Order) => Promise<number>) {
  const itemPrices = order.items.map(i => calculateItemPrice(i))
  const basePrice = itemPrices.reduce((acc, i) => acc + i.price, 0)
  const discount = calculateDiscount(order)
  const shipping = await shippingFn(order)
  const adjustedShipping = applyShippingDiscounts(order, shipping)
  return basePrice + discount + adjustedShipping
}

该函数的单元测试可以替换一个简单的桩。

const shippingFn = async (o:Order) => 113
expect(await calculatePrice(sampleOrder, shippingFn)).toStrictEqual(153)

每个缝合点都带有一个启用点:“你可以做出使用一种行为还是另一种行为的决策的地方” [WELC]。将函数作为参数传递会在calculateShipping的调用者中打开一个启用点。

现在测试变得容易多了,我们可以输入不同的运费值,并检查applyShippingDiscounts是否正确响应。虽然我们必须更改原始源代码以引入缝合点,但对该函数的任何进一步更改都不需要我们更改该代码,所有更改都发生在启用点,它位于测试代码中。

将函数作为参数传递并不是引入缝合点的唯一方法。毕竟,更改calculateShipping的签名可能很麻烦,我们可能不想在生产代码中将运费函数参数传递到遗留调用栈中。在这种情况下,查找可能是一种更好的方法,例如使用服务定位器。

export async function calculatePrice(order:Order) {
  const itemPrices = order.items.map(i => calculateItemPrice(i))
  const basePrice = itemPrices.reduce((acc, i) => acc + i.price, 0)
  const discount = calculateDiscount(order)
  const shipping = await ShippingServices.calculateShipping(order)
  const adjustedShipping = applyShippingDiscounts(order, shipping)
  return basePrice + discount + adjustedShipping
}
class ShippingServices {
  static #soleInstance: ShippingServices
  static init(arg?:ShippingServices) {
    this.#soleInstance = arg || new ShippingServices()
  }
  static async calculateShipping(o:Order) {return this.#soleInstance.calculateShipping(o)}
  async calculateShipping(o:Order)  {return legacy_calcuateShipping(o)}
  // ... more services

定位器允许我们通过定义一个子类来覆盖行为。

class ShippingServicesStub extends ShippingServices {
  calculateShippingFn: typeof ShippingServices.calculateShipping =
     (o) => {throw new Error("no stub provided")}
  async calculateShipping(o:Order) {return this.calculateShippingFn(o)}
  // more services

然后,我们可以在测试中使用一个启用点

const stub = new ShippingServicesStub()
stub.calculateShippingFn = async (o:Order) => 113
ShippingServices.init(stub)
expect(await calculatePrice(sampleOrder)).toStrictEqual(153)

这种服务定位器是通过函数查找设置缝合点的经典面向对象方法,我在这里展示它是为了说明我在其他语言中可能使用的方法,但我不会在 TypeScript 或 JavaScript 中使用这种方法。相反,我会将类似的东西放入一个模块中。

export let calculateShipping = legacy_calculateShipping

export function reset_calculateShipping(fn?: typeof legacy_calculateShipping) {
  calculateShipping = fn || legacy_calculateShipping
}

然后,我们可以在测试中使用这段代码,如下所示

const shippingFn = async (o:Order) => 113
reset_calculateShipping(shippingFn)
expect(await calculatePrice(sampleOrder)).toStrictEqual(153)

正如最后一个例子所暗示的那样,用于缝合点的最佳机制很大程度上取决于语言、可用的框架,以及遗留系统的风格。控制遗留系统意味着学习如何在代码中引入各种缝合点,以便在最大限度地减少对遗留软件的干扰的同时提供正确类型的启用点。虽然函数调用是引入这种缝合点的简单例子,但它们在实践中可能要复杂得多。一个团队可能需要花费几个月的时间来弄清楚如何在经过充分磨合的遗留系统中引入缝合点。在遗留系统中添加缝合点的最佳机制可能与我们在绿地中为了类似的灵活性而采取的措施不同。

费瑟斯的书主要关注如何让遗留系统接受测试,因为这通常是能够以理智的方式处理遗留系统的关键。但缝合点的用途不止于此。一旦我们有了缝合点,我们就可以将探针放置到遗留系统中,从而提高系统的可观察性。我们可能想要监控对calculateShipping的调用,找出我们使用它的频率,并捕获其结果以进行单独分析。

但缝合点可能最有价值的用途是它们允许我们将行为从遗留系统中迁移出去。缝合点可以将高价值客户重定向到不同的运费计算器。有效的遗留代码替换是建立在将缝合点引入遗留系统,并利用它们将行为逐渐迁移到更现代的环境中。

在编写新软件时,接缝也是需要考虑的因素,毕竟每个新系统迟早都会变成遗留系统。我的许多设计建议都是关于在适当的位置构建软件接缝,以便我们能够轻松地测试、观察和增强它。如果我们在编写软件时考虑到测试,我们往往会得到一组良好的接缝,这也是为什么测试驱动开发 是一种如此有用的技术的原因。


软件与工程

2023 年 12 月 13 日

在我的职业生涯中,人们一直将软件开发与“传统”工程进行比较,通常是为了责备软件开发人员没有做好工作。作为一名电子工程学位的获得者,这在我职业生涯的早期引起了我的共鸣。但这种思维方式是有缺陷的,因为大多数人对工程在实践中是如何运作的都有错误的印象。

Glenn Vanderburg 花费了大量时间来挖掘这些误解,我强烈建议任何想将软件开发与工程进行比较的人观看他的演讲真正的软件工程。同样值得一听的是他在 Oddly Influenced 播客上的采访。遗憾的是,我无法说服他将这些材料写下来——这将是一篇很棒的文章。

另一个对这种关系有深入思考的人是 Hillel Wayne。他采访了一群“跨界者”——那些既在传统工程领域工作又在软件领域工作的人。他将自己学到的东西写成了一系列文章,从我们真的是工程师吗?开始。


测试驱动开发

2023 年 12 月 11 日

测试驱动开发 (TDD) 是一种通过编写测试来指导软件开发的软件构建技术。它是由Kent Beck 在 1990 年代后期作为极限编程的一部分开发的。本质上,我们反复遵循三个简单的步骤

  • 为要添加的下一个功能编写测试。
  • 编写功能代码,直到测试通过。
  • 重构新旧代码,使其结构良好。

虽然这三个步骤,通常总结为红色 - 绿色 - 重构,是该过程的核心,但还有一个重要的初始步骤,即我们首先列出测试用例。然后,我们选择其中一个测试,对其应用红绿重构,完成后选择下一个。正确排序测试是一项技能,我们希望选择能够快速将我们引导到设计关键点的测试。在此过程中,我们应该在想到时将更多测试添加到我们的列表中。

首先编写测试,XPE2 称为测试优先编程,提供了两个主要好处。最明显的是,它是一种获得自测试代码 的方法,因为我们只能编写一些功能代码来响应通过测试。第二个好处是,首先考虑测试迫使我们首先考虑代码的接口。这种对接口和如何使用类的关注有助于我们将接口与实现分离,这是许多程序员难以处理的良好设计的关键要素。

我听到的最常见的搞砸 TDD 的方法是忽略第三步。重构代码以保持其清洁是该过程的关键部分,否则我们最终只会得到一堆杂乱无章的代码片段。(至少这些代码将有测试,因此它比大多数设计失败的结果不那么痛苦。)

进一步阅读

Kent 对进行 TDD 的规范方法 的总结是关键的在线总结。

要了解更多信息,请前往 Kent Beck 的著作测试驱动开发

James Shore 的敏捷开发艺术 中的相关章节是另一个合理的描述,它还将 TDD 与有效的敏捷开发的其他方面联系起来。James 还制作了一系列名为让我们玩 TDD 的屏幕截图。

修订

我最初发布此页面的日期是 2005-03-05。受 Kent 的规范帖子的启发,我在 2023-12-11 更新了它。


差异调试

2023 年 12 月 4 日

回归错误是在一段时间内存在的软件功能中新出现的错误。在追踪它们时,弄清楚软件中的哪些更改导致它们出现通常很有价值。查看这些更改可以提供有关错误在哪里以及如何修复它的宝贵线索。这种形式的调查没有一个众所周知的术语,但我称之为差异调试。

差异调试只有在我们的代码处于版本控制下才能起作用,但幸运的是,如今这已成为常态。但要使其有效地工作,还需要一些其他事项。我们需要可重现的构建,以便我们可以轻松地运行旧版本的软件。由于高频集成,进行小的提交非常有帮助。这样,当我们找到有问题的提交时,就可以更容易地缩小发生的事情的范围。

要找到导致错误的提交,我们首先找到任何没有错误的过去版本。将此标记为最后一个良好版本,并将当前版本标记为最早的错误。然后找到这两个版本之间一半的提交,看看错误是否在那里。如果是,则此提交变为最早的错误,否则它变为最后一个良好。重复此过程(这是一种“半间隔”或“二进制”搜索),直到我们找到有问题的提交。

如果我们使用 git,那么git bisect 命令将为我们自动执行大部分操作。如果我们可以编写一个测试来显示错误的存在,那么 git bisect 也可以使用它,自动完成查找有问题的提交的整个过程。

我经常发现差异调试在编程会话中很有用。如果我的测试速度很慢,需要几分钟才能运行,我可能会编程半小时,只运行最相关的测试子集。只要我在每次绿色测试运行后提交,我就可以使用差异调试,以防这些较慢的测试之一失败。这就是频繁提交的价值所在,即使它们很小,以至于我觉得最好将它们压缩以获得长期的历史记录。一些 IDE 通过自动保留比版本控制提交更细粒度的本地历史记录来简化此操作。

修订

我最初发布此页面的日期是 2004-06-01。在其原始形式中,它更像是一份随意的经验报告。我在 2023-12-04 重写了它,使其更像是一个术语的定义。差异调试不是一个在行业中流行的术语,但我没有看到另一个普遍用于描述它的术语。


团队拓扑

2023 年 7 月 25 日

任何大型软件工作,例如大型公司的软件资产,都需要很多人——每当你有很多人时,你都必须弄清楚如何将他们分成有效的团队。形成以业务能力为中心的 团队有助于软件工作响应客户的需求,但所需的技能范围往往会压倒这些团队。 团队拓扑 是一个描述软件开发团队组织的模型,由 Matthew Skelton 和 Manuel Pais 开发。它定义了四种团队形式和三种团队交互模式。该模型鼓励健康的互动,使以业务能力为中心的团队能够在其提供稳定而有价值的软件流的任务中蓬勃发展。

此框架中的主要团队类型是流对齐团队,这是一个以业务能力为中心的 团队,负责单个业务能力的软件。这些是长期运行的团队,将他们的工作视为提供软件产品 来增强业务能力。

每个流对齐团队都是全栈和全生命周期的:负责前端、后端、数据库、业务分析、功能优先级排序、用户体验、测试、部署、监控——整个软件开发过程。他们是以结果为导向的,专注于业务成果,而不是以活动为导向的 团队,专注于业务分析、测试或数据库等功能。但他们也不应该太大,理想情况下,每个团队都是一个两个披萨团队。一个大型组织将拥有许多这样的团队,虽然他们拥有不同的业务能力来支持,但他们有共同的需求,例如数据存储、网络通信和可观察性。

这样一个小型团队需要减少他们的认知负荷,这样他们就可以专注于支持业务需求,而不是(例如)数据存储问题。实现此目标的一个重要部分是在一个处理这些非重点关注事项的平台上构建。对于许多团队来说,平台可以是一个广泛可用的第三方平台,例如 Ruby on Rails,用于数据库支持的 Web 应用程序。但对于许多产品来说,没有一个现成的平台可以使用,团队将不得不找到并集成多个平台。在一个更大的组织中,他们将不得不访问一系列内部服务并遵循公司标准。

这个问题可以通过为组织构建一个内部平台来解决。这样的平台可以完成第三方服务的集成、近乎完整的平台和内部服务。团队拓扑将构建此平台的团队分类为(毫不奇怪但明智地)平台团队

较小的组织可以使用单个平台团队,该团队在外部提供的产品集之上生成一个薄层。然而,更大的平台需要比两个披萨所能容纳的人员更多。因此,作者正在转向描述由许多平台团队组成的平台分组

平台的一个重要特征是它被设计为以一种主要自助的方式使用。流对齐团队仍然负责其产品的运营,并直接使用平台,而无需期望与平台团队进行复杂的协作。在团队拓扑框架中,这种交互模式被称为X 作为服务模式,平台充当流对齐团队的服务。

然而,平台团队需要将他们的服务构建为产品本身,并深入了解客户的需求。这通常需要他们使用不同的交互模式,即协作模式,在他们构建该服务时。协作模式是一种更密集的伙伴关系形式的交互,应被视为一种临时方法,直到平台成熟到足以转移到 x 作为服务模式为止。

到目前为止,该模型并没有代表任何特别有创意的东西。将组织分解为业务对齐团队和技术支持团队是一种与企业软件一样古老的方法。近年来,许多作家都表达了让这些业务能力团队负责全栈和全生命周期的重要性。对我来说,团队拓扑的明智见解是关注这样一个问题:拥有全栈和全生命周期的业务对齐团队意味着他们经常面临过度的认知负荷,这不利于对小型、响应式团队的渴望。平台的主要好处是它减少了这种认知负荷

团队拓扑的一个关键见解是,平台的主要好处是减少了流对齐团队的认知负荷。

这种洞察具有深远的影响。首先,它改变了平台团队应该如何思考平台。减少客户团队的认知负荷会导致不同的设计决策和产品路线图,这些平台主要用于标准化或成本降低。除了平台之外,这种洞察力还促使团队拓扑通过识别另外两种类型的团队来进一步发展其模型。

某些能力需要专家,他们可以投入大量时间和精力来掌握对许多流对齐团队都很重要的主题。安全专家可能比作为流对齐团队成员花费更多时间研究安全问题并与更广泛的安全社区互动。这些人聚集在赋能团队中,他们的作用是在其他团队内部培养相关技能,以便这些团队能够保持独立,并更好地拥有和发展其服务。为了实现这一点,赋能团队主要使用团队拓扑中的第三种也是最后一种交互模式。促进模式涉及一种指导角色,赋能团队不在那里编写和确保符合标准,而是教育和指导他们的同事,以便流对齐团队变得更加自主。

流对齐团队负责为其客户提供完整的价值流,但偶尔我们会发现流对齐团队工作中的一些方面要求很高,需要一个专门的团队来专注于它,从而导致第四种也是最后一种类型的团队:复杂子系统团队。复杂子系统团队的目标是减少使用该复杂子系统的流对齐团队的认知负荷。即使该子系统只有一个客户团队,这也是一个值得的划分。大多数复杂子系统团队努力使用 x 作为服务模式与其客户互动,但需要在短时间内使用协作模式。

团队拓扑包含一组图形符号来说明团队及其关系。这里显示的是来自当前标准的符号,与书中使用的符号不同。一篇最近的文章详细阐述了如何使用这些图表。

团队拓扑的设计明确认识到康威定律的影响。它鼓励的团队组织考虑了人与软件组织之间的相互作用。团队拓扑的倡导者希望其团队结构能够将软件架构的未来发展塑造成响应式和解耦的组件,这些组件与业务需求保持一致。

乔治·博克斯巧妙地调侃道:“所有模型都是错误的,有些是有用的”。因此,团队拓扑是错误的:复杂的组织不能简单地分解成只有四种类型的团队和三种类型的交互。但像这样的约束正是使模型有用的原因。团队拓扑是一种工具,它促使人们将他们的组织发展成一种更有效的方式,一种允许流对齐团队通过减轻他们的认知负荷来最大化其流程的方式。

致谢

Andrew Thal、Andy Birds、Chris Ford、Deepak Paramasivam、Heiko Gerin、Kief Morris、Matteo Vaccari、Matthew Foster、Pavlo Kerestey、Peter Gillard-Moss、Prashanth Ramakrishnan 和 Sandeep Jagtap 在我们的内部邮件列表中讨论了这篇文章的草稿,并提供了宝贵的反馈。

Matthew Skelton 和 Manuel Pais 友好地对这篇文章提供了详细的评论,包括分享了自本书出版以来的最新思考。

进一步阅读

对团队拓扑框架的最佳处理是同名书籍,该书于 2019 年出版。作者还维护着团队拓扑网站,并提供教育和培训服务。他们最近关于团队交互建模的文章是了解如何使用团队拓扑(元)模型来构建和发展组织模型的良好介绍。[1]

团队拓扑的很大一部分基于认知负荷的概念。作者在 Tech Beacon 中探讨了认知负荷。Jo Pearce 扩展了认知负荷如何应用于软件开发

团队拓扑中的模型与我在此网站上发布的关于软件团队组织的大部分想法产生了共鸣。您可以在团队组织标签中找到这些内容。

笔记

1: 为了在我的建模语言中更加严格,我会说团队拓扑通常充当元模型。如果我使用团队拓扑来构建航空公司软件开发组织的模型,那么该模型将显示根据团队拓扑的术语分类的航空公司中的团队。然后我会说团队拓扑模型是我的航空公司模型的元模型。


双披萨团队

2023 年 7 月 25 日

双披萨团队是一个小型团队,完全支持特定业务能力的软件。这个术语变得流行起来,因为它用来描述亚马逊如何组织他们的软件人员。

这个名字暗示了这种团队最明显的方面,即它们的大小。这个名字来自这样的原则:团队不应该比用两个披萨能喂饱的人数更多。(虽然我们这里说的是美式披萨,但当我第一次在这里遇到它们时,它们似乎大得惊人。)保持团队规模小可以保持团队的凝聚力,形成紧密的合作关系。我通常听到这意味着这样的团队大约有 5-8 人,尽管我的经验表明上限大约在 15 人左右。

虽然这个名字只关注规模,但同样重要的是团队的重点。双披萨团队应该拥有为其用户提供有价值的软件所需的所有能力,并且与其他团队的依赖关系和交接工作最少。他们可以弄清楚客户的需求,并迅速将其转化为可用的软件,能够随着客户需求的变化而实验和发展该软件。

双披萨团队是结果导向而不是活动导向。他们不是按照技能(数据库、测试、运营)进行组织,而是承担了支持其客户所需的所有责任。这最大限度地减少了功能流向客户的团队间交接工作,使他们能够缩短周期时间(将功能的想法转化为生产环境中运行的代码所需的时间)。这种结果导向也意味着他们将代码部署到生产环境中并监控其在那里的使用,最著名的是对任何生产故障负责(通常意味着他们负责非工作时间支持)——这一原则被称为“你构建它,你就运行它”。

专注于像这样的客户需求意味着团队是长期的,业务能力中心团队,只要该能力处于活动状态,他们就会支持该能力。与项目导向型团队不同——在软件“完成”后解散——他们认为自己是支持长期产品的团队。这方面通常导致他们被称为产品团队

双披萨团队需要支持其产品所需广泛的技能和责任,这意味着虽然这种团队可以成为团队组织的主要方法,但它们需要一个构建良好的软件平台的支持。对于小型组织来说,这可以是一个商业平台,例如现代云服务。较大的组织将创建自己的内部平台,以便他们的双披萨团队更容易协作,而不会造成困难的交接工作。团队拓扑提供了一种很好的方法来思考支持双披萨团队所需的各种团队和交互(团队拓扑称之为流对齐团队)。

为了使以业务能力为中心的团队有效,他们需要利用彼此的能力。因此,团队需要通过精心设计的 API 向同行提供其能力。这种团队为同行提供服务的责任经常被忽视,如果它没有发生,它会导致信息孤岛硬化。

由于康威定律的影响,围绕业务能力组织人员会对组织的软件结构方式产生深远的影响。由双披萨团队构建的软件组件需要与其同行之间有良好的控制交互,并在它们之间有明确的 API。这种想法导致了微服务的发展,但这并不是唯一的方法——单片运行时内的结构良好的组件通常是更好的路径。