宁可重复,也不要错误的抽象(2016)
摘要
文章认为,重复比错误的抽象代价更低,并建议开发者避免强行引入抽象,以免后续因条件判断而变得复杂。
暂无内容
查看缓存全文
缓存时间: 2026/06/22 01:34
# 错误的抽象 —— Sandi Metz
来源:https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
*我最初为我的 Chainline Newsletter(http://www.sandimetz.com/subscribe)撰写了以下内容,但不断收到关于这个想法的推文,因此我在此博客上重新发布这篇文章。这个版本经过轻微编辑。*
---
我一直在思考“错误抽象”的后果。在我的 RailsConf 2014 演讲“所有小事(https://youtu.be/8bZh5LMaSmE)”中,有一个部分我断言(https://youtu.be/8bZh5LMaSmE?t=893):
> 重复比错误的抽象廉价得多
在总结中,我继续建议(https://youtu.be/8bZh5LMaSmE?t=2142):
> 优先选择重复而不是错误的抽象
这个更大型演讲中的一小部分引发了一个出人意料的强烈反应。一些人认为我疯了,但更多的人表达了类似这样的情感:
> 这个,说一百万遍这个!"@BonzoESC(https://twitter.com/BonzoESC):"重复比错误的抽象廉价得多"@sandimetz(https://twitter.com/sandimetz)@rbonales(https://twitter.com/rbonales)pic.twitter.com/3qMI0waqWb(http://t.co/3qMI0waqWb)" — 41 shades of blue(@pims)2014年3月7日(https://twitter.com/pims/status/442010383725760512)
反应的强烈程度让我意识到“错误抽象”问题有多么普遍和棘手。我开始提问,并逐渐看到以下模式:
1. 程序员 A 看到了重复。
2. 程序员 A 提取重复并给它一个名字。*这创造了一个新的抽象。可能是一个新方法,甚至可能是一个新类。*
3. 程序员 A 用新的抽象替换了重复。*啊,代码完美了。程序员 A 欢快地离开了。*
4. 时间流逝。
5. 出现一个新需求,当前抽象*几乎*完美。
6. 程序员 B 被指派实现这个需求。*程序员 B 感到有义务保留现有的抽象,但由于它对每个案例并不完全相同,他们修改代码以接受一个参数,然后根据该参数的值添加逻辑以有条件地执行正确的事情。* *曾经是通用抽象的东西现在对不同案例表现不同。*
7. 又一个新需求到来。*程序员 X。又一个附加参数。又一个新条件。循环直到代码变得不可理解。*
8. 你大约在这个时刻出现在故事中,你的生活发生了戏剧性的转折,变得更糟。
现有代码施加了强大的影响。它的存在本身就论证了它是正确且必要的。我们知道代码代表了已经付出的努力,我们非常倾向于保留这努力的价值。而且,不幸的是,悲哀的事实是,代码越复杂和难以理解,即创建它的投资越深,我们感到保留它的压力就越大(“沉没成本谬误(https://en.wikipedia.org/wiki/Sunk_costs#Loss_aversion_and_the_sunk_cost_fallacy)”)。仿佛我们的潜意识告诉我们:“天哪,这太混乱了,肯定花了好长时间才弄对。肯定非常非常重要。让所有这些努力白白浪费是一种罪过。”
当你在上面的第8步出现在这个故事中时,这种压力可能迫使你继续前进,即通过更改现有代码来实现新需求。然而,试图这样做是残酷的。代码不再代表一个单一的通用抽象,而是变成了一个充满条件的过程,交织了许多模糊相关的想法。它难以理解且容易破坏。
如果你发现自己处于这种情况,不要被沉没成本所驱动。当面对错误的抽象时,*最快的前进方式是后退*。做以下事情:
1. 通过将抽象代码内联回每个调用者中,重新引入重复。
2. 在每个调用者中,使用传递的参数来确定该特定调用者执行的内联代码的子集。
3. 删除该特定调用者不需要的位。
这既移除了抽象,也移除了条件,并将每个调用者减少到仅它需要的代码。当你以这种方式回溯决策时,常见的是发现尽管每个调用者表面上调用了共享抽象,但它们运行的代码却是相当独特的。一旦你完全移除了旧的抽象,你可以重新开始,重新隔离重复并重新提取抽象。
我曾见过问题,人们拼命试图用错误的抽象前进,但收效甚微。添加新特性异常困难,每次成功都进一步复杂化代码,使得添加下一个特性更加困难。当他们将观点从“我必须保留我们在代码中的投资”转变为“这段代码在一段时间内是有意义的,但也许我们已经从中学到了所有能学到的”,并允许自己根据当前需求重新思考抽象时,一切变得更容易。一旦他们将代码内联,前进的道路变得显而易见,添加新特性变得更快更容易。
这个故事的道理?不要被沉没成本谬误所困。如果你发现自己传递参数并通过共享代码添加条件路径,那么抽象就是错误的。它可能在开始时是正确的,但那个日子已经过去了。一旦抽象被证明是错误的,最好的策略是重新引入重复,让它告诉你什么是对的。尽管有时积累一些条件以获得对正在发生的事情的洞察是有意义的,但如果你尽早而不是过迟放弃错误的抽象,你会遭受更少的痛苦。
当抽象错误时,最快的前进方式是后退。这不是撤退,而是朝着更好方向前进。去做吧。你会改善自己的生活,以及所有后来者的生活。
### 新闻:《99 Bottles of OOP》JS、PHP 和 Ruby 版已发售!
《99 Bottles of OOP(https://sandimetz.com/99bottles)》第二版已发布!
第二版包含 3 个新章节,比第一版长约 50%。此外,由于《99 Bottles of OOP(https://sandimetz.com/99bottles)》是关于面向对象设计的通用知识,而不是任何特定语言,这次我们创建了技术上相同但示例使用不同编程语言的独立书籍。
《99 Bottles of OOP》目前有 Ruby、JavaScript 和 PHP 版本,以及啤酒和牛奶饮料版本。它提供 epub、kepub、mobi 和 pdf 格式。这产生了六种不同的书籍和 (3x2x4) 24 种可能的下载;全部独特,但仍然相同。一次购买即可获得下载任意或全部的权利。
相似文章
# 代码(更)廉价了
Carson Gross(htmx 的创建者)认为,尽管 AI 让代码生成的成本降低了,但理解代码的成本却在上升。他警告开发者警惕"魔法师学徒"陷阱——让 LLM 生成难以驾驭的复杂代码。他提倡增量式地使用 LLM,并保持对代码库的深度理解。
@addyosmani: https://x.com/addyosmani/status/2056078124346228860
Addy Osmani 提醒:过度依赖 AI 编写代码可能会阻碍学习,并削弱对软件开发的思维模型。
Martin Fowler:技术债、认知债与意图债
Martin Fowler 反思 AI 对代码质量的影响,指出人类的“懒惰”反而促成清晰抽象,而 LLM 则可能用不必要的复杂性把系统拖胖。
智能体循环很棒,直到它们从你最糟糕的代码中学习
本文讨论了AI编码智能体循环如何在不经意间从现有代码库中学习并传播已弃用的代码模式,导致技术债务,尽管表面看起来很成功。
引用布莱恩·坎特里尔
布莱恩·坎特里尔批评LLM缺乏人类懒惰带来的优化约束,认为LLM会不必要地使系统复杂化而非改进,并强调人类时间限制推动了高效抽象的发展。