设计已死?

对于许多短暂接触极限编程的人来说,XP 似乎要求软件设计的消亡。不仅许多设计活动被嘲笑为“大型前期设计”,而且 UML、灵活框架,甚至模式等设计技术也被淡化或完全忽略。事实上,XP 涉及大量设计,但它以不同于既定软件流程的方式进行设计。XP 使演进式设计理念焕发活力,其实践使演进成为一种可行的设计策略。它还带来了新的挑战和技能,因为设计师需要学习如何进行简单设计,如何使用重构来保持设计整洁,以及如何以演进式风格使用模式。

2004 年 5 月



极限编程 (XP) 对许多关于软件开发的常见假设提出了挑战。其中最具争议的一点是,它拒绝在前期设计中投入大量精力,转而采用更具演进性的方法。对于它的批评者来说,这是一种回归到“编码和修复”开发的模式,通常被嘲笑为黑客行为。对于它的支持者来说,这通常被视为对设计技术(如 UML)、原则和模式的拒绝。不要担心设计,如果你倾听你的代码,一个好的设计就会出现。

我发现自己身处这场争论的中心。我职业生涯的很大一部分都与图形设计语言——统一建模语言 (UML) 及其前身——以及模式有关。事实上,我已经出版了关于 UML 和模式的书籍。我拥抱 XP 是否意味着我放弃了我之前关于这些主题的所有著作,清空我的脑海中所有这些反革命的观念?

好吧,我不会让你吊在戏剧性悬念的钩子上。简短的回答是否定的。长答案是本文的其余部分。

计划设计与演进式设计

在这篇文章中,我将描述两种在软件开发中进行设计的风格。也许最常见的是演进式设计。本质上,演进式设计意味着系统的设计随着系统的实现而不断发展。设计是编程过程的一部分,随着程序的演进,设计也会发生变化。

在它的常用用法中,演进式设计是一场灾难。设计最终会变成一堆临时战术决策的集合,每个决策都会使代码更难修改。从许多方面来说,你可能会争辩说这不是设计,当然它通常会导致糟糕的设计。正如肯特所说,设计是为了让你能够在长期内轻松地改变软件。随着设计的恶化,你有效地进行更改的能力也会下降。你处于软件熵的状态,随着时间的推移,设计会越来越糟糕。这不仅使软件更难更改,而且还会使错误更容易滋生,也更难找到并安全地消除。这就是“编码和修复”的噩梦,随着项目的进行,错误的修复成本呈指数级增长。

计划设计是对此的对抗,它包含了来自工程学其他分支的理念。如果你想建造一个狗窝,你可以直接收集一些木材,然后做成一个粗略的形状。但是,如果你想建造一座摩天大楼,你就不能那样做——它甚至还没建到一半就会倒塌。所以你从工程图纸开始,这些图纸是在像我妻子在波士顿市中心工作的工程办公室这样的地方完成的。当她进行设计时,她会找出所有问题,部分是通过数学分析,但主要是通过使用建筑规范。建筑规范是关于如何根据经验(以及一些基础数学)设计结构的规则。设计完成后,她的工程公司就可以将设计交给另一家公司来建造。

软件中的计划设计应该以同样的方式工作。设计师提前思考重大问题。他们不需要编写代码,因为他们不是在构建软件,而是在设计软件。因此,他们可以使用像 UML 这样的设计技术,它可以摆脱一些编程细节,让设计师在更抽象的层面上工作。设计完成后,他们可以将其交给另一个团队(甚至另一家公司)来构建。由于设计师在更大的范围内思考,他们可以避免导致软件熵的一系列战术决策。程序员可以遵循设计的指示,只要他们遵循设计,就会有一个构建良好的系统。

现在,计划设计方法已经存在了几十年,许多人都在使用它。在许多方面,它比“编码和修复”的演进式设计要好。但它也有一些缺陷。第一个缺陷是,当你编程时,不可能思考你需要处理的所有问题。因此,在编程时,你不可避免地会发现一些质疑设计的问题。然而,如果设计师已经完成了工作,转而进行另一个项目,会发生什么?程序员开始绕过设计进行编码,熵就开始了。即使设计师没有离开,解决设计问题、更改图纸,然后修改代码也需要时间。通常有一个更快的解决方法,而且时间压力很大。因此,熵(再次出现)。

此外,通常存在文化问题。设计师之所以成为设计师,是因为他们的技能和经验,但他们太忙于设计,以至于没有太多时间编码了。然而,软件开发的工具和材料正在快速变化。当你不再编码时,你不仅会错过这种技术变革带来的变化,还会失去那些真正编码的人的尊重。

这种建造者和设计师之间的紧张关系在建筑中也存在,但在软件中更为强烈。它之所以强烈,是因为存在一个关键区别。在建筑中,设计师和建造者之间的技能划分更加明确,但在软件中则不然。在高设计环境中工作的任何程序员都需要非常熟练。他们必须足够熟练,能够质疑设计师的设计,尤其是在设计师对开发平台的日常现实了解不足的情况下。

