通过规范的命名、短小的函数和清晰的错误处理,编写可读、可维护的代码。当用户提到“清理这段代码”、“这个函数太长了”、“代码坏味道”、“命名规范”、“童子军规则”、“单一职责”或“单元测试质量”时使用。在审查拉取请求的可读性、梳理混乱的函数、讨论注释风格或改进错误处理模式时也会触发。涵盖SRP、注释纪律、格式化和单元测试。有关重构技术,请参见refactoring-patterns。有关架构和依赖规则,请参见clean-architecture。
整洁代码框架
一种严谨的代码编写方法,旨在传达意图、减少意外并欢迎变更。在编写新代码、审查拉取请求、重构遗留系统或就代码质量提供建议时应用这些原则。
核心原则
代码被阅读的次数远多于编写的次数——为读者优化。 阅读与编写的比例远超10:1,因此每个命名选择、函数边界和格式化决策要么增加清晰度,要么增加成本。整洁代码读起来像精心编写的散文:名称揭示意图,函数一步步讲述故事,并遵循童子军规则——始终让代码比你发现时更整洁。
评分
目标:10/10。 根据以下原则对任何代码进行0-10评分。报告当前分数以及达到10/10所需的具体改进。
- 9-10: 名称揭示意图,函数短小且专注,错误处理一致,测试整洁且全面
- 7-8: 基本整洁,但存在轻微命名歧义或少数长函数;测试可能缺少边界情况
- 5-6: 混合——良好模式与不清晰名称、重复逻辑或不一致错误处理并存
- 3-4: 长而多用途的函数,误导性名称,测试差或缺失
- 1-2: 几乎不可读——魔法数字、晦涩缩写、无结构、无测试
整洁代码框架
编写清晰沟通并适应变更的代码的六项纪律:
1. 有意义的命名
核心概念: 名称应揭示意图,避免误导信息,并使代码读起来像散文。如果名称需要注释来解释,那么名称就是错误的。
为什么有效: 名称是最普遍的文档形式——精心选择的名称消除了阅读实现的需要;糟糕的名称迫使每个读者逆向工程意图。
关键见解:
- 名称应回答它为什么存在、做什么以及如何使用
- 没有编码、前缀或类型信息(没有匈牙利命名法);单字母仅用于极小作用域的循环计数器
- 类是名词;方法是动词
- 每个概念一个词:不要混用
fetch、retrieve和get - 作用域越大,名称应越长、越具描述性
- 自由重命名——IDE使其变得简单
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 变量 | 意图揭示 | elapsedTimeInDays而不是d |
| 布尔值 | 谓词短语 | isActive、hasPermission、canEdit |
| 函数 | 动词+名词 | calculateMonthlyRevenue()而不是calc() |
| 类 | 命名职责的名词 | InvoiceGenerator而不是InvoiceManager |
重命名或审查名称时,请参见references/naming-conventions.md——按语言惯例、可发音/可搜索表以及前后示例。
2. 函数
核心概念: 函数应短小,只做一件事,并做好——理想情况下4-6行,零到两个参数,一个抽象层次。
为什么有效: 短小的单用途函数易于命名、理解、测试和重用;长函数隐藏错误、抵制测试并积累职责。
关键见解:
- 逐步下降规则:代码自上而下阅读,每个函数调用下一个抽象层次
- 参数数量:零个最佳,一个可以,两个可接受,三个以上需要理由
- 标志参数是一种坏味道——函数做了两件事;拆分它
- 命令查询分离:要么改变状态,要么返回值,不要两者都做
- 提取直到不能:如果可以提取出一个命名的函数,就去做
- 没有隐藏的副作用——名称必须说出全部真相
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 长函数 | 提取命名步骤 | validateInput(); transformData(); saveRecord(); |
| 标志参数 | 拆分为两个函数 | renderForPrint() / renderForScreen()而不是render(isPrint) |
| 错误情况 | 顶部守卫子句 | 早期返回错误,单一快乐路径 |
| 多参数 | 引入参数对象 | new DateRange(start, end)而不是report(start, end, format, locale) |
| 副作用 | 使效果明确 | checkPassword()启动会话→重命名或分离 |
拆分长函数时,请参见references/functions-and-methods.md——参数数量规则、命令查询分离和逐步下降工作示例。
3. 注释与格式化
核心概念: 注释是用代码表达自己的失败。当注释必要时,它们解释为什么,从不解释什么。格式化创建使代码可扫描的视觉结构。
为什么有效: 注释会腐烂——代码变化但注释通常不变,产生比没有更糟糕的文档。整洁的格式化让开发者像读报纸一样扫描代码:先看标题,细节按需呈现。
关键见解:
- 最好的注释是一个命名良好的提取函数
- 可接受:法律头、TODO、公共API文档、真正的“为什么”解释
- 注释掉的代码和日志注释:删除——版本控制会记住
- 概念之间的垂直开放性;概念内部的垂直密度;变量靠近使用处声明
- 报纸隐喻:高级函数在文件顶部,细节在下面
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 解释“什么” | 用更好的名称替换 | // check if eligible → isEligible() |
| 解释“为什么” | 保留为注释 | // RFC 7231 requires this header for proxies |
| 注释掉的代码 | 删除它 | 信任版本控制 |
| 团队格式化 | 决定一次,自动化 | Prettier、Black、gofmt |
决定注释是否值得保留时,请参见references/comments-formatting.md——好与坏注释目录和垂直格式化规则。
4. 错误处理
核心概念: 错误处理是与业务逻辑分离的关注点。使用异常而不是返回码,为每个异常提供上下文,并且绝不返回或传递null。
为什么有效: 返回码用检查污染快乐路径;异常将两者干净地分离。返回null强制每个调用者进行null检查,一个遗漏的检查会在远离源头的地方崩溃。
关键见解:
- 先写try-catch——它定义了一个事务边界
- 优先使用非受检异常——受检异常违反开闭原则
- 根据调用者的需求定义异常类,而不是根据失败类型
- 不要返回null(使用空集合、Optional或抛出);也不要传递null
- 特例/空对象模式:返回具有默认行为的对象而不是null
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| null返回 | 空集合或Optional | return Collections.emptyList()而不是return null |
| 错误码 | 替换为异常 | throw new InsufficientFundsException(balance, amount) |
| 第三方API | 用适配器包装 | PortfolioService包装供应商API,翻译其异常 |
| 特例 | 空对象模式 | GuestUser具有默认行为而不是null检查 |
| 错误中的上下文 | 包含操作+状态 | "Failed to save invoice #1234 for customer 'Acme'" |
设计异常或null策略时,请参见references/error-handling.md——特例模式和第三方API包装示例。
5. 单元测试
核心概念: 测试是一等代码,与生产代码一样保持整洁。脏测试比没有测试更糟糕——它们成为拖慢每次变更的负债。
为什么有效: 整洁测试是可执行的文档和重构的安全网;脏测试使每次修改都成为与难以理解的测试代码的斗争。
关键见解:
- TDD三定律:先写一个失败的测试;只写足以失败的测试;只写足以通过的代码
- 每个测试一个概念——一个逻辑断言,不一定是单个assert
- F.I.R.S.T.:快速、独立、可重复、自我验证、及时
- 构建领域特定测试语言:像DSL一样可读的辅助函数
- 像重构生产代码一样重构测试代码
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 测试结构 | 安排-执行-断言 | 设置、执行、验证——清晰分离 |
| 测试命名 | 场景+预期行为 | shouldRejectExpiredToken而不是test1 |
| 共享设置 | 构建器/工厂辅助函数 | aUser().withRole(ADMIN).build() |
| 不稳定测试 | 移除外部依赖 | 模拟时间、网络、文件系统 |
编写或清理测试时,请参见references/testing-principles.md——TDD定律、F.I.R.S.T.扩展和整洁测试模式。
6. 代码坏味道与启发式方法
核心概念: 坏味道是更深层设计问题的表面指标——学会快速识别它们,并应用有针对性的重构,而不是模糊的“清理”。
为什么有效: 坏味道是无需深入分析就能指向可能问题的启发式方法,将代码审查直觉转化为具体、可重复的操作。
关键见解:
- 函数坏味道:参数过多、输出参数、标志参数、死函数
- 通用坏味道:重复、错误抽象层次、依恋情结、魔法数字
- 测试坏味道:覆盖率不足、跳过测试、未测试的边界条件和失败路径
- 以小而经过测试的步骤重构——绝不同时重构和添加功能
- 童子军规则:让代码比你发现时更整洁
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 重复 | 提取共享逻辑 | 通用验证→validateEmail()辅助函数 |
| 依恋情结 | 将方法移动到数据所属的类 | order.calculateTotal()而不是calculator.total(order) |
| 死代码 | 删除它 | 移除未使用的函数、不可达分支 |
| 魔法数字 | 命名常量 | MAX_LOGIN_ATTEMPTS = 5而不是裸5 |
| 霰弹式修改 | 合并相关变更 | 将分散的逻辑分组到单个模块 |
当坏味道难以命名时,请参见references/code-smells.md——按类别分类的完整目录,每个都配有针对性重构。
常见错误
| 错误 | 为什么失败 | 修复 |
|---|---|---|
| 缩写名称 | 节省几秒编写时间,花费数小时阅读 | 完整描述性名称;IDE自动补全 |
| “巧妙”的一行代码 | 写起来令人印象深刻,调试起来不可能 | 展开为可读的命名步骤 |
| 用注释代替重构 | 注释会腐烂;代码才是真相 | 提取一个命名良好的函数 |
| 捕获通用异常 | 吞掉错误以及预期异常 | 捕获特定异常;让其余传播 |
| 没有错误路径测试 | 快乐路径工作,边界情况崩溃 | 测试每个分支、边界和失败模式 |
| 过早优化 | 为微小收益模糊意图 | 先整洁;优化测量到的瓶颈 |
| 上帝类 | 一个2000行的类做所有事 | 应用SRP——按职责拆分 |
| 没有测试的重构 | 没有回归安全网 | 先编写特征测试 |
| 不一致的约定 | 每个文件感觉像不同的代码库 | 就风格达成一致;用linter和格式化工具强制执行 |
| 到处返回null | null检查像病毒一样传播 | Optional、空集合或空对象 |
快速诊断
| 问题 | 如果否 | 行动 |
|---|---|---|
| 你能在不阅读函数体的情况下理解每个函数吗? | 名称未揭示意图 | 重命名以描述其功能 |
| 所有函数都在20行以下吗? | 函数做了太多事 | 将子操作提取为命名辅助函数 |
| 零个注释掉的代码块? | 死代码造成混乱 | 删除——版本控制有历史 |
| 错误处理是否与业务逻辑分离? | try-catch混乱主流程 | 提取处理程序;异常优于返回码 |
| 每个类是否只有一个职责? | 类积累不相关的职责 | 拆分为专注、命名良好的类 |
| 每个公共方法是否有测试? | 没有变更安全网 | 在进一步更改前添加测试 |
| 测试名称是否描述行为? | 失败难以解释 | 重命名为shouldDoXWhenY |
| 重复是否低于3次? | 复制粘贴传播错误 | 提取共享逻辑(§6) |
| 魔法数字是否是命名常量? | 意图隐藏在原始值后面 | 命名常量(§6) |
| 所有测试是否在10秒内运行? | 慢测试不会被运行 | 模拟外部依赖;拆分集成测试 |
进一步阅读
基于Robert C. Martin关于软件工艺的开创性指南:
- 《整洁代码:敏捷软件工艺手册》 by Robert C. Martin
- 《程序员的职业素养》 by Robert C. Martin
- 《整洁架构:软件结构与设计的工匠指南》 by Robert C. Martin
- 《重构:改善既有代码的设计》 by Martin Fowler
关于作者
Robert C. Martin("Uncle Bob") 自1970年开始编程,是敏捷宣言的合著者,并创立了Uncle Bob Consulting和Clean Coders。他的著作——《整洁代码》、《程序员的职业素养》、《整洁架构》和《整洁敏捷》——塑造了一代开发者对代码质量的思考方式,他的核心立场是:要想快,唯一的方法是做好。






