写点什么

代码简洁之道:'两个就是太多'的编程哲学

作者:qife
  • 2025-07-18
    福建
  • 本文字数:3198 字

    阅读完需:约 10 分钟

代码简洁之道:"两个就是太多"的编程哲学

作者:Max Kanat-Alexander


发布日期:2015 年 12 月 21 日


在进行增量开发和设计时,我遵循一个关键原则,称之为"两个就是太多"。这是我对《软件设计的三个缺陷》中"只做到你需要的通用程度"这一规则的具体实践。


简单来说,当我发现自己想要复制粘贴某些代码时,我会意识到代码需要达到的通用程度。这时,我不会直接复制粘贴,而是设计一个刚好满足这两个特定需求的通用解决方案。一旦我发现自己想要为某个功能创建两个实现时,就会立即这样做。


例如,假设我正在设计一个音频解码器,最初只支持 WAV 文件。然后我想在代码中添加 MP3 解析器。WAV 和 MP3 解析代码肯定有共同部分,我不会复制粘贴任何代码,而是立即创建一个超类或工具库,仅满足这两个实现的需求。


关键在于我立即行动——不允许存在两个相互竞争的实现;我立即创建一个通用解决方案。另一个重要方面是我不让它过于通用——这个解决方案只支持 WAV 和 MP3,不以任何方式预期其他格式。


这个规则的另一个部分是,开发人员理想情况下不应该以相似或相同的方式修改代码的一个部分,就像他们刚刚修改另一个部分那样。他们不应该在更新类 B 时"记得"更新类 A。他们不应该知道如果常量 X 改变,就必须更新文件 Y。换句话说,不仅是两个实现不好,两个位置也不好。虽然并不总是能以这种方式实现系统,但这是值得努力的目标。


如果你发现自己不得不为某事物保留两个位置,请确保当它们"不同步"时,系统会明显且可见地失败。编译应该失败,总是运行的测试应该失败等。应该不可能让它们失去同步。


当然,这个规则最简单的部分是经典的"不要重复自己"原则——不要有两个表示完全相同事物的常量,不要有两个做完全相同事情的函数等。


这个规则可能还有其他应用方式。总体思路是,当你想为单一概念创建两个实现时,你应该以某种方式将其变成一个实现。


在重构时,这个规则有助于发现可以改进的地方,并提供一些指导。当你看到系统中的重复逻辑时,应该尝试将这两个位置合并为一个。然后如果有另一个位置,将其合并到新的通用系统中,以此类推。也就是说,如果需要将许多不同的实现合并为一个,你可以通过一次合并两个实现来进行增量重构,只要合并它们确实能使系统更简单(更容易理解和维护)。有时你必须找出最佳合并顺序以提高效率,但如果你无法确定,不用担心——只需一次合并两个,通常你会得到一个解决所有问题的单一好方案。


同样重要的是,不应该在不应该合并的时候合并事物。有时将两个实现合并为一个会增加整个系统的复杂性或违反单一职责原则。例如,如果你的系统中 Car 和 Person 的表示有一些稍微相似的代码,不要通过将它们合并为一个 CarPerson 类来解决这个"问题"。这不太可能降低复杂性,因为 CarPerson 实际上是两个不同的事物,应该由两个单独的类表示。


这不是宇宙的硬性法则——更像是我在增量开发时用于判断设计的强烈指南。然而,它在重构遗留系统、开发新系统以及普遍提高代码简洁性方面非常有用。


  • Max



评论

Alex Vincent 说:


2015 年 12 月 21 日下午 1:51


我个人在有三个或更多不同实现时进行合并。当然,一个是最理想的。两个让我感觉不舒服,但有时有充分的理由。三个就完全不可接受了,是代码马虎的标志。


David Cuccia 说:


2015 年 12 月 21 日下午 3:45


我同意 Alex 的观点。在我早期,我甚至会为一个单一实现过度设计。现在,我通常等到代码需要在三个不同地方时才集中它。很多时候,维护一个中央库的负担超过了避免同步问题带来的满足感。


Jens Bannmann 说:


2015 年 12 月 22 日上午 3:42