现在,这些问题可以解决。也许我们可以处理人类之间的紧张关系。也许我们可以让设计师足够熟练,能够处理大多数问题,并拥有一个足够严谨的流程来更改图纸。还有一个问题:不断变化的需求。不断变化的需求是我遇到的软件项目中导致头痛的首要问题。

处理不断变化的需求的一种方法是在设计中构建灵活性,以便你可以轻松地根据需求的变化进行更改。然而,这需要洞察你期望的更改类型。可以计划设计来处理不稳定区域,但这将有助于预见的更改,但不会有助于(甚至会损害)不可预见的更改。因此,你必须充分了解需求,才能将不稳定区域区分开来,而我的观察是,这非常困难。

现在,一些需求问题是由于对需求理解不够清晰造成的。因此,许多人专注于需求工程流程,以获得更好的需求,希望这将防止以后需要更改设计。但即使这个方向也可能不会带来治愈。许多不可预见的需求变化是由于业务变化造成的。无论你的需求工程流程多么谨慎,这些变化都无法阻止。

所以,这一切都让计划设计听起来不可能。当然,它们是巨大的挑战。但我并不倾向于声称计划设计比演进式设计更糟糕,因为演进式设计通常以“编码和修复”的方式进行。事实上,我更喜欢计划设计而不是“编码和修复”。然而,我意识到计划设计的问题,并且正在寻求新的方向。

XP 的赋能实践

XP 由于许多原因而存在争议,但 XP 中的一个关键红旗是它提倡演进式设计而不是计划设计。众所周知,演进式设计由于临时设计决策和软件熵而不可能奏效。

理解这场争论的核心是软件变化曲线。变化曲线表明,随着项目的进行,进行更改的成本呈指数级增长。变化曲线通常用阶段来表示,“在分析阶段进行的更改花费 1 美元,而在生产阶段修复则需要花费数千美元”。这很讽刺,因为大多数项目仍然使用没有分析阶段的临时流程,但指数增长仍然存在。指数变化曲线意味着演进式设计不可能奏效。它还说明了为什么计划设计必须谨慎进行,因为计划设计中的任何错误都会面临同样的指数增长。

XP 的基本假设是,有可能使变化曲线足够平坦,以使演进式设计能够奏效。这种平坦化既是 XP 赋能的结果,也是 XP 利用的结果。这是 XP 实践耦合的一部分:具体来说,你不能在没有进行赋能实践的情况下进行利用实践,而赋能实践可以使利用实践发挥作用。这是 XP 争议的常见来源。许多人批评利用,而不理解赋能。批评通常源于批评者自己的经验,他们在这些经验中没有进行赋能实践,而这些实践可以让利用实践发挥作用。结果,他们被烧伤了,当他们看到 XP 时,他们就会想起这场大火。

赋能实践有很多部分。核心是测试和持续集成实践。如果没有测试提供的安全性,XP 的其余部分将是不可能的。持续集成对于保持团队同步是必要的,这样你就可以进行更改,而不必担心将其与其他人的更改集成。这些实践一起可以对变化曲线产生重大影响。我在 Thoughtworks 再次被提醒了这一点。引入测试和持续集成对开发工作有了明显的改进。当然,这足以让人严重质疑 XP 的断言,即你需要所有实践才能获得重大改进。

重构具有类似的效果。那些以 XP 建议的严谨方式重构代码的人发现,他们的效率与进行更松散、更临时重构相比有显著差异。这当然是我在肯特教我如何正确重构之后我的经验。毕竟,只有这样强烈的变化才会促使我写一整本书来介绍它。

吉姆·海史密斯在他的优秀 XP 总结 中使用了天平的类比。在一个托盘上是计划设计,另一个托盘上是重构。在更传统的做法中,计划设计占主导地位,因为假设你以后无法改变主意。随着更改成本的降低,你可以在以后进行更多设计,因为重构。计划设计不会完全消失,但现在有两种设计方法的平衡可以一起使用。对我来说,感觉就像在重构之前,我一直在用一只手进行所有设计。

持续集成、测试和重构这些促成性实践,提供了一个新的环境,使演化式设计成为可能。然而,我们还没有弄清楚平衡点在哪里。我相信,尽管外界印象认为,XP 不仅仅是测试、编码和重构。在编码之前,还有设计的空间。其中一部分是在编码之前进行的,大部分是在为特定任务编码之前的迭代中进行的。但预先设计和重构之间存在新的平衡。

简洁的价值

XP 中最伟大的两个口号是“做最简单的能起作用的事情”和“你不需要它”(称为 YAGNI)。两者都是 XP 简单设计实践的表现形式。

