2.3 整洁代码
← 2.2 数据结构 | 首页 | 下一节: 2.4 源代码管理 →
核心实践
- 命名是代码可读性的关键:文件、类、变量、函数的命名要表意清晰——读代码时不应需要查阅定义才能理解意图。
- 避免过长的函数和类:合理拆分到方法和类中,遵循单一职责原则。
- 遵循项目结构约定:保持目录清晰,让新成员能直觉地找到代码位置。
- 将复杂的布尔条件提取为命名良好的函数:
if (user.IsEligibleForDiscount())胜过if (user.Age > 65 && user.YearsAsMember > 5 && !user.HasOutstandingBalance)。 - 尽量让代码自解释:代码写出来,他人就能理解"做了什么"。
- 以代码的形式写文档:Markdown 文件放在仓库
docs/目录中,与代码一同版本管理、一同受 CI 检查(拼写、死链)。文档应描述"为什么这样做"和"怎么做到的"(设计目标、使用场景、组件说明、架构概览)。 - 遵循语义化版本(SemVer)规范。
语义化版本(SemVer)
版本号格式 MAJOR.MINOR.PATCH:
- MAJOR(主版本):不兼容的 API 变更时递增。
1.0.0之前(0.y.z阶段)API 不稳定,任意变更。 - MINOR(次版本):向后兼容的功能新增时递增。标记废弃(deprecation)也必须递增次版本。
- PATCH(修订版本):向后兼容的 Bug 修复时递增。
- 预发布标签(如
1.0.0-alpha.1),构建元数据(如1.0.0+20130313144700)。 - 已发布的版本不可修改——任何修改必须发布新版本。
- 主版本递增时,次版本和修订版本归零。
代码注释是谎言
核心论点来自 Pieter Koornhof:如果你需要写注释解释代码做了什么,你已经未能用代码表达清楚意图。
注释为什么成为问题:
- 陈述显而易见的事实,纯冗余:// The Person's First Name 后面接 public string FirstName { get; set; }。
- 阻碍流畅阅读:注释散落在代码中,阅读者在注释行和代码行之间跳跃,意图不清晰。
- 注释很少随代码更新——代码改了注释没改,从有用变成误导。注释经常成为谎言。
- TODO 注释往往永远不会被完成。
- 注释随代码一同被复制,代码再改时注释不改,信息彻底失真。
"好注释"的例外:
- 无法重构的遗留代码文档,记录潜在副作用。
- 框架限制说明:// EntityFramework requires a default constructor。
- 功能分支上的 TODO(合并前必须实现或转为 Backlog 工单)。
- 高度优化的代码,可能难以阅读(但大多数人不常遇到)。
- API 文档注释(如 JavaDoc / JSDoc / XML doc),仅限公开接口。
自律原则:每当想写注释,先问自己"能否重构代码让意图自明?"如果不能,注释是必要的妥协——但不是借口。
组合优于继承
组合优于继承是 GoF《Design Patterns》(1994)推广的 OOP 设计原则:优先用"有什么"(has-a)而非"是什么"(is-a)来实现代码复用。
| 维度 | 继承 | 组合 |
|---|---|---|
| 关系 | is-a("是什么") | has-a("有什么") |
| 耦合度 | 高——子类紧密绑定父类实现 | 低——组件可独立替换 |
| 灵活性 | 静态,编译时决定 | 动态,运行时替换组件 |
| 问题 | 深层继承树、钻石问题(菱形继承)、脆弱基类 | 可能需要大量转发方法 |
示例(游戏对象):
- 继承方案:Player 同时继承 Visible、Solid、Movable → 需要多重继承 → 菱形问题。
- 组合方案:GameObject 包含 VisibilityDelegate、CollisionDelegate、UpdateDelegate 成员 → 构造函数传入不同的组合即可创建 Player(可见+可碰撞+可移动)、Cloud(可见+可移动)、Trap(可碰撞)。
现代语言通过 traits、mixins、类型嵌入、默认接口方法等特性弥补了组合需要写转发方法的问题。实证研究(2013 年对 93 个开源 Java 项目的分析):约 2% 的继承用法可被组合替换,另有 22% 仅用于外部或内部复用——继承没有那么灾难性,但当组合可用时选组合更安全。
测试驱动开发(TDD)
TDD 的核心循环:红(Red)→ 绿(Green)→ 重构(Refactor)
- 红:先写一个失败的测试,描述期望的行为——此时还没有实现代码。
- 绿:写最少量的代码让测试通过——不追求完美,只求让测试变绿。
- 重构:在测试保护下优化代码结构,消除重复,提升可读性——确保测试仍然通过。
- 循环:下一个测试→下一个功能。
TDD 的核心价值不是测试本身,而是驱动设计——迫使你从调用者的角度思考接口,促使低耦合、高内聚的设计自然浮现。测试是副产品,好的设计才是目标。
来源
- Semantic Versioning 2.0.0: https://semver.org/
- Pieter Koornhof — Code Comments are Lies: https://sneakycode.net/code-comments-are-lies (original at CodeProject)
- Wikipedia — Composition over inheritance: https://en.wikipedia.org/wiki/Composition_over_inheritance