两个即为过多
2015年12月21日 作者:Max Kanat-Alexander
在进行增量开发和设计时,我遵循一个关键原则,称之为“两个即为过多”。这是我实现《软件设计三大缺陷》中“只需必要的通用性”规则的具体方式。
本质上,当我发现想要复制粘贴某些代码时,我能准确判断代码需要多通用——此时我不会复制粘贴,而是设计一个仅满足这两个特定需求的通用解决方案。一旦我意识到需要某个东西的两个实现,就会立即这样做。
例如,假设我正在设计一个音频解码器,最初只支持WAV文件。后来我想在代码中添加MP3解析器。WAV和MP3解析代码肯定有共同部分,我不会复制粘贴任何部分,而是立即创建一个超类或工具库,仅满足这两个实现的需求。
关键点在于我立即行动——不允许存在两个竞争实现;我立即创建一个通用解决方案。另一个重要方面是我不让它过于通用——该解决方案仅支持WAV和MP3,不以任何方式预期其他格式。
该规则的另一个部分是,理想情况下,开发人员不应以类似或相同的方式修改代码的某个部分,就像他们刚刚修改了另一个部分那样。他们不应在更新B类时“记住”要更新A类。他们不应知道如果常量X改变,就必须更新文件Y。换句话说,不仅两个实现不好,两个位置也不好。虽然并不总能以这种方式实现系统,但这是值得追求的目标。
如果你发现自己必须为某个东西设置两个位置,请确保当它们不同步时,系统会明显且可见地失败。编译应失败,始终运行的测试应失败等。应不可能让它们不同步。
当然,该规则最简单的部分是经典的“不要重复自己”原则——不要有两个代表完全相同事物的常量,不要有两个执行完全相同功能的函数等。
可能还有其他适用此规则的方式。总体思路是,当你想为单个概念提供两个实现时,应以某种方式将其变成单个实现。
在重构时,此规则有助于发现可改进之处,并提供一些如何进行的指导。当你看到系统中的重复逻辑时,应尝试将这两个位置合并为一个。然后,如果有另一个位置,将其合并到新的通用系统中,并以此方式继续。也就是说,如果需要将许多不同的实现合并为一个,你可以通过一次合并两个实现来进行增量重构,只要合并它们确实使系统更简单(更易于理解和维护)。有时你必须找出合并的最佳顺序以提高效率,但如果无法确定,不必担心——只需一次合并两个,通常你会得到一个解决所有问题的单一良好方案。
同样重要的是,不应在不应该合并的时候合并东西。有时将两个实现合并为一个会导致整体系统更复杂或违反单一职责原则。例如,如果你的系统中Car和Person的表示有一些略微相似的代码,不要通过将它们合并为一个CarPerson类来解决这个“问题”。这不太可能降低复杂性,因为CarPerson实际上是两个不同的东西,应该由两个单独的类表示。
这不是宇宙的硬性法则——更多是我在增量开发中用于判断设计的强烈指南。然而,它在重构遗留系统、开发新系统以及普遍提高代码简洁性方面非常有用。
-Max
分享 点击在Facebook上分享(在新窗口中打开) Facebook 点击在LinkedIn上分享(在新窗口中打开) LinkedIn 点击在Hacker News上分享(在新窗口中打开) Hacker News 点击在Reddit上分享(在新窗口中打开) Reddit 点击在Threads上分享(在新窗口中打开) Threads 点击在X上分享(在新窗口中打开) X
22条评论 发表回复
Alex Vincent 说:2015年12月21日下午1:51 就个人而言,当有三个或更多不同实现时,我会进行合并。当然,一个是最理想的。两个让我感觉不舒服,但有时有充分理由。三个简直不可接受,是马虎代码的标志。 回复
David Cuccia 说:2015年12月21日下午3:45 我同意Alex的观点。在我早期,即使只有一个实现,我也会过度设计某些东西。现在,我通常等到代码需要在三个独立的地方时才集中处理。很多时候,维护中央库的负担超过了避免同步问题带来的温暖模糊感。 回复
David Cuccia 说:2015年12月21日下午3:45 (避免同步问题) 回复
Codesimplicity – Two is too many – yaleman.org 说:2015年12月21日晚上9:51 […] Codesimplicity – Two is too many […] 回复
Jens Bannmann 说:2015年12月22日凌晨3:42 嗯,我支持Max:两个确实太多了。 虽然理论上,Alex等待第三个实现的方法感觉不错,但我的经验表明,多年来,其他开发人员通常会盲目跟随两个不同实现所建立的先例。等到有人最终想到合并它们时,可能不是三个,而是例如七个竞争实现。在我看来,几乎总是从一开始就做更好。只有一个统一的实现也会提高可读性,帮助新开发人员理解系统。 回复
Max Kanat-Alexander 说:2016年9月18日晚上10:35 是的,根据我的经验,“两个即为过多”方法的优点在于它可靠有效。可能有很多其他好的理论原因使用另一种方法,但我 simply haven’t seen them actually work to keep a codebase maintainable. -Max 回复
Ian Thomas 说:2015年12月23日凌晨6:21 “三个即为过多”方法的一个问题是,当你编写第三个实现时,现有的实现可能已经分化,在它们可以合并为一个通用实现之前需要工作。 回复
Max Kanat-Alexander 说:2016年9月18日晚上10:34 是的,它有很多问题。另一个问题是,注意到有两个实现比注意到有三个要容易得多。也就是说,可能有三个实现,但开发人员只找到另一个实现,因此认为只有两个,并等待出现三个。而且,正如你指出的,合并三个实现可能比合并两个然后 later enhancing the combined solution when you need it to do something slightly different 要多得多的工作。在非常大的代码库中,这一点变得非常明显,甚至在有些情况下合并两个现有实现都很困难。 回复
Alex Vincent 说:2015年12月23日上午7:08 值得一提的是,我也不是说这是一个硬性规定。我发现,很大一部分与你有多少能力和影响力来进行此类更改有关……但这更多是关于项目/时间管理,而不是一名普通高级工程师想做正确的事情。 我说两个实现让我感觉不舒服。在可行的情况下,我会尝试合并它们,而不是等待第三个。 回复
Mark 说:2015年12月23日上午8:40 谢谢提醒,Max。好东西。 回复
Jim 说:2015年12月31日下午4:40 我同意在Max的例子中,CarPerson类是一个糟糕(实际上,我认为是可怕)的想法;然而,有时将完全相同或几乎完全相同的代码或逻辑提取到一个单独的类或模块中,并通过你所使用语言中可用的任何适当机制(例如,某种类型的继承,或在Ruby中,“include”机制)在任何适当的类(Max例子中的Car和Person)中使用这些共享代码/逻辑是有用的。这样的设计可能在Car与Person的例子中是合适的(尽管没有具体细节,我不能肯定)。我相信,在Meyer的继承分类中(http://se.ethz.ch/~meyer/publications/computer/taxonomy.pdf),这种情况通常会被归类为“实现继承”。 回复
Anil 说:2016年1月10日凌晨1:29 再同意不过了。我们需要特别注意在维护别人编写的旧代码时处理这个问题。在那里,我反复遇到,不仅仅是2个,而是至少5-6个需要做相同更改的地方。我从事软件开发已经10年了(从C开始,然后转到C++,现在是Java),我正在将我的方法从OOP转向函数式,并看到生产力的显著提高。 回复
abdullah 说:2016年2月19日上午8:35 作者有很多有效的观点,肯定每个开发人员都应该努力追求提到的原则。但回到现实世界,预算和时间可能是个问题,你如何证明更改一个已经功能的系统部分是合理的?你的老板会高兴你现在必须回去测试一些不在项目范围内的东西吗? 另一个方面是,花时间制作一个通用实现可能很棒——但如果你从不重用那段代码呢?因此你的解决方案变得过度工程化,这意味着更长的开发周期(以及修复bug的更多努力)。 作为一个通用原则,这种方法是有道理的,但像任何事情一样,它需要适度和常识来调和。 回复
Shalaka Virkar 说:2016年5月11日下午12:16 这是一个非常合理的担忧。但我认为这是单元测试发挥非常重要作用的地方。如果你有好的单元测试作为安全网,那么它应该帮助你发现是否破坏了任何东西。如果需要,你应该添加更多。话虽如此,端到端测试没有替代品。一个好的测试自动化框架应该处理这个问题。如果时间允许,你也可以进行手动测试。 回复
Max Kanat-Alexander 说:2016年9月18日晚上10:25 我在《测试哲学》中详细讨论了这一点。 回复
Max Kanat-Alexander 说:2016年9月18日晚上10:29 Abdullah,总体我建议你阅读这本书,它回答了所有这些问题以及更多。 -Max 回复
Kamran 说:2016年2月20日下午3:37 我编程多年,从高中起就弹钢琴和学习音乐(我47岁)。我看到编程和创作严肃音乐之间有很多相似之处。音乐中有句谚语,大致是:“如果它能让你的音乐听起来更好,你可以忽略音乐中的任何规则”。编程或音乐(以及许多其他领域)的规则或模型只是一些指南,一旦你掌握了它们,你就会看到它们的优点和缺点,然后可以在你想的时候不使用它们,即使别人说,“嘿,你做的事情不对!”,如果你知道自己在做什么,让他们说任何他们想说的。 回复
Shalaka Virkar 说:2016年5月11日下午12:35 这是一个非常合理的担忧。但我认为这是单元测试发挥非常重要作用的地方。如果你有好的单元测试作为安全网,那么它应该帮助你发现是否破坏了任何东西。如果需要,你应该添加更多。话虽如此,端到端测试没有替代品。一个好的测试自动化框架应该处理这个问题。如果时间允许,你也可以进行手动测试。 回复
Dave Beck 说:2016年7月3日下午1:21 复制和粘贴就像往嘈杂的变速箱里倒锯末——看起来你取得了进展,但你以后会为此付出代价。 回复
Code Simplicity » Kindness and Code 说:2017年8月12日上午8:07 […] 这行代码很难理解,这看起来像代码重复。你能重构它,使得它 […] 回复
Kindness and Code - Best Application Development Platforms, Software, App Developers 说:2017年8月16日凌晨4:18 […] 这行代码很难理解,这看起来像代码重复。你能重构它,使得它 […] 回复
Ruslan Spivak: Let’s Build A Simple Interpreter. Part 15. – Cebu Scripts 说:2019年6月22日凌晨4:10 […] 我们可以移除上面的手动步骤(b),并将令牌类型仅保留在一个地方。这是“两个即为过多”规则在行动——向前看,添加新关键字唯一需要做的更改 […] 回复
发表回复取消回复
联系 关于 书:《理解软件》 书:《代码简洁性》
输入您的邮箱… 订阅
Max Kanat-Alexander 6天前 非常兴奋地宣布,我将在2025年开发者生产力工程峰会上发表主题演讲之一。我将谈论什么造就了伟大的开发者体验。我自1995年以来一直在技术领域工作,自2004年以来几乎全职从事开发者体验。关于如何使开发者体验变得伟大,有一些基本的、普遍的真理,我将尽力在有限的时间内涵盖尽可能多的内容。希望能见到你。:) 阅读更多1177分享
Max Kanat-Alexander 6月27日 我目前认为,AI代理能成功生成的输出数量和质量取决于:
- 模型的质量。
- 代理的质量。
- 输入的质量(如提示或其他上下文)。
- 代理可以独立运行的确定性、客观验证的质量。 我目前认为,除非你是模型开发者,最重要的部分是“确定性、客观验证”。 简单来说,如果你告诉AI做某事,它需要某种方式能够自行验证它做对了。在软件中,这通常意味着运行测试、linter等,如果系统做错了就会失败。 这意味着你的测试和验证工具越好,从模型获得的输出就越好。这不仅仅是测试数量的问题。它们必须是好的测试,具有智能断言和良好的错误消息。 这也意味着代理成功涉及思考如何将任务分解成可以分别通过自动化测试、linter等客观验证的单个部分。 需要注意的是,输入的质量也很重要,并且在我们的控制之下。如果我们编写更好的文档和更清晰的代码,代理会做得更好。令人惊讶的是,几乎一切帮助人类编写代码的东西也帮助代理。 阅读更多2510分享
Max Kanat-Alexander 6月4日 技术债务有价值的想法 mostly a myth。当你做出糟糕的软件工程决策时,它会在几小时、几天或几周内减慢你的速度。那是上限。许多人认为需要数月或数年才能看到在软件设计上偷工减料的影响,但这 simply not true。它几乎立即开始。做正确事的成本通常是几小时,也许一天,而且你几乎总是立即收回那段时间。也就是说,做对通常花费与做错相同的时间。“等等,什么?怎么可能?我们偷工减料是为了节省时间!”结果几乎总是它根本不会节省你的时间!它使处理变更更加混乱。它使代码审查时间更长。它在测试期间以更令人困惑的方式失败,需要更长时间调试。它几乎从不节省你的实际时间。 偶尔,你的路径上会有一些如此巨大的“岩石”,你根本无法移动它。没有人应该花三个月设计一个新工具,只是为了你能一次性地交付一个一天的功能。但这只是技术债务决策的极小一部分。 这变得更加疯狂,因为如果你在过程中做对一切(你总是重构系统,使其看起来像是被设计成 exactly 做它现在做的事情),那么一切在整个过程中都保持相对容易。但如果你偷工减料,一切都会变得复合性地更加困难,以至于你生成没人能移动的“岩石”。复杂性不是某种线性的东西,你可以 later invest a linear amount of time to fix。你绝对可以让自己陷入一种如此糟糕的境地,以至于你公司中没有人有时间或专业知识来修复它。 这一切感觉如此无辜:“让我们偷工减料吧,它会帮助我们 meet this deadline,”(一个通常是虚构的截止日期,你可以推迟几周而没有任何后果)。“很难弄清楚如何做对,我们能推迟吗?”然后砰,很快你发现自己陷入了一个沼泽,一切都很难,而且“我们不知道为什么!” 我最后留给你一件事:当我每天直接编码时,我在这方面完全 uncompromising。我基本上无法看坏代码——我必须重构它,否则我无法工作。不知何故,在我整个职业生涯的那部分,我从未错过一个截止日期。 阅读更多12942分享
Max Kanat-Alexander 5月30日 我估计,每看到或被告知一万条坏建议,我才会收到一条好建议。互联网充满了坏建议,因为文明充满了坏建议。文明充满了坏建议,因为全球的教育大多不教人们思考、研究或确定什么是真实的。相反,它教人们 memorizing and repeating facts,其中大多数是不重要、不真实或无用的。 所以总的来说,我们不应惊讶互联网(尤其是社交媒体)充满了不重要、不真实或无用的信息。并非全部如此——我在网上学到了大量非常有用的东西。有大量有价值的内容。我会说,人们对事物的看法,无论是在报纸、博客文章、论坛还是其他什么地方, rarely had that value。 那么,启示是什么?嗯,最好有一些方法在开始依赖或向他人重复之前确定某事是否重要或有用的。最简单的工具是说:“它有效吗?”这条数据是否可靠地帮助你完成某事?它100%有效,还是更少? 对于许多帖子,你会发现:这些数据根本无助于你做任何事情。 阅读更多249分享 加载更多
© 2025 版权所有。由 The Fox 提供支持。 管理同意联系 关于 书:《理解软件》 书:《代码简洁性》 转到顶部