YAGNI 通常的描述方式是,你不应该在今天添加任何代码,这些代码只会在明天需要的功能中使用。表面上看,这听起来很简单。问题在于框架、可重用组件和灵活设计等方面。这些东西很复杂,需要构建。你支付了额外的预先成本来构建它们,期望你以后能收回这些成本。这种预先构建灵活性的想法被认为是有效软件设计的一个关键部分。

然而,XP 的建议是,你不应该为第一个需要该功能的案例构建灵活的组件和框架。让这些结构随着需要而增长。如果我今天想要一个处理加法但不处理乘法的 Money 类,那么我只在 Money 类中构建加法。即使我确信在下一轮迭代中需要乘法,并且了解如何轻松地实现它,并且认为这样做非常快,我仍然会把它留到下一轮迭代。

这样做的一个原因是经济上的。如果我必须做一些只用于明天需要的功能的工作,这意味着我会从需要为本轮迭代完成的功能中损失精力。发布计划说明了现在需要做什么工作,而对未来其他事情的工作与开发人员与客户的协议相违背。存在本轮迭代的故事可能无法完成的风险。即使本轮迭代的故事没有风险,也由客户决定应该做哪些额外的工作——这可能仍然不包括乘法。

这种经济上的不利因素加剧了我们可能无法正确实现的可能性。无论我们对这个函数如何工作有多确定,我们仍然可能出错——尤其是在我们还没有详细的需求的情况下。过早地进行错误的解决方案比过早地进行正确的解决方案更浪费。而且 XPerts 通常认为我们更有可能出错而不是正确(我同意这种观点)。

简单设计的第二个原因是,复杂的设计比简单设计更难理解。因此,任何对系统的修改都会因增加的复杂性而变得更加困难。这会在更复杂的设计被添加和需要它之间的时间段内增加成本。

现在,这个建议让很多人觉得是胡说八道,他们有理由这样想。只要你想象一下通常的开发世界,那里没有 XP 的促成性实践。然而,当计划设计和演化式设计之间的平衡发生变化时,YAGNI 就变成了好的实践(也只有在那个时候)。

所以总结一下。你不想花费精力添加在未来迭代之前不需要的新功能。即使成本为零,你仍然不想添加它,因为它会增加修改的成本,即使添加它的成本为零。但是,只有当你使用 XP 或类似的技术降低了更改成本时,你才能明智地这样做。

到底什么是简洁

所以我们希望我们的代码尽可能简单。这听起来并不难争论,毕竟谁想复杂呢?但当然,这引出了一个问题:“什么是简单?”

XPE 中,Kent 给出了四个简单系统的标准。按顺序(最重要的放在最前面)

  • 运行所有测试
  • 没有重复
  • 揭示所有意图
  • 最少的类或方法数量

运行所有测试是一个非常简单的标准。没有重复也很简单,尽管许多开发人员需要指导才能实现它。棘手的是揭示意图。这到底意味着什么?

这里的基本价值是代码的清晰度。XP 非常重视易于阅读的代码。在 XP 中,“巧妙的代码”是一个贬义词。但有些人认为揭示意图的代码是另一种人的巧妙之处。

在 Josh Kerievsky 的 XP 2000 论文中,他指出了一个很好的例子。他查看了可能是所有公开 XP 代码中最公开的代码——JUnit。JUnit 使用装饰器为测试用例添加可选功能,例如并发同步和批处理设置代码。通过将此代码分离到装饰器中,它允许通用代码比以前更清晰。

但是你必须问问自己,生成的代码是否真的简单。对我来说是,但我熟悉装饰器模式。但对于许多不熟悉的人来说,它相当复杂。同样,JUnit 使用可插拔方法,我注意到大多数人最初发现它除了清晰之外什么都不是。那么我们是否可以得出结论,JUnit 的设计对于经验丰富的设计师来说更简单,而对于经验不足的人来说更复杂?

我认为,消除重复的重点,无论是 XP 的“一次且仅一次”还是 务实程序员 的 DRY(不要重复自己),都是那些显而易见且非常强大的良好建议。仅仅遵循这一点就可以让你走很远。但它不是全部,简单仍然是一件复杂的事情。

最近,我参与了一些可能过度设计的事情。它被重构了,一些灵活性被去掉了。但正如一位开发人员所说,“重构过度设计比重构没有设计更容易”。最好比你需要的稍微简单一点,但稍微复杂一点也不算灾难。

我听到的关于这一切的最佳建议来自 Bob 叔叔(Robert Martin)。他的建议是不要太在意最简单的设计是什么。毕竟,你可以、应该并且会以后重构它。最终,重构的意愿比一开始就知道最简单的事情更重要。

重构是否违反 YAGNI?

这个话题最近在 XP 邮件列表中出现过,在我们查看 XP 中设计的作用时,它值得一提。

