Uncle Bob — 整洁代码与架构原则
在编写、审查或重构代码时应用这些原则。它们不是需要盲目遵循的规则——应自行判断,但默认以整洁为取向。
童子军规则
让代码比你接手时更整洁。每次提交都应改善代码库,哪怕只改善一点。
整洁代码基础
命名
- 名称应揭示意图。如果一个名称需要注释才能解释,说明这个名称不合适。
- 使用可读、可搜索的名称。避免使用缩写、单字母(循环计数器除外)和前缀。
- 类/类型:使用名词或名词短语(
AccountManager、OrderRepository)。 - 函数/方法:使用动词或动词短语(
calculateTotal、fetchUser、isValid)。 - 布尔值:读起来应像一个问题(
isActive、hasPermission、canExecute)。 - 避免在脑中进行映射。
r不是 URL,应使用url。
函数
- 函数要小,还要更小。一个函数只做一件事。
- 参数数量最好为 0-2 个。3 个及以上就是代码坏味道——应提取选项对象,或重新考虑设计。
- 不得有副作用。名为
checkPassword的函数不得同时初始化会话。 - 命令-查询分离:函数要么执行操作(命令),要么回答问题(查询),二者不得兼具。
- 不要重复自己(DRY)——但也不要过早抽象。重复出现三次是阈值。
- 不断提取:只要能提取出有意义的子函数,就这样做。
注释
- 优秀的代码本身就是文档。注释用于弥补未能通过代码清楚表达的问题。
- 法律声明类、信息说明类、意图澄清类、后果警示类以及 TODO 注释均可接受。
- 删除被注释掉的代码。版本控制会保留记录。
- 禁止编写只是复述代码行为的注释(例如在
i++前写// increment i)。
格式
- 纵向:采用报纸式结构——顶部是高层摘要,下方是细节。
- 相关函数应彼此靠近。调用方位于被调用方上方。
- 横向:避免横向滚动。保持短行。
- 全团队一致的格式优先于个人偏好。
错误处理
- 相较于错误码,优先使用异常/Result 类型。
- 不要返回 null。不要传递 null。
- 将 try-catch 写在函数顶层,而不是分散在函数各处。
- 错误处理是一件事——负责处理错误的函数应尽量少做其他事情。
- 应根据调用方的需求定义异常类,而不是根据异常抛出方的实现来定义。
对象与数据结构
- 对象隐藏数据并公开行为。数据结构公开数据且没有行为。
- 不要混用二者。既有公共字段又有业务方法的类是两种方式中最糟糕的组合。
- 迪米特法则:一个方法只能调用其自身对象、参数、它创建的对象或直接依赖项的方法。禁止使用
a.getB().getC().doThing()。
SOLID 原则
有关详细说明和示例,请参见 [references/solid.md](references/solid.md)。
- S — 单一职责:一个类只能有一个发生变化的理由。一个行为主体,一项职责。
- O — 开放/封闭:对扩展开放,对修改封闭。使用多态,而非条件语句。
- L — 里氏替换:子类型必须能够替换其基类型,且不破坏行为。
- I — 接口隔离:多个专用接口优于一个通用接口。客户端不应依赖其不使用的方法。
- D — 依赖倒置:依赖抽象,而非具体实现。高层模块不得依赖低层模块。
整洁架构
有关完整的架构指南,请参见 [references/clean-architecture.md](references/clean-architecture.md)。
依赖规则
源代码依赖关系必须向内指向更高层策略。
Frameworks & Drivers → Interface Adapters → Use Cases → Entities
(outer) (inner)
- 实体:企业业务规则、纯领域对象。
- 用例:应用特定的业务规则(协调实体)。
- 接口适配器:在用例格式和外部格式之间转换(控制器、呈现器、网关)。
- 框架与驱动:最外层(DB、Web 框架、UI)。细节。可替换。
关键规则
- 内圈中的任何部分都不知晓外圈中的任何内容。
- 跨越边界传递的数据只能是简单的 DTO 或值对象——绝不能是框架特定类型。
- 数据库只是实现细节。Web 只是实现细节。框架也只是实现细节。
组件原则
- 共同闭包原则(CCP):一起变更的类应归在一起。
- 共同复用原则(CRP):不要强迫用户依赖其不使用的内容。
- 稳定依赖原则:依赖关系应指向稳定的方向。
- 稳定抽象原则:稳定的组件应当是抽象的。
代码异味(危险信号)
- 僵化性:一个小改动会引发其他位置的一连串变更。
- 脆弱性:一处变更会破坏不相关的代码。
- 不可移植性:无法在不连带依赖项的情况下复用模块。
- 无谓的复杂性:臆测性的通用设计、过早抽象。
- 无谓的重复:复制粘贴代码(违反 DRY 原则)。
- 晦涩性:代码难以理解。
- 过长的函数、过大的类、过长的参数列表、布尔标志、针对类型的 switch/case。
测试(TDD)
- TDD 三定律:(1)先编写一个会失败的测试。(2)只编写刚好能让测试失败的测试代码。(3)只编写刚好能让测试通过的生产代码。
- 测试也是一等代码。应保持整洁、可读且运行快速。
- 每个测试只使用一个断言(这是指导原则,而非教条)。每个测试只验证一个概念。
- F.I.R.S.T.:快速、独立、可重复、自我验证、及时。
- 测试边界,而非实现。测试行为,而非方法。
应用这些原则
审查或编写代码时,请按以下顺序检查:
- 可读性:其他人能否在 30 秒内理解这段代码?
- 命名:名称是否能体现意图?
- 函数大小:是否有任何部分可以提取出来?
- 单一职责:每个单元是否只有一个变更理由?
- 依赖关系:依赖关系是否指向稳定/抽象的方向?
- 耦合:是否存在不必要的耦合?
- 错误处理:是否整洁且一致?
- 测试:测试是否存在、是否整洁,以及是否验证了行为?