其他
代码能跑但难读难维护时,用行为不变的增量简化提升可读性
在功能正确且测试通过后,对最近修改、复杂难读的代码进行行为不变的简化,使其更易理解、维护和扩展。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 54/100 ×0.15
适用场景
代码能跑但难读难维护时,用行为不变的增量通用
解决思路
- 把简化目标定为提升可读性、可理解性、可修改性和可调试性,而不是减少行数;每次简化后用“新团队成员是否比原版更快理解”来检验。
- 保持行为完全不变:只改变表达方式,不改变代码做什么;所有输入、输出、副作用、错误行为和边界情况必须一致;不确定是否保持行为时不要简化。
- 每次改动前自问:是否对所有输入产生相同输出?是否保持相同错误行为?是否保持相同副作用和顺序?所有现有测试是否无需修改仍通过?
- 遵循项目约定:简化是让代码更符合代码库,而不是强加个人偏好;先读 CLAUDE.md 或项目约定。
- 研究相邻代码如何处理类似模式,并匹配项目的导入顺序和模块系统、函数声明风格、命名约定、错误处理模式、类型注解深度。
- 优先清晰而非聪明:当紧凑写法需要停下来解析时,用显式代码替代;例如把密集三元链改为 if/else 映射,把链式 reduce 改为命名中间步骤。
- 保持平衡,避免过度简化:不要过度内联;不要合并无关逻辑;不要移除为扩展性或可测试性存在的抽象;不要为行数优化。
- 将范围限定在最近修改的代码;除非明确要求扩大范围,否则不要顺手重构无关代码。
- 动手前先理解(Chesterton's Fence):在更改或删除任何东西前,先理解它为什么存在;先理解原因,再判断原因是否仍适用。
- 简化前回答:这段代码的职责是什么?谁调用它、它调用谁?边界情况和错误路径是什么?是否有测试定义预期行为?为什么它可能这样写(性能、平台约束、历史原因)?
- 用 git blame 检查代码的原始上下文;如果上述问题答不出来,先阅读更多上下文,不要开始简化。
- 识别结构复杂度的简化机会:3 层以上深层嵌套时,把条件提取成守卫子句或辅助函数。
- 50 行以上长函数按职责拆分为命名清晰的聚焦函数。
- 嵌套三元替换为 if/else 链、switch 或查找对象。
- 布尔参数标志如 doThing(true, false, true) 替换为选项对象或拆分函数。
- 重复的条件判断提取为命名良好的谓词函数。
- 识别命名和可读性问题:把 data、result、temp、val、item 等泛化名重命名为描述内容的名字,如 userProfile、validationErrors。
- 把 usr、cfg、btn、evt 等缩写改为完整单词,除非缩写是通用写法,如 id、url、api。
- 函数名误导时(例如名为 get 却会改状态)重命名以反映实际行为。
- 解释“做什么”的注释(如 // increment counter 在 count++ 上方)删除;解释“为什么”的注释(如 // Retry because the API is flaky under load)保留。
- 识别冗余:多处出现相同 5 行以上逻辑时提取为共享函数。
- 删除死代码(不可达分支、未使用变量、注释掉的代码块),但先确认它真的是死代码。
- 对不增加价值的包装器直接内联,改为直接调用底层函数。
- 把过度工程模式(工厂的工厂、只有一个策略的策略模式)替换为简单直接方法。
- 删除冗余类型断言:已经能推断出的类型不要再 cast。
- 增量应用更改:一次只做一个简化,每次后运行测试;重构提交与功能或 bug 修复提交分开;混合的 PR 应拆成两个。
- 每个简化的循环:1. 改;2. 运行测试套件;3. 测试通过则 commit 或继续下一个;4. 测试失败则 revert 并重新考虑。
- 避免把多个简化批量放进一个未测试的改动;如果出问题,必须知道是哪个简化导致。
- 遵循 500 行规则:如果重构会触及超过 500 行,使用自动化(codemods、sed scripts、AST transforms)而不是手工修改。
- 完成后整体验证:对比前后,确认简化版真的更易理解;确认没有引入与代码库不一致的新模式;确认差异干净可审查;确认队友会同意这是净改进;否则回滚。
- TypeScript / JavaScript 中:去掉不必要的异步包装器,例如 async function getUser(id: string): Promise<User> { return await userService.findById(id); } 改为 function getUser(id: string): Promise<User> { return userService.findById(id); }。
- TypeScript / JavaScript 中:把冗长条件赋值改为 const displayName = user.nickname || user.fullName。
- TypeScript / JavaScript 中:把手动数组构建(for + if + push)改为 filter,例如 const activeUsers = users.filter((user) => user.isActive);。
- TypeScript / JavaScript 中:把冗余布尔返回改为直接返回条件,例如 function isValid(input: string): boolean { return input.length > 0 && input.length < 100; }。
- Python 中:把冗长字典构建改为字典推导,例如 result = {item.id: item.name for item in items}。
- Python 中:用提前返回简化嵌套条件:先检查 data is None 抛 TypeError;再检查 not data.is_valid() 抛 ValueError;再检查 not data.has_permission() 抛 PermissionError;最后 return do_work(data)。
- React / JSX 中:简化冗长条件渲染:计算 variant 和 label 后再返回 <Badge variant={variant}>{label}</Badge>。
- React / JSX 中:遇到 prop drilling 时评估 context 或 composition 是否更好;这是判断,标记出来,不要自动重构。
- 避免合理化“它已经在工作,不用碰”:难读的工作代码以后出问题时也难修,现在简化能节省未来每次修改的时间。
- 避免合理化“行数越少越简单”:1 行嵌套三元不比 5 行 if/else 简单;简单性关乎理解速度,不是行数。
- 避免合理化“我顺手也简化无关代码”:无范围简化会产生噪音差异,并在你无意改动的代码中引入回归风险;保持聚焦。
- 避免合理化“类型让它自文档化”:类型记录结构,不记录意图;命名良好的函数比类型签名更能解释为什么。
- 避免合理化“这个抽象以后可能有用”:不要保留投机抽象;现在没用的抽象就是无价值复杂度,删除它,需要时再加回。
- 避免合理化“原作者一定有原因”:也许有,用 git blame 检查并应用 Chesterton's Fence;但累积复杂度常常没有原因,只是压力下迭代的残留。
- 避免合理化“我在加功能时顺便重构”:把重构与功能工作分开;混合改动更难审查、回滚和在历史中理解。
- 检查红旗:简化需要修改测试才能通过,说明你可能改变了行为。
- 检查红旗:所谓简化后的代码比原版更长、更难跟随。
- 检查红旗:按个人偏好而不是项目约定重命名。
- 检查红旗:以代码更干净为由移除错误处理。
- 检查红旗:简化你还没有完全理解的代码。
- 检查红旗:把很多简化批量成一个很大、难审查的 commit。
- 检查红旗:未经要求重构当前任务范围之外的代码。
- 最终验证:所有现有测试无需修改通过。
- 最终验证:构建成功且没有新警告。
- 最终验证:linter/formatter 通过,没有风格回归。
- 最终验证:每个简化都是可审查的增量更改。
- 最终验证:差异干净,没有混入无关更改。
- 最终验证:简化代码遵循项目约定(对照 CLAUDE.md 或等价物)。
- 最终验证:没有移除或削弱错误处理。
- 最终验证:没有留下死代码(未用 imports、不可达分支)。
- 最终验证:队友或审查代理会批准这个改动是净改进。
适用边界 · 注意事项
- 代码已经干净可读时,不要为了简化而简化。
- 还不理解代码做什么时,先理解再简化,不要直接动手。
- 代码是性能关键路径且更简单的版本会可测量地变慢,或你即将完全重写该模块时,不要做这种简化。