基本上,这个问题从重构需要时间但不会增加功能这一点开始。既然 YAGNI 的重点是应该为现在设计,而不是为未来设计,那么这是否违反了它?

YAGNI 的重点是,你不应该添加当前故事不需要的复杂性。这是简单设计实践的一部分。重构是必要的,以保持设计尽可能简单,因此,只要你意识到可以使事情更简单,你应该重构。

简单设计既利用了 XP 实践,也是一种促成性实践。只有当你拥有测试、持续集成和重构时,你才能有效地实践简单设计。但同时,保持设计简单对于保持变化曲线平坦至关重要。任何不必要的复杂性都会使系统在所有方向上都更难改变,除了你用复杂灵活性预期的那个方向。然而,人们不擅长预测,所以最好追求简单。然而,人们不会第一次就得到最简单的东西,所以你需要重构才能更接近目标。

模式与 XP

JUnit 的例子不可避免地让我提到了模式。模式与 XP 之间的关系很有趣,这也是一个常见的问题。Joshua Kerievsky 认为模式在 XP 中被低估了,他雄辩地阐述了这一论点,所以我不想重复它。但值得记住的是,对于许多人来说,模式似乎与 XP 冲突。

这个论点的本质是模式经常被过度使用。世界上充满了传奇程序员,他们刚读完 GOF,并在 32 行代码中包含了 16 种模式。我记得一个晚上,在喝了非常好的单一麦芽威士忌之后,我和 Kent 一起讨论了一篇名为“非设计模式:23 个廉价技巧”的论文。我们当时在想诸如使用 if 语句而不是策略之类的事情。这个笑话是有道理的,模式经常被过度使用,但这并不意味着它们是一个坏主意。问题在于你如何使用它们。

关于这一点的一种理论是,简单设计的驱动力会把你带入模式。许多重构明确地做到了这一点,但即使没有它们,通过遵循简单设计的规则,你也会想出模式,即使你以前不知道它们。这可能是真的,但真的是最好的方法吗?如果你大致知道你要去哪里,并且有一本书可以帮助你解决问题,而不是自己发明一切,那肯定更好。我当然仍然会在感觉到模式即将出现时翻阅 GOF。对我来说,有效的设计表明我们需要知道模式的价值——这本身就是一项技能。同样,正如 Joshua 所建议的那样,我们需要更熟悉如何逐渐融入模式。在这方面,XP 对我们使用模式的方式与某些人使用模式的方式不同,但当然不会消除模式的价值。

但是,阅读一些邮件列表,我明显感觉到,许多人认为 XP 阻止了模式,尽管具有讽刺意味的是,XP 的大多数支持者也是模式运动的领导者。这是因为他们已经超越了模式,还是因为模式已经深深地融入他们的思维,以至于他们不再意识到它?我不知道其他人答案,但对我来说,模式仍然至关重要。XP 可能是一个开发过程,但模式是设计知识的支柱,无论你的过程是什么,这种知识都是有价值的。不同的过程可能以不同的方式使用模式。XP 强调在需要之前不要使用模式,并通过简单的实现逐步演化到模式。但模式仍然是需要获取的关键知识。

我对使用模式的 XPers 的建议是

  • 投入时间学习模式
  • 专注于何时应用模式(不要太早)
  • 专注于如何首先以最简单的形式实现模式,然后在以后添加复杂性。
  • 如果你加入了一个模式,后来意识到它没有发挥作用——不要害怕再次把它拿出来。

我认为 XP 应该更强调学习模式。我不确定如何将它融入 XP 的实践中,但我相信 Kent 可以想出一个方法。

架构的成长

我们所说的软件架构是什么意思?对我来说,架构这个词传达了一种关于系统核心元素的概念,这些元素很难改变。它是其他元素必须建立的基础。

当你使用演化式设计时,架构扮演什么角色?同样,XP 的批评者指出,XP 忽略了架构,XP 的路线是快速进入代码,并相信重构将解决所有设计问题。有趣的是,他们是对的,这可能是一个弱点。当然,最激进的 XPers——Kent Beck、Ron Jeffries 和 Bob Martin——正投入越来越多的精力来避免任何预先的架构设计。在你真正知道你需要它之前,不要加入数据库。首先使用文件,并在以后的迭代中重构数据库。

众所周知,我是一个胆小的 XP 实践者,因此我不得不表示不同意。我认为,广泛的起点架构是有其作用的。例如,在早期就说明如何分层应用程序、如何与数据库交互(如果需要的话)、使用什么方法来处理 Web 服务器。

从本质上讲,我认为许多领域都是我们多年来积累的模式。随着你对模式的了解不断加深,你应该能够对如何使用它们有一个合理的初步认识。然而,关键的区别在于,这些早期的架构决策并不期望是板上钉钉的,或者说团队知道他们可能会在早期的决策中犯错,并且应该有勇气去修正它们。其他人讲述过一个项目的故事,该项目在接近部署时,决定不再需要 EJB,并将其从系统中删除。这是一个相当大的重构,而且是在后期完成的,但支持性的实践使其不仅成为可能,而且值得这样做。