我支持 Max 的观点:两个确实太多了。虽然在理论上,Alex 等待第三个实现的方法感觉不错,但我的经验表明,多年来,其他开发人员通常会盲目跟随两个不同实现的先例。等到有人最终想到合并它们时,可能不是三个,而是七个相互竞争的实现。在我看来,几乎总是从一开始就做对更好。只有一个统一的实现也会提高可读性,帮助新开发人员理解系统。


Max Kanat-Alexander 说:


2016 年 9 月 18 日晚上 10:35


是的,根据我的经验,"两个就是太多"的方法可靠有效。可能有很多其他好的理论原因使用其他方法,但我确实没有看到它们实际保持代码库可维护性。


Ian Thomas 说:


2015 年 12 月 23 日上午 6:21


"三个就是太多"方法的一个问题是,当你开始编写第三个实现时,现有的实现可能已经分叉,需要工作才能合并为一个通用实现。


Max Kanat-Alexander 说:


2016 年 9 月 18 日晚上 10:34


确实有很多问题。另一个问题是,注意到有两个实现比注意到有三个实现容易得多。也就是说,可能有三个实现,但开发人员只找到另一个实现,所以认为只有两个并等待出现三个。而且,正如你指出的,合并三个实现可能比合并两个然后在需要时增强合并的解决方案要困难得多。在非常大的代码库中,这一点变得非常明显,有时甚至合并两个现有实现都很困难。


Alex Vincent 说:


2015 年 12 月 23 日上午 7:08


值得一提的是,我也不是说这是一个硬性规则。我发现很大一部分与你是否有能力和影响力首先进行此类更改有关...但这更多是关于项目/时间管理,而不是一个资深工程师想做正确的事情。我说两个实现让我感觉不舒服。在可行的情况下,我会尝试合并它们,而不是等待第三个。


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),我正在将我的方法从面向对象转向函数式,看到了生产力的显著提高。


abdullah 说:


2016 年 2 月 19 日上午 8:35


作者有很多有效的观点,肯定每个开发人员都应该努力遵循这些原则。但在现实世界中,预算和时间可能是问题,你如何证明改变一个已经正常工作的系统部分是合理的?你的老板会高兴你现在必须回去测试一些不在项目范围内的事情吗?另一个方面是,花时间制作一个通用实现可能很棒——但如果你从未重用那段代码呢?因此你的解决方案变得过度设计,这意味着更长的开发周期(以及更多的修复 bug 的工作)。作为一个通用原则,这种方法是有意义的,但像任何事情一样,需要适度和常识来调和。


Shalaka Virkar 说:


2016 年 5 月 11 日中午 12:16


这是一个非常合理的担忧。但我认为这正是单元测试发挥重要作用的地方。如果你有好的单元测试作为安全网,那应该能帮助你发现是否破坏了任何东西。如果需要,你应该添加更多。话虽如此,端到端测试没有替代品。一个好的测试自动化框架应该能解决这个问题。如果时间允许,你也可以进行手动测试。


Max Kanat-Alexander 说:


2016 年 9 月 18 日晚上 10:25


我在《测试的哲学》中详细讨论了这一点。


Kamran 说:


2016 年 2 月 20 日下午 3:37


我编程多年,从高中开始就弹钢琴和学习音乐(我 47 岁)。我看到编程和创作严肃音乐之间有很多相似之处。音乐中有句话说:"如果能让你的音乐听起来更好,你可以忽略任何规则"。编程或音乐(以及许多其他领域)中的规则或模型只是一些指导,一旦你掌握了它们,你就会看到它们的优缺点,然后可以在任何时候不使用它们,即使别人说,"嘿,你做的事情不对!",如果你知道自己在做什么,就让他们说去吧。


Dave Beck 说:


2016 年 7 月 3 日下午 1:21


复制粘贴就像往嘈杂的变速箱里倒锯末——看起来你可能取得了进展,但以后会为此付出代价。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码


办公AI智能小助手


用户头像

qife

关注

还未添加个人签名 2021-05-19 加入

还未添加个人简介

评论

发布
暂无评论
代码简洁之道:'两个就是太多'的编程哲学_设计模式_qife_InfoQ写作社区