首页 › programming › 重构神课深读:错抽象比重复贵

错一次抽象,比抄十遍代码更贵

每个工程师都背过 DRY(Don't Repeat Yourself):不要重复自己,见到两段相似的代码,就该抽一个公共函数出来。这条铁律如此正确,以至于很少有人追问:抽象一定比重复便宜吗? 2014 年的 RailsConf 大会上,Ruby 社区的传奇讲师 Sandi Metz 当着几百位工程师的面,把这条圣条拆了。她的结论只有一句话:duplication is far cheaper than the wrong abstraction —— 重复远比错误的抽象便宜。 这场名为 All the Little Things 的演讲,十多年过去,依然是程序员社区被引用最多的重构课之一。它讲的不是新框架、不是新语言,而是一个每个写代码的人都逃不掉的抉择:面对一坨祖传代码和一张新需求单,你到底该抄一遍,还是该抽一层。更难得的是,她不是在讲道理,而是现场把一段臭名昭著的烂代码,一刀一刀重构成能优雅接住新需求的形状——全程不到四十分钟,刀刀见血。如果你只想看一场“怎么做重构”的演示,它是教科书;如果你还想听“为什么绝大多数抽象都抽错了”,它几乎是孤本。

视频背景卡片

项目 内容
视频标题 All the Little Things
演讲者 Sandi Metz(《面向对象设计实践指南:Ruby 语言版》作者)
频道 Confreaks
场合 RailsConf 2014
时长 约40分钟
播放量 数十万级(Confreaks 官方频道,数据截至发文时)
核心主题 现场重构金玫瑰难题,论证「重复远比错误的抽象便宜」

核心内容拆解

一、金玫瑰难题:一份让老程序员 PTSD 的需求单

演讲的道具是社区里流传多年的 Gilded Rose(金玫瑰旅馆)难题:一家旅店要管理库存商品的“质量值”,规则琐碎得像现实本身——普通物品每天贬值 1 点,过期后翻倍贬值;Aged Brie 越放越升值;传奇物品 Sulfuras 永远不变;演出门票 Backstage 则是越临近演出越升值,临期 10 天内每天涨 2,5 天内每天涨 3,演出一结束直接归零。而所有物品的质量都卡在 0 到 50 之间。负责这一切的,是一个写满 if 判断的巨型方法,夹杂着 s、t 这种天书般的变量名。现在新需求来了:新增“施法物品”(Conjured),变质速度是普通物品的两倍——而且老板不许你大改老代码的对外行为。这就是每个工程师都做过的噩梦:需求很小,代码很烂,责任很大。(视频设定部分为 kata 的经典背景,属 Sandi Metz 引用的社区难题)

二、DRY 不是道德律,是账本

整场演讲真正的靶子,是大家对 DRY 的教条化执行。Sandi Metz 的做法是把“重复”重新定价:重复代码的代价是线性的、局部的——抄了两遍,将来规则变了就改两处,改漏了好歹看得见;而错误抽象的代价是复利的、全局的——一个抽错的公共函数,会随着每个新需求长出新的参数开关,每个调用点都要理解全部分支,最后没人敢动它。她在演讲中反复用一个词:cost(代价)。判断抄还是抽,不看代码像不像,看代价记在哪一栏。我的延伸分析是:DRY 这条原则出自《程序员修炼之道》(The Pragmatic Programmer),本意是“每一条知识在系统里应该只有唯一的权威表述”——针对的是知识,不是字面上的相似。后人把它执行成“见重复就抽函数”,把手段供成了目的。Sandi Metz 做的,其实是把这条戒律从道德法庭拉回了成本会计。

三、第一刀:先展平,再谈设计

面对那个巨型 if 方法,她的第一反应出人意料:不设计。不画类图,不谈架构,而是做最机械的事——把层层嵌套的条件展平,把大方法切成一个个小方法,每切一小步就跑一遍测试确认行为没变。她在演讲中展示的重构速度非常快,因为她已经做过很多遍,但每一个动作都小到可以随时回退。这一段传达的手感极其重要:重构不是一次大手术,而是一串小步快走。延伸一层:Kent Beck 有句被反复引用的话——"make the change easy, then make the easy change"(先把改动变容易,再做那个容易的改动)。很多团队的问题恰恰是想跳过第一步直接“做改动”,结果在烂代码上叠加更多烂代码。Sandi Metz 用四十分钟证明了:在测试兜底的前提下,“变容易”这个过程本身可以快到令人发指。

四、命名是最便宜的设计工具

展平之后,她做的第二件事几乎不涉及任何“技术”:改名。把 s、t 这类魔法变量,一个一个换成业务词汇——不是程序员的语言,而是旅店店员的语言。名字改对之后,一段原本需要读三遍才能猜出意图的代码,变得像一份需求文档。这一段常被观众低估,但在我看来它是全场最实用的一节:命名是成本最低、收益最高的重构,因为它是在给未来的维护者(很可能就是你自己)写信。当一个方法必须靠注释才能解释清楚,而一个名字就能让注释下岗时,省钱的地方是下一次阅读——而代码被读的次数,永远比被写的次数多一个数量级。名字起对了,后面的类该怎么拆,往往会自己浮出水面;名字起错了,再漂亮的架构也是海市蜃楼。

五、第二刀:把 if 长梯搬进类里

真正的结构手术发生在这一步:用多态替换条件分支(Replace Conditional with Polymorphism,这是 Martin Fowler《重构》目录里的经典手法)。她把那个巨无霸方法按物品类型拆开——普通物品、Aged Brie、Backstage、Sulfuras 各自成为一个几行的小类,各自只负责自己的更新规则。那个曾经让所有人恐惧的 if 长梯消失了,因为它回答的问题(“这是什么类型的物品”)被语言本身的分派机制接管了。这是 Sandi Metz 的招牌主张:小方法、小类、小对象。她不追求那种“一个类管全局”的优雅,恰恰相反,她相信许多个只会一件事的小零件,比一个什么都会的大零件便宜得多。我的延伸看法:这一步能成立,前提是前三步——没有展平和改名,你连“条件到底在分派什么”都看不清;抽象的机会,是清理出来的,不是想出来的。

六、结账时刻:新需求两行接入

重构完成后的验收环节,是整场演讲的高潮。回到那张需求单:新增 Conjured 物品,变质速度翻倍。在重构前的代码里,这意味着往巨型 if 里再塞一根枝条,并且祈祷不弄坏其他四种物品。而现在,答案是一个几行的小类,复制普通物品的模板、把贬值速度改成 2——主流程一行都不用动。这一刻把全场的论点钉死了:可维护性不是一种美学偏好,而是可以精确度量的东西——接入一个新需求需要改动多少行既有代码。好的设计里,复用是重构的副产品,自动长出来;坏的设计里,复用是预先设计的目标,结果抽错了方向,每一个复用者都变成了人质。Sandi Metz 用一个 kata 说清了软件设计里最难的一课:面向未来设计的最好方式,是先把今天的代码弄干净,而不是猜明天的需求。

七、那句话:duplication is far cheaper than the wrong abstraction

演讲结尾,她给出了那句被整个行业引用至今的判词。错误的抽象长什么样? 它通常始于一次善意:有人看到两段相似代码,好心抽成了公共函数;第三段相似但不完全相同的代码来了,为了塞进同一个函数,加了一个布尔参数;第四段来了,参数变成枚举;最后,这个函数里挤满了 if (flag) 开关,每个调用点都必须理解全部分支才能放心调用——重复代码只需要读一处,错误抽象却要求你读全部。她后来在一篇广为流传的博客 The Wrong Abstraction 里,把这套主张压缩成一句口号:AHA——Avoid Hasty Abstractions,别急着抽象。宁可以先抄三遍,等规则的形状自己稳定下来再动手。这不是反对抽象,而是反对用想象中的未来绑架真实的现在。这句话值得贴在每一间办公室的墙上。

知识结构图

flowchart TD
    A["发现重复代码"] --> B{"规则还会变吗"}
    B -->|还在抖| C["先抄第三遍"]
    B -->|已稳定| D["开始抽象"]
    C --> D
    D --> E["展平嵌套条件"]
    E --> F["切成小方法"]
    F --> G["换成业务语言命名"]
    G --> H{"分支逻辑属于谁"}
    H -->|按业务类型| I["拆小类用多态"]
    H -->|只差几个数字| J["保留参数化函数"]
    I --> K["新需求几行接入"]
    J --> K
    K --> L["可维护性可度量"]

实战启示

把这场演讲带回到你自己的工位上,至少有三件事可以立刻做。

第一,抽象之前过三问。 这条规则稳定了吗?我是不是已经抄到第三遍了?抽象之后,参数会不会长出“开关”?三问里只要有一问答不上来,就先抄,别抽。社区里对应的经验法则是“三次法则”(Rule of Three):第三次出现重复时再抽象,前两次都忍住。这不是懒,这是让真实需求替你完成设计调研。

第二,改一改代码评审的话术。 下次评审看到同事抽了个带布尔参数的公共函数,别只说“有点复杂”,可以直接问:“这个开关的两条分支,各自的调用方在哪里?”如果答案是“以后可能会有”,那就是在给未来放高利贷。反过来,看到有人老老实实抄了两遍几乎一样的代码,也别急着打回——先问规则会不会变,而不是数重复了几行。

第三,用它审 AI 生成的代码。 这是我认为这条十一年前的原则在今天反而更值钱的原因:AI 生成重复代码几乎零成本,生成错误抽象也几乎零成本——而且它特别擅长“看起来很专业”的过早抽象,抽出一堆没人需要的接口和参数。人作为 reviewer 的核心价值,恰恰是 Sandi Metz 这套账本思维:判断哪一笔债值得背,哪一笔是伪装成资产的负债。工具越快,判断越贵。

一句话:重构的本质不是让代码变漂亮,而是让下一次改动变便宜。

延伸学习路径

  1. Sandi Metz《面向对象设计实践指南:Ruby 语言版》(POODR) — 本场演讲的思想底盘,“小类小方法”的完整方法论,程序员口碑极好的一本设计书。
  2. 博客文章 The Wrong Abstraction(sandimetz.com) — 演讲论点的书面版,AHA(Avoid Hasty Abstractions)原则的出处,十分钟读完,适合转发给团队。
  3. Gilded Rose Refactoring Kata(Emily Bache 维护的 GitHub 仓库) — 本场演讲用的练习题,有 Java、Python、C# 等几十种语言版本,强烈建议亲手做一遍,你会在第七个 if 处理解这场演讲。
  4. Sandi Metz《99 Bottles of OOP》 — 她与 Katrina Owen 合著的后续之作,把“多态替换条件”这套刀法拆得更细,整本书就是一个 kata。
  5. Martin Fowler《重构:改善既有代码的设计》 — 演讲中“用多态替换条件”等手法的原始出处,工具书性质,放在手边按需查。

一句话总结

这场演讲用四十分钟证明了一件事:代码的债不是重复,而是错误的抽象——宁可老老实实抄三遍,也不要凭想象抽一层。

参考来源

  1. All the Little Things — Sandi Metz, RailsConf 2014 — 本文分析的核心视频(Confreaks 官方频道)
  2. The Wrong Abstraction — Sandi Metz 博客 — 演讲论点的书面版与 AHA 原则出处
  3. GildedRose-Refactoring-Kata — Emily Bache 的 GitHub 仓库 — 演讲所用练习题的多语言实现
  4. POODR: Practical Object-Oriented Design in Ruby — Sandi Metz 的代表作官网

本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入