如果反过来呢?如果你决定不使用 EJB,那么以后再添加它是否会更难?因此,你是否应该在尝试过不使用 EJB 的方法并发现它不足之前,永远不要从 EJB 开始?这是一个涉及许多因素的问题。当然,在没有复杂组件的情况下工作会提高简单性,并使工作更快。然而,有时,移除某些东西(比如 EJB)比添加它更容易。

因此,我的建议是先评估一下可能的架构。如果你看到大量数据和多个用户,那么从第一天开始就使用数据库。如果你看到复杂的业务逻辑,那么就加入一个领域模型。然而,为了尊重 YAGNI 的原则,在有疑问的情况下,应该倾向于简单性。此外,一旦你发现架构的某一部分没有增加任何价值,就准备好简化你的架构。

UML 与 XP

在我收到的关于我参与 XP 的所有问题中,其中一个最大的问题围绕着我与 UML 的关系。这两者不是不兼容的吗?

存在一些不兼容的地方。当然,XP 在很大程度上淡化了图表的重要性。虽然官方立场是“如果它们有用就使用它们”,但有一个强烈的潜台词是“真正的 XP 实践者不使用图表”。这一点得到了像 Kent 这样的人的证实,他们对图表很不舒服,事实上,我从未见过 Kent 自愿用任何固定的符号绘制软件图表。

我认为这个问题源于两个不同的原因。一个是有些人发现软件图表有用,而有些人则不。危险在于,那些认为有用的人认为那些认为没用的人也应该使用,反之亦然。相反,我们应该接受有些人会使用图表,而有些人则不会。

另一个问题是,软件图表往往与重量级的流程相关联。这种流程会花费大量时间绘制无用的图表,甚至可能造成损害。因此,我认为应该建议人们如何有效地使用图表并避免陷阱,而不是通常从 XP 实践者那里得到的“只有在必须的情况下才使用(懦夫)”的信息。

因此,以下是我关于如何有效地使用图表的建议。

首先,要记住你绘制图表的目的是什么。主要价值在于沟通。有效的沟通意味着选择重要的事物,而忽略不重要的事物。这种选择性是有效使用 UML 的关键。不要绘制所有类,只绘制重要的类。对于每个类,不要显示所有属性和操作,只显示重要的属性和操作。不要为所有用例和场景绘制序列图,只绘制……你懂的。使用图表的常见问题是,人们试图使它们变得全面。代码是全面信息的最佳来源,因为代码是最容易与代码保持同步的东西。对于图表来说,全面性是可理解性的敌人。

图表的常见用途是在开始编写代码之前探索设计。你经常会得到这样的印象,即这种活动在 XP 中是非法的,但事实并非如此。许多人说,当你遇到一个棘手的问题时,值得花点时间进行一个快速的设计会议。但是,当你进行这样的会议时,

  • 要保持简短
  • 不要试图解决所有细节(只解决重要的细节)
  • 将最终的设计视为草图,而不是最终设计

最后一点值得扩展。当你进行一些前期设计时,你不可避免地会发现设计的一些方面是错误的,而且你只有在编码时才会发现这一点。这不是问题,只要你随后更改设计。麻烦的是,当人们认为设计已经完成时,他们不会将通过编码获得的知识反馈到设计中。

更改设计并不一定意味着更改图表。绘制有助于你理解设计的图表,然后丢弃这些图表是完全合理的。绘制它们是有帮助的,这足以使它们变得有价值。它们不必成为永久的工件。最好的 UML 图表不是工件。

许多 XP 实践者使用 CRC 卡片。这与 UML 并不冲突。我一直使用 CRC 和 UML 的混合方法,根据手头的任务选择最有效的技术。

UML 图表的另一个用途是持续文档。在通常的形式中,这是一个驻留在案例工具中的模型。其理念是,维护这些文档有助于人们在系统上工作。在实践中,它往往毫无帮助。

  • 维护图表需要花费太长时间,因此它们与代码脱节
  • 它们隐藏在 CASE 工具或厚厚的活页夹中,所以没有人会去看它们

因此,关于持续文档的建议来自这些观察到的问题

  • 只使用那些可以在没有明显痛苦的情况下保持更新的图表
  • 将图表放在每个人都能轻松看到的地方。我喜欢将它们贴在墙上。鼓励人们用笔编辑墙上的副本,以进行简单的更改。
  • 注意人们是否在使用它们,如果没有,就扔掉它们。

