二即是多:代码简洁性的重构艺术

本文探讨软件开发中"二即是多"原则,强调避免重复代码和实现,通过即时重构创建通用解决方案,提升代码可维护性和简洁性,包含实际案例和开发者讨论。

二即是多 » 代码简洁性

管理同意

为了提供最佳体验,我们使用Cookie等技术存储和/或访问设备信息。同意这些技术将允许我们处理诸如浏览行为或本网站唯一ID等数据。不同意或撤回同意可能会对某些特性和功能产生不利影响。

功能性

始终活跃 技术存储或访问严格用于合法目的,即启用订阅者或用户明确请求的特定服务,或仅用于通过电子通信网络进行通信传输。

偏好

技术存储或访问对于存储订阅者或用户未请求的偏好是必要的。

统计

技术存储或访问仅用于统计目的。仅用于匿名统计目的的技术存储或访问。没有传票、互联网服务提供商的自愿合规或第三方的额外记录,仅为此目的存储或检索的信息通常无法用于识别您的身份。

营销

需要技术存储或访问来创建用户配置文件以发送广告,或在网站或跨多个网站上跟踪用户以用于类似的营销目的。

管理选项 管理服务 管理{vendor_count}供应商 阅读更多关于这些目的 接受 拒绝 查看偏好 保存偏好 查看偏好 Cookie政策 {title} 印记

代码简洁性

二即是多

2015年12月21日 by Max Kanat-Alexander

我在进行增量开发和设计时遵循一个关键规则,我称之为"二即是多"。这是我实现《软件设计的三个缺陷》中"只需尽可能通用"规则的方式。

本质上,我通过注意到自己 tempted 要剪切粘贴某些代码,然后不是剪切粘贴它,而是设计一个仅满足这两个特定需求的通用解决方案,来确切知道我的代码需要多通用。一旦我 tempted 要对某个东西有两个实现,我就会这样做。

例如,假设我正在设计一个音频解码器,最初我只支持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 是的,根据我的经验,“二即是多"方法的优点是它可靠地工作。可能有很多其他好的理论原因使用另一种方法,但我根本没有看到它们实际工作以保持代码库可维护。 -Max 回复

Ian Thomas 说: 2015年12月23日凌晨6:21 “三即是多"方法的一个问题是,当你开始编写第三个实现时,你现有的实现可能已经分叉,它们需要工作才能合并成一个通用实现。 回复

Max Kanat-Alexander 说: 2016年9月18日晚上10:34 是的,它有很多问题。另一个是,注意到有两个实现比注意到有三个要容易得多。也就是说,可能有三个实现,但开发人员只找到另一个实现,所以认为只有两个并等待有三个。而且是的,正如你指出的,合并三个实现可能比合并两个然后在你需要它做稍微不同的事情时增强合并的解决方案要工作得多。在非常大的代码库上,这变得非常明显,在某些情况下合并甚至两个现有实现都很困难。 回复

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

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月27日 我目前认为,AI代理可以成功输出的数量和质量取决于:

  1. 模型的质量。
  2. 代理的质量。
  3. 输入的质量(如提示或其他上下文)。
  4. 代理可以独立运行的确定性、客观验证的质量。 我目前认为,除非你是模型开发者,否则最重要的部分是"确定性、客观验证”。 简单来说,如果你告诉AI做某事,它需要某种方式能够验证它做了正确的事,靠自己。在软件中,这通常意味着运行一个测试、一个linter等,如果系统做错了就会失败。 这意味着你的测试和验证工具越好,你从模型得到的输出就越好。不仅仅是测试的数量。它们必须是好的测试,有智能断言和好的错误消息。 这也意味着代理成功涉及思考如何将任务分解成可以分别由自动化测试、linter等客观验证的单个部分。 作为说明,输入的质量也很重要并在我们的控制之下。如果我们写更好的文档和更清晰的代码,代理会做得更好。令人惊讶的是,几乎一切帮助人类写代码的东西也帮助代理。 阅读更多2310分享

Max Kanat-Alexander 6月4日 技术债务有价值的想法 mostly 是一个神话。当你做出一个糟糕的软件工程决策时,它会在几小时、几天或几周内减慢你的速度。那是上限。许多人认为需要几个月或几年才能看到在软件设计上偷工减料的影响,但那根本不是真的。它几乎立即开始。做正确事的成本通常是几小时,也许一天,而且你几乎总是立即收回那个时间。也就是说,做对通常花费与做错相同的时间。“等等,什么?那怎么可能?我们偷工减料是为了节省时间!“它几乎总是结果根本不会节省你时间!它使处理变更更令人困惑。它使代码审查更长。它在测试期间以更令人困惑的方式失败,需要更长时间调试。它几乎从不节省你实际时间。 偶尔,你的路径中有一些"岩石"如此巨大,你根本无法移动它。没有人应该花三个月设计一个新工具,只是为了你能一次交付一个一天的功能。但那是技术债务决策的极小、极小部分。 这变得更疯狂,因为如果你在过程中做对一切(你总是重构系统,使其看起来像是设计来做 exactly 它现在做的事情),那么一切始终保持相对容易。但如果你偷工减料,一切变得复合性地更困难,到点你生成没人能移动的"岩石”。复杂性不是某种线性东西,你可以后来投入线性时间修复。你绝对可以让自己陷入一个如此糟糕的情况,你公司中没有人有时间或专业知识修复它。 这一切感觉如此无辜:“让我们就偷工减料一点,它会帮助我们 meet 这个截止日期,"(一个截止日期通常是虚构的,无论如何,你可以滑几周没有后果)。“很难弄清楚如何做对这件事,我们可以推迟它吗?“然后砰,很快你发现自己在一个沼泽中,一切都很难,“我们不知道为什么!” 我留给你最后一件事:当我直接编码我生活的每一天时,我对此完全 uncompromising。我基本上 incapable 看坏代码——我必须重构它否则我不能做工作。而且 somehow,我在那部分整个职业生涯中从未错过一个截止日期。 阅读更多12940分享

Max Kanat-Alexander 5月30日 我估计,我每看到或被告知一万条坏建议,才收到一条好建议。互联网充满坏建议,因为文明充满坏建议。文明充满坏建议,因为全球的教育大多不教人们思考、研究或确定什么是真的。相反,它教人们记忆和重复事实,其中大多数不重要、不真实或无

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计