代码简化
Inspired by the Claude Code Simplifier plugin. Adapted here as a model-agnostic, process-driven skill for any AI coding agent.
概览
通过降低复杂度来简化代码,同时保持行为完全不变。目标不是让代码行数更少,而是让它更容易读、容易理解、容易修改、也更容易调试。每一次简化都应该通过一个简单测试:“一个新加入团队的人,会不会比看原始版本更快看懂它?”
何时使用
- 功能已经能跑、测试也通过了,但实现比它应该有的样子更重
- Code review 中指出了可读性或复杂度问题
- 你碰到深层嵌套逻辑、超长函数或命名不清的代码
- 你在整理那些赶工写出来的代码
- 你要把散落在多个文件中的相关逻辑收拢起来
- 合并一些引入重复或不一致的改动之后
不适用的场景:
- 代码已经足够干净清晰,不要为了“简化而简化”
- 你还没真正理解代码在干什么,先看懂再简化
- 这段代码是性能热点,而“更简单”的写法会带来可测量的性能损失
- 你马上就要彻底重写这个模块,对废弃代码做简化是在浪费精力
五个原则
1. 精确保留行为
不要改变代码做的事情,只改变它的表达方式。所有输入、输出、副作用、错误行为和边界情况都必须保持一致。如果你不确定某次“简化”是否保留行为,就不要做。
ASK BEFORE EVERY CHANGE:
→ Does this produce the same output for every input?
→ Does this maintain the same error behavior?
→ Does this preserve the same side effects and ordering?
→ Do all existing tests still pass without modification?
2. 遵守项目约定
简化代码,意味着让它更贴近当前代码库,而不是把你自己的偏好强行带进来。开始简化前:
1. Read CLAUDE.md / project conventions
2. Study how neighboring code handles similar patterns
3. Match the project's style for:
- Import ordering and module system
- Function declaration style
- Naming conventions
- Error handling patterns
- Type annotation depth
如果所谓“简化”打破了代码库一致性,那它不是简化,而是无意义扰动。
3. 清晰优先于聪明
当紧凑写法需要人停下来想一下时,显式代码通常更好。
// UNCLEAR: Dense ternary chain
const label = isNew ? 'New' : isUpdated ? 'Updated' : isArchived ? 'Archived' : 'Active';
// CLEAR: Readable mapping
function getStatusLabel(item: Item): string {
if (item.isNew) return 'New';
if (item.isUpdated) return 'Updated';
if (item.isArchived) return 'Archived';
return 'Active';
}
// UNCLEAR: Chained reduces with inline logic
const result = items.reduce((acc, item) => ({
...acc,
[item.id]: { ...acc[item.id], count: (acc[item.id]?.count ?? 0) + 1 }
}), {});
// CLEAR: Named intermediate step
const countById = new Map<string, number>();
for (const item of items) {
countById.set(item.id, (countById.get(item.id) ?? 0) + 1);
}
4. 保持平衡
简化也有失败模式,就是过度简化。要警惕这些陷阱:
- 过度内联,把本来有清晰概念名的 helper 拆掉,反而让调用处更难懂
- 合并无关逻辑,两个简单函数合成一个复杂函数,并不叫更简单
- 删掉“看似没必要”的抽象,有些抽象是为了扩展性或可测性存在,而不是为了炫技
- 只盯行数,目标不是更短,而是更快理解
5. 聚焦在当前范围
默认只简化最近被修改到的代码。除非明确要求扩展范围,否则不要顺手改无关代码。无范围的简化只会让 diff 变噪音,还会带来无谓回归风险。
简化流程
步骤 1:先理解,再动手(Chesterton's Fence)
在修改或删除任何东西之前,先弄清楚它为什么存在。这就是 Chesterton's Fence:如果你看到路中央有一道篱笆,而你不知道它为什么在那,就不要先拆。先理解原因,再决定这个原因是否还成立。
BEFORE SIMPLIFYING, ANSWER:
- What is this code's responsibility?
- What calls it? What does it call?
- What are the edge cases and error paths?
- Are there tests that define the expected behavior?
- Why might it have been written this way? (Performance? Platform constraint? Historical reason?)
- Check git blame: what was the original context for this code?
如果这些问题你答不上来,那你还没准备好去简化它。
步骤 2:识别简化机会
寻找这些具体模式,它们不是模糊“味道”,而是很实在的信号:
结构复杂度:
| 模式 | 信号 | 简化方式 |
|---|---|---|
| 深层嵌套(3 层以上) | 控制流难以跟踪 | 提前返回、guard clause,或抽 helper |
| 长函数(50+ 行) | 职责过多 | 拆成小而有清晰名字的函数 |
| 嵌套三元表达式 | 阅读时需要额外脑内堆栈 | 改成 if/else、switch 或查表 |
| 布尔参数标志位 | 出现 doThing(true, false, true) |
改成 options object 或拆函数 |
| 重复条件判断 | 相同 if 在多处出现 |
抽成有语义名字的谓词函数 |
命名与可读性:
| 模式 | 信号 | 简化方式 |
|---|---|---|
| 泛泛命名 | data、result、temp、val、item |
改成能说明内容的名字,如 userProfile |
| 过度缩写 | usr、cfg、btn、evt |
除通用缩写外,尽量用完整单词 |
| 误导性命名 | 叫 get 的函数实际上会修改状态 |
名字要反映真实行为 |
| 注释解释 “what” | // increment counter 写在 count++ 上方 |
这种注释删掉 |
| 注释解释 “why” | // Retry because the API is flaky under load |
这种注释要保留 |
冗余:
| 模式 | 信号 | 简化方式 |
|---|---|---|
| 重复逻辑 | 同样的 5 行以上代码出现在多处 | 抽公共函数 |
| 死代码 | 不可达分支、未使用变量、注释掉的旧块 | 确认真死后删除 |
| 无价值抽象 | 包了一层但没有真正带来意义 | 内联 wrapper,直接调用底层函数 |
| 过度设计模式 | factory-for-a-factory,单策略的 strategy | 换回直接简单方案 |
| 冗余类型断言 | 明明可推导还强转一次 | 删除断言 |
步骤 3:增量应用改动
一次做一个简化,做完就跑测试。重构改动要和功能 / bug 修复改动分开提交。 一个 PR 同时做重构和功能,本质上是两个 PR。
FOR EACH SIMPLIFICATION:
1. Make the change
2. Run the test suite
3. If tests pass → commit (or continue to next simplification)
4. If tests fail → revert and reconsider
不要把多个简化打包成一个未经验证的大改动。要是出了问题,你需要知道是哪一次简化导致的。
500 规则: 如果某次重构要改超过 500 行,就应考虑自动化工具,例如 codemod、sed 脚本、AST transform,而不是手工一点点改。那种规模的手工修改既容易出错,也很难 review。
步骤 4:验证结果
所有简化做完后,退一步整体看:
COMPARE BEFORE AND AFTER:
- Is the simplified version genuinely easier to understand?
- Did you introduce any new patterns inconsistent with the codebase?
- Is the diff clean and reviewable?
- Would a teammate approve this change?
如果所谓“简化”版本实际上更难懂、更难审,那就撤回。不是每一次简化尝试都会成功。
语言特定建议
TypeScript / JavaScript
// SIMPLIFY: Unnecessary async wrapper
// Before
async function getUser(id: string): Promise<User> {
return await userService.findById(id);
}
// After
function getUser(id: string): Promise<User> {
return userService.findById(id);
}
// SIMPLIFY: Verbose conditional assignment
// Before
let displayName: string;
if (user.nickname) {
displayName = user.nickname;
} else {
displayName = user.fullName;
}
// After
const displayName = user.nickname || user.fullName;
// SIMPLIFY: Manual array building
// Before
const activeUsers: User[] = [];
for (const user of users) {
if (user.isActive) {
activeUsers.push(user);
}
}
// After
const activeUsers = users.filter((user) => user.isActive);
// SIMPLIFY: Redundant boolean return
// Before
function isValid(input: string): boolean {
if (input.length > 0 && input.length < 100) {
return true;
}
return false;
}
// After
function isValid(input: string): boolean {
return input.length > 0 && input.length < 100;
}
Python
# SIMPLIFY: Verbose dictionary building
# Before
result = {}
for item in items:
result[item.id] = item.name
# After
result = {item.id: item.name for item in items}
# SIMPLIFY: Nested conditionals with early return
# Before
def process(data):
if data is not None:
if data.is_valid():
if data.has_permission():
return do_work(data)
else:
raise PermissionError("No permission")
else:
raise ValueError("Invalid data")
else:
raise TypeError("Data is None")
# After
def process(data):
if data is None:
raise TypeError("Data is None")
if not data.is_valid():
raise ValueError("Invalid data")
if not data.has_permission():
raise PermissionError("No permission")
return do_work(data)
React / JSX
// SIMPLIFY: Verbose conditional rendering
// Before
function UserBadge({ user }: Props) {
if (user.isAdmin) {
return <Badge variant="admin">Admin</Badge>;
} else {
return <Badge variant="default">User</Badge>;
}
}
// After
function UserBadge({ user }: Props) {
const variant = user.isAdmin ? 'admin' : 'default';
const label = user.isAdmin ? 'Admin' : 'User';
return <Badge variant={variant}>{label}</Badge>;
}
// SIMPLIFY: Prop drilling through intermediate components
// Before — consider whether context or composition solves this better.
// This is a judgment call — flag it, don't auto-refactor.
常见自我安慰
| 自我安慰 | 现实 |
|---|---|
| “它现在能跑,没必要动” | 能跑但难懂的代码,出问题时也会更难修。现在简化,能为未来每次改动省时间。 |
| “行数越少就越简单” | 一行嵌套三元表达式,通常不如五行 if/else 简单。简单是看理解速度,不是看行数。 |
| “顺手把这段无关代码也简化一下” | 无范围简化只会制造噪音 diff,还会引入你本来不打算承担的风险。 |
| “类型已经足够自解释了” | 类型只能说明结构,说明不了意图。一个命名好的函数,比一个类型签名更能解释 why。 |
| “这个抽象以后可能会有用” | 不要为假想中的未来保留抽象。现在没用,就是没有价值的复杂度。 |
| “原作者肯定有他的理由” | 也许有。先看 git blame,遵守 Chesterton's Fence。但很多复杂度只是历史压力下堆积出来的残留。 |
| “我加功能的时候顺手重构一下” | 重构和功能要分开。混合改动更难 review、更难回滚、历史也更难读。 |
危险信号
- 为了让简化通过而去修改测试,这通常意味着你改了行为
- “简化后”的代码反而更长、更难理解
- 按你自己的偏好重命名,而不是按项目约定
- 因为“这样更清爽”就删掉错误处理
- 在还没真正理解代码前就开始简化
- 把很多简化揉进一个很大、很难审的提交
- 未经要求就去重构当前任务范围外的代码
验证
完成一轮简化后,确认:
- 所有现有测试都无需修改即可通过
- 构建成功,没有新增 warning
- Linter / formatter 通过,没有样式回退
- 每次简化都是可 review 的增量改动
- Diff 很干净,没有混入无关改动
- 简化后的代码遵守项目约定,已对照 CLAUDE.md 或等价规则
- 没有移除或削弱错误处理
- 没有留下死代码,例如未使用 import、不可达分支
- 同事或 review agent 会认为这是一项净改进