使用 UML 的最后一个方面是用于交接情况下的文档,例如,当一个小组将系统移交给另一个小组时。这里的 XP 观点是,生成文档与任何其他用户故事一样,是一个用户故事,因此它的业务价值由客户决定。同样,UML 在这里也很有用,只要图表具有选择性以帮助沟通。请记住,代码是详细信息的存储库,图表的作用是总结和突出显示重要问题。

关于隐喻

好吧,我也可以公开地说,我仍然没有掌握这个隐喻的东西。我看到它在 C3 项目中起作用,而且效果很好,但这并不意味着我知道如何去做,更不用说如何解释如何去做。

XP 的隐喻实践建立在 Ward Cunningham 的命名系统方法之上。关键是,你想出一个众所周知的命名集,作为谈论领域的词汇。这个命名系统会影响你在系统中命名类和方法的方式。

我通过构建领域的概念模型来构建命名系统。我与领域专家一起使用 UML 或其前身来完成这项工作。我发现你必须小心地进行这项工作。你需要保持最小的简单符号集,并且必须防止任何技术问题渗透到模型中。但是,如果你这样做,我发现你可以用它来构建领域的词汇,领域专家可以理解并用它与开发人员进行沟通。该模型并不完全匹配类设计,但足以让整个领域拥有一个共同的词汇。

现在,我认为没有理由这个词汇不能是一个隐喻性的词汇,比如 C3 的隐喻,它将工资单变成了工厂流水线。但我也不认为将你的命名系统建立在领域的词汇基础上是一个糟糕的主意。我也不会放弃对我来说在获得命名系统方面效果很好的技术。

人们经常批评 XP,理由是,你至少需要对系统进行一些概要设计。XP 实践者经常用“那就是隐喻”来回答。但我仍然认为我没有看到隐喻以一种令人信服的方式得到解释。这是 XP 的一个真正的差距,也是 XP 实践者需要解决的一个问题。

长大后你想成为一名架构师吗?

在过去十年的很大一部分时间里,“软件架构师”一词变得流行起来。对我个人来说,这是一个难以使用的词。我的妻子是一名结构工程师。工程师和建筑师之间的关系……很有趣。我最喜欢的一句话是“建筑师擅长三件事:灯泡、灌木、鸟”。意思是,建筑师会想出所有这些漂亮的图纸,但真正确保它们能够站立起来的是工程师。因此,我一直避免使用“软件架构师”这个词,毕竟,如果我自己的妻子都不能尊重我的职业,那么我还能指望其他人尊重我吗?

在软件中,“架构师”一词意味着很多东西。(在软件中,任何词语都意味着很多东西。)然而,总的来说,它传达了一种威严,就像“我不仅仅是一个程序员,我是一个架构师”。这可能会转化为“我现在是一个架构师,我太重要了,不能做任何编程”。那么问题就变成了,当你想要行使技术领导力时,是否应该将自己与平凡的编程工作分离。

这个问题引发了大量的感情。我见过有些人对他们不再有架构师的角色感到非常愤怒。“XP 中没有经验丰富的架构师的位置”是我经常听到的呼声。

就像设计本身的角色一样,我认为 XP 并不贬低经验或良好的设计技能。事实上,许多 XP 的倡导者——Kent Beck、Bob Martin,当然还有 Ward Cunningham——都是我从他们那里学到了很多关于设计是什么的知识。然而,这意味着他们的角色与许多人所认为的技术领导力的角色有所不同。

举个例子,我将引用 Thoughtworks 的一位技术领导者:Dave Rice。Dave 经历过几个生命周期,并在一个 50 人的项目中担任了非正式的技术主管。他作为领导者的角色意味着要花很多时间与所有程序员在一起。当程序员需要帮助时,他会与他们一起工作,他会四处看看谁需要帮助。一个重要的标志是他坐的位置。作为一名长期 ThoughtWorker,他几乎可以拥有他喜欢的任何办公室。他与发布经理 Cara 共用了一个办公室一段时间。然而,在过去的几个月里,他搬到了程序员工作的开放式工作区(使用 XP 喜欢的开放式“作战室”风格)。这对他很重要,因为这样他就能看到正在发生的事情,并且可以随时伸出援助之手。

那些了解 XP 的人会意识到,我正在描述 XP 中明确的教练角色。事实上,XP 做的几个文字游戏之一就是将领先的技术人物称为“教练”。含义很清楚:在 XP 中,技术领导力体现在教导程序员和帮助他们做出决策。这需要良好的沟通能力和良好的技术能力。Jack Bolles 在 XP 2000 上评论说,现在没有多少空间容纳孤独的主人。协作和教学是成功的关键。

