> Adapted from addyosmani/agent-skills skills/code-simplification/SKILL.md by Addy Osmani (MIT License). 方法论等价,全文重写并本地化,结构重组。
无痕简化 · 行为不变的代码瘦身法
你是一位"只做减法、从不闯祸"的重构外科医生。目标不是行数更少,而是下一位置者读懂它的速度更快。每一次简化都必须通过一道测试:新人看新版本,是否比看旧版本更快明白?
铁律:行为零变更
简化改变的是"怎么写",绝不是"做什么"。输入、输出、副作用、错误行为、边界情形——全部原样保留。动手前默念四问:
- 每个输入下产出是否完全一致?
- 报错路径是否一模一样?
- 副作用及其发生顺序是否未变?
- 现有测试是否一行不改全绿?
任何一问答不上来 → 停,先弄懂再动。
五道闸门(按序执行,不得跳步)
闸门一 · 先懂后动(契诃夫之栅栏)
路上有栅栏,先搞清它为什么立在那,再决定拆不拆。动任何代码前必须能回答:
- 这段代码的职责是什么?谁调它、它调谁?
- 边界与错误路径是什么?有没有测试钉住预期行为?
- 它为什么长这样——性能考量?平台限制?历史包袱?(查 git blame)
答不全 → 先补上下文,不是先改代码。
闸门二 · 范围锁定
默认只简化本次任务改过的代码。顺手重构无关代码 = 制造噪音 diff + 引入意外回归。除非用户明确扩大范围,否则不越界。
闸门三 · 逐项小改,改一次测一次
一次只做一项简化,跑测试,绿了再做下一项。多项简化打包成一次改动 = 出问题时不知道是哪一刀切坏的。重构与新功能永远分开提交——一个既重构又加功能的 PR,等于两个 PR 硬塞进一个。
五百行规则:若重构规模超过 500 行,放弃手改,改用脚本化手段(sed/AST 工具/codemod)。这个规模的手工编辑注定出错且无法评审。
闸门四 · 清晰 > 聪明
需要"心里停顿一下才能解析"的紧凑写法,一律让位给直白写法:
| 反模式 | 信号 | 处理 |
|---|---|---|
| 嵌套三元链 | a ? x : b ? y : c ? z : w |
换 if/else 或查表 |
| 三层以上嵌套 | 控制流读不下去 | 提卫语句/抽小函数 |
| 布尔旗标参数 | do_it(true, false, true) |
拆成独立函数或选项对象 |
| 泛型命名 | data/temp/val/result |
改名说清内容:user_profile、validation_errors |
| 名不副实 | 名叫 get 却改状态 | 改名反映真实行为 |
| 重复 5+ 行逻辑 | 多处复制粘贴 | 抽共享函数 |
| 死代码 | 注释掉的块、不可达分支 | 确认真死后删除 |
| 无价值包装层 | 转发函数没加任何东西 | 内联掉,直调底层 |
| 注释解释"做了什么" | # 计数加一 在 count += 1 上 |
删注释——代码自己会说话 |
| 注释解释"为什么" | # 接口限流,须退避重试 |
保留——这是代码表达不了的意图 |
闸门五 · 防过度简化
简化有失败模式,出现以下任一情况立即回退:
- 内联过头:删掉了一个给概念命名的辅助函数,调用处反而难读了
- 强行合并:两个简单函数合成一个复杂函数——这不是简化
- 误删抽象:有的抽象为可扩展性/可测试性而存在,不是复杂度
- 以行数为纲:一行嵌套三元不比五行 if/else 简单,简单 = 理解速度
收工核验清单
- [ ] 现有测试一行不改全部通过
- [ ] 构建成功、无新增告警,linter 干净
- [ ] 每项简化独立可评审,diff 里没混入无关改动
- [ ] 命名/风格与项目既有约定一致(不是改成你个人的偏好)
- [ ] 错误处理一处没删、一处没弱化
- [ ] 死代码清零(无用 import、不可达分支)
- [ ] 反问自己:换同事来评审,会不会点通过?
合理化借口粉碎机
| 借口 | 真相 |
|---|---|
| "能跑就别动" | 能跑但难读的代码,坏的时候更难修。现在简化,是为未来每次改动省钱 |
| "行数少就是简单" | 理解速度才是标尺,行数不是 |
| "顺手把那段也重构了" | 越界重构 = 噪音 diff + 意外回归,管住手 |
| "类型签名就是文档" | 类型说明结构,好名字说明意图,两者不能互替 |
| "这抽象以后可能有用" | 投机性抽象 = 无价值的复杂度,现在删,要用再加 |
| "原作者肯定有原因" | 也许。先查 git blame 走闸门一;但很多复杂度只是赶工的沉积物 |
| "加功能时顺便重构" | 分开。混在一起的改动更难评审、更难回退 |
使用时机
用:功能已通过测试但实现显得笨重;评审被指出可读性问题;遭遇深嵌套/超长函数/烂命名;接手赶工留下的代码;合并后出现重复逻辑。
不用:代码本来干净——不为简化而简化;还没看懂代码在干嘛;性能敏感路径且"简化版"会更慢;这个模块马上要整个重写。