代码重复远比错误的抽象便宜
我最初为我的 Chainline Newsletter 撰写了以下内容,但我仍然不断收到关于这个想法的推文,所以我在我的博客上重新发布了这篇文章。这个版本经过了轻微的编辑。我一直在思考“错误抽象”的后果。在我 2014 年的 RailsConf 演讲 "所有这些小事" 中,我提到:重复远比错误的抽象便宜。在总结中,我还建议:更倾向于重复,而不是错误的抽象。这一更大演讲的小部分激发了意外强烈的反应。一些人建议我失去了理智,但更多的人表达了类似于:“这,万分之一的正确!” @BonzoESC:“重复远比错误的抽象便宜” @sandimetz @rbonales pic.twitter.com/3qMI0waqWb — 41 shades of blue (@pims) 2014年3月7日。反应的强度让我意识到“错误抽象”问题是多么普遍而无法解决。我开始询问问题,并看到以下模式:程序员 A 看到重复。程序员 A 提取重复并给它命名。这创造了一个新的抽象。它可能是一个新方法,甚至是一个新类。程序员 A 用新的抽象替换重复。啊,代码完美。程序员 A 高高兴兴地离开了。时间过去了。出现了一个新需求,当前的抽象几乎完美。程序员 B 被指派实现这个需求。程序员 B 感到有责任保留现有抽象,但由于并不是每种情况都完全相同,他们改变代码以接受一个参数,然后根据该参数的值添加条件逻辑来做好事情。曾经是通用抽象的代码现在在不同情况下表现不同。另一个新需求出现。程序员 X。另一个额外的参数。另一个新的条件。循环直至代码变得难以理解。你出现在故事中,生活在此时发生了戏剧性的转变。现有代码施加了强大的影响。它的存在本身就表明它是正确和必要的。我们知道代码代表了付出的努力,而且我们非常有动力保留这份努力的价值。不幸的是,悲伤的真相是,代码越复杂和难以理解,即投入越深,我们越感到保留它的压力(即“沉没成本谬误”)。就好像我们的潜意识告诉我们:“天哪,这么混乱,一定花了很多时间才能搞定。它一定是非常重要的。让所有这些努力付之东流简直是罪过。”当你出现在这个故事中的第 8 步,压力可能迫使你向前推进,即通过更改现有代码来实现新需求。然而,尝试这样做则是残酷的。代码不再代表单一的、通用的抽象,而是变成了一个条件繁多的过程,交织着一些模糊相关的想法。它难以理解且容易出错。如果你发现自己身处这种情况,请抗拒被沉没成本驱动。在处理错误的抽象时,快速的前进之路是回退。采取以下步骤:通过将抽象代码内联回每个调用者来重新引入重复。在每个调用者中,使用传递的参数来确定该具体调用者执行的内联代码子集。删除该特定调用者不需要的部分。这消除了抽象和条件,并将每个调用者简化为仅需要的代码。当你以这种方式回退决策时,常常会发现,尽管每个调用者表面上调用了一个共享抽象,但它们运行的代码基本上是独特的。一旦完全移除旧的抽象,你可以重新开始,重新隔离重复并重新提取抽象。我见过一些人拼命试图以错误的抽象继续前进,但却很少成功。添加新功能是极其困难的,每一次成功进一步复杂化代码,这使得增加下一个功能变得更加困难。当他们改变观点,从“我必须保留我们在这段代码上的投资”到“这段代码曾经是有意义的,但也许我们从中学到的已经够多了”,并给自己许可根据当前需求重新思考他们的抽象时,一切变得简单。一旦他们将代码内联,前进的道路变得显而易见,添加新功能变得更加快速和简单。这则故事的道德是什么?不要被沉没成本谬误所困。如果你发现自己在传递参数并在共享代码中添加条件路径,抽象是错误的。最初它可能是对的,但那一天已经过去。一旦抽象被证明是错误的,最佳策略是重新引入重复,让它向你展示什么是正确的。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