在一次会议晚宴上,我和 Dave 与一位 XP 的强烈反对者进行了交谈。当我们讨论我们所做的事情时,我们方法的相似之处非常明显。我们都喜欢自适应的迭代开发。测试很重要。所以我们对他强烈反对感到困惑。然后他发表了类似于“我最后不想让我的程序员重构和乱搞设计”的言论。现在一切都清楚了。Dave 后来对我说:“如果他不信任他的程序员,为什么还要雇佣他们?”,这进一步解释了概念上的差距。在 XP 中,经验丰富的开发人员可以做的最重要的事情是将尽可能多的技能传授给更初级的开发人员。你不需要一个做出所有重要决定的架构师,你只需要一个教导开发人员做出重要决定的教练。正如 Ward Cunningham 指出的那样,通过这样做,他放大了自己的技能,并且为项目贡献了比任何独行侠都多的价值。

可逆性

在 XP 2002 上,Enrico Zaninotto 做了一个引人入胜的演讲,讨论了敏捷方法与精益制造之间的联系。他的观点是,这两种方法的关键方面之一是它们通过减少流程中的不可逆性来应对复杂性。

从这个角度来看,复杂性的主要来源之一是决策的不可逆性。如果你可以轻松地改变你的决定,这意味着让它们正确变得不那么重要——这会让你的生活变得更简单。对演化设计的启示是,设计人员需要考虑如何避免决策的不可逆性。与其现在试图做出正确的决定,不如寻找一种方法,要么将决定推迟到以后(当你拥有更多信息时),要么以一种可以稍后轻松撤销的方式做出决定,而不会造成太多困难。

这种对可逆性的支持决心是敏捷方法非常重视源代码控制系统并将所有内容都放入这种系统的原因之一。虽然这不能保证可逆性,特别是对于长期存在的决策,但它确实提供了一个基础,让团队充满信心,即使它很少被使用。

为可逆性设计还意味着一个能够快速发现错误的流程。迭代开发的价值之一是,快速的迭代可以让客户看到系统随着时间的推移而增长,如果在需求中犯了错误,可以在修复成本变得过高之前发现并修复它。这种快速发现对于设计也很重要。这意味着你必须设置好系统,以便快速测试潜在的故障区域,以查看出现哪些问题。这也意味着值得进行实验,看看未来的更改有多难,即使你实际上现在没有进行真正的更改——有效地对系统的一个分支进行一次一次性原型。几个团队报告说,他们尝试在原型模式下尽早尝试未来的更改,以查看它有多难。

设计意愿

虽然我在本文中集中讨论了许多技术实践,但很容易忽略的是人的因素。

为了使演化设计起作用,它需要一种驱使它收敛的力量。这种力量只能来自人——团队中的某个人必须有决心确保设计质量保持高水平。

这种意志并不需要来自每个人(虽然如果来自每个人会很好),通常团队中只有一两个人承担起保持设计完整性的责任。这通常是“架构师”一词所涵盖的任务之一。

这种责任意味着要不断关注代码库,查看其中是否有任何区域变得混乱,然后迅速采取行动纠正问题,以免它失控。设计维护者不必是修复它的人——但他们必须确保它被某个人修复。

缺乏设计意愿似乎是演化设计失败的主要原因之一。即使人们熟悉我在本文中讨论的内容,如果没有这种意愿,设计就不会发生。

难以重构的事物

我们能否使用重构来处理所有设计决策,或者是否存在一些问题过于普遍,以至于难以在以后添加?目前,XP 正统观点认为,所有事情在你需要时都很容易添加,因此 YAGNI 始终适用。我想知道是否有一些例外。一个在以后添加起来有争议的例子是国际化。这是否是一件在以后添加起来很痛苦的事情,以至于你应该从一开始就添加它?

我可以很容易地想象有一些事情会属于这一类。然而,现实是我们仍然没有太多数据。如果你必须在以后添加一些东西,比如国际化,你会非常清楚地意识到这样做需要付出多少努力。你不太清楚在实际需要之前,每周都添加和维护它实际上需要付出多少努力。你也不太清楚你可能已经做错了,因此需要进行一些重构。

YAGNI 的部分理由是,许多这些潜在的需求最终没有被需要,或者至少没有按照你预期的方式被需要。通过不这样做,你会节省大量的努力。虽然将简单的解决方案重构为你实际需要的解决方案需要付出努力,但这种重构可能比构建所有有问题的功能需要更少的努力。

需要牢记的另一个问题是,你是否真的知道如何做。如果你已经多次进行国际化,那么你就会知道你需要使用哪些模式。因此,你更有可能做对。如果你处于这种状态,那么添加预期结构可能比你对问题不熟悉时更好。所以我的建议是,如果你知道如何做,你就可以判断现在做和以后做的成本。但是,如果你以前没有做过,不仅你无法很好地评估成本,而且你也不太可能做得很好。在这种情况下,你应该在以后添加它。如果你确实添加了它,并且发现它很痛苦,那么你可能比你早点添加它时要好。你的团队更有经验,你更了解领域,你更了解需求。在这种情况下,你经常会回过头来看,用 20/20 的后见之明,看看它有多容易。添加它可能比你想象的要困难得多。

这也与关于故事排序的问题相关。在Planning XP中,Kent 和我公开表示了我们的分歧。Kent 赞成让业务价值成为驱动故事排序的唯一因素。在最初的意见分歧之后,Ron Jeffries 现在同意这一点。我仍然不确定。我认为这需要在业务价值和技术风险之间取得平衡。这将促使我在早期提供一些国际化功能,以降低这种风险。但是,这只有在第一个版本需要国际化功能的情况下才成立。尽快发布一个版本至关重要。如果第一个版本不需要任何额外的复杂性,那么在第一个版本发布之后再做这些事情是值得的。已发布的运行代码的力量是巨大的。它集中了客户的注意力,提高了信誉,并且是学习的巨大来源。尽你所能,让这个日期更近。即使在第一个版本发布之后添加一些东西需要付出更多努力,但早点发布更好。

对于任何新技术来说,它的倡导者不确定其边界条件是自然的。大多数 XP 专家都被告知,演化设计对于某个问题来说是不可能的,只是后来发现它确实是可能的。克服“不可能”的情况会带来一种信心,即所有此类情况都可以克服。当然,你不能做出这样的概括,但是直到 XP 社区遇到边界并失败,我们永远无法确定这些边界在哪里,并且尝试突破其他人可能看到的潜在边界是正确的。

(Jim Shore 最近发表的一篇文章讨论了一些情况,包括国际化,其中潜在的边界最终并没有成为障碍。)

设计正在发生吗?

演化设计的一个难点是,很难判断设计是否真的在进行。将设计与编程混合在一起的危险在于,编程可以在没有设计的情况下进行——这就是演化设计偏离并失败的情况。

如果你在开发团队中,那么你可以通过代码库的质量来判断设计是否正在进行。如果代码库变得越来越复杂,越来越难以使用,那么设计做得还不够。但遗憾的是,这是一种主观的观点。我们没有可靠的指标可以让我们对设计质量有一个客观的看法。

如果这种缺乏可见性对于技术人员来说很难,那么对于团队中非技术成员来说就更令人担忧了。如果你是一名经理或客户,你如何才能判断软件是否设计良好?这对你很重要,因为设计糟糕的软件在将来修改起来会更昂贵。对此没有简单的答案,但这里有一些提示。

  • 倾听技术人员的意见。如果他们抱怨更改的难度,那么认真对待这些抱怨,并给他们时间来修复问题。
  • 关注有多少代码被丢弃。一个进行健康重构的项目会不断删除不良代码。如果没有任何东西被删除,那么几乎可以肯定地表明重构做得不够——这会导致设计退化。但是,与任何指标一样,这也会被滥用,优秀技术人员的意见胜过任何指标,尽管它具有主观性。

那么设计真的死了吗?

设计本质上并没有改变,但它已经改变了。XP 设计寻求以下技能

  • 始终如一地希望将代码保持得尽可能清晰和简单
  • 重构技能,以便你可以在看到需要时自信地进行改进。
  • 对模式的良好了解:不仅是解决方案,还包括何时使用它们以及如何演化为它们的理解。
  • 以未来的更改为目标进行设计,知道现在做出的决定将来需要更改。
  • 知道如何使用代码、图表,最重要的是对话,将设计传达给需要理解它的人。

这是一组可怕的技能,但成为一名优秀的设计师一直都很困难。XP 并没有真正让它变得更容易,至少对我来说没有。但我认为 XP 确实为我们提供了一种思考有效设计的新方法,因为它使演化设计再次成为一种可行的策略。我非常喜欢进化——否则谁知道我可能会变成什么样?


致谢

在过去的几年里,我从许多优秀的人那里学习并窃取了许多好主意。其中大多数都消失在我的记忆的模糊中。但我确实记得从 Joshua Kerievsky 那里窃取了一些好主意。我还记得 Fred George 和 Ron Jeffries 的许多有益的评论。我也不会忘记 Ward 和 Kent 不断涌现的许多好主意。

我总是感谢那些提出问题并发现错别字的人。我一直没有认真地记录这些人的名单以表示感谢,但其中包括 Craig Jones、Nigel Thorne、Sven Gorts、Hilary Nelson、Terry Camerlengo。

进一步阅读

有关更多关于我软件设计方法的文章,请参阅我网站的软件设计指南页面。这包含了一些文章,涉及演化设计的关键方面,例如数据库的演化设计服务契约接口

有关进化式设计技术的更多详细信息,请查看 Neal Ford 的 developerWorks 系列视频研讨会

重大修订

2004 年 5 月:添加了关于“设计意愿”、“可逆性”和“设计是否正在发生”的部分。

2001 年 2 月:文章更新了关于架构增长、架构师的作用以及重构难以添加内容的部分。

2000 年 7 月:原始文章提交给 XP 2000 并发布到 martinfowler.com