Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6. 代码质量: 重构 / code review / 复杂度治理 / DDD 落地

TL;DR

代码质量决定的是"软件 5 年后还能不能改"。这一章把"好代码"从玄学变成可操作的方法:重构是有纪律的语义等价变换,code review 是质量门禁而非走过场,复杂度治理让代码不被技术债压垮,DDD 让复杂业务领域保持清晰。

读完应能:

  1. 用"坏味道清单"发现代码问题,用安全的重构手法(保持行为不变)改进。
  2. 知道 code review 该看什么、怎么给反馈、怎么不让人难受。
  3. 用量化手段(圈复杂度等)治理复杂度,而不是靠感觉。
  4. 理解 DDD 的核心战术(实体/值对象/聚合/领域服务)何时用。

一、什么是"好代码"

1.1 质量的本质:可修改性

代码质量 = 改它需要多少成本 + 多少风险。好代码 = 低成本、低风险地满足新需求。

不是"优雅""花哨",而是:

  • 可读:5 分钟后/5 个月后/别人能看懂。
  • 可测:能写测试(依赖注入、边界清晰)。
  • 可改:加功能不破坏其他部分(低耦合)。
  • 可维护:出问题能快速定位。

1.2 四个质量维度

维度坏的样子好的样子
可读性长函数、魔法数、烂命名短函数、自描述命名、清晰控制流
可测性全局状态、直接 IO、难注入依赖注入、纯函数、接口边界
可维护性深耦合、重复代码、上帝对象低耦合、DRY、小类
可演化性改一处裂一片开闭原则、接口稳定

二、代码坏味道清单(先发现)

2.1 命名与结构

  • 魔法数字/字符串if (x > 86400) → 提取常量 ONE_DAY_SECONDS
  • 烂命名dtempdata2 → 用意图命名。
  • 长函数:> 30-50 行 → 拆成有名字的小函数。
  • 重复代码(DRY 违规):两处相似逻辑 → 提取。
  • 长参数列表:> 4 个参数 → 参数对象。

2.2 结构与耦合

  • 上帝对象:一个类做所有事 → 拆分职责。
  • 深耦合:A 知道 B 的内部细节 → 减少暴露。
  • 散弹式修改:改一个需求要动 10 个文件 → 关注点没聚合。
  • 过度耦合到具体实现:依赖接口而非具体类。

2.3 行为问题

  • 注释撒谎:注释和代码不符 → 改注释或改代码。
  • 死代码:没被调用 → 删。
  • 隐藏依赖:函数依赖全局状态但签名看不出 → 参数显式传。
  • 副作用隐藏在 getter 里getName() 居然改状态 → 命名撒谎。

三、重构:有纪律的改进

3.1 重构的定义

重构 = 不改变外部行为,只改进内部结构,每一步都有测试兜底。

"我顺手改了一下"不算重构——重构必须每一步都能跑测试验证行为没变

3.2 经典手法

手法做什么何时用
Extract Function把一段代码提成函数长函数、注释块
Rename改更清晰的名字命名不清
Introduce Variable表达式提取为局部变量重复/难读表达式
Replace Magic Number魔法数→常量魔法数
Extract Class一个类拆成多个上帝对象
Move Function函数挪到它更相关的类功能放错地方
Parameter Object长参数→对象参数太多
Replace Conditional with Polymorphism分支→多态if/switch 膨胀

3.3 重构的安全流程

1. 先补/跑测试(建立安全网)
2. 一次只做一个微重构(小步)
3. 每步跑测试(确认行为没变)
4. 全部绿 → 继续下一步;红 → 回退

warning

重构必须和 bug 修复/功能开发分开。混在一起,改挂了无法判断是重构还是功能引入的问题。Commit 也应该分开。

3.4 示例:Extract Function

# ❌ 一个函数做三件事(校验、计算、格式化)
def process_order(order):
    # 校验
    if order.status != "pending":
        raise ValueError("invalid status")
    if order.total <= 0:
        raise ValueError("invalid total")
    # 计算
    total = order.total
    discount = 0.1 if total > 1000 else 0
    final = total * (1 - discount)
    # 格式化
    return f"ORDER-{order.id}-{final:.2f}"

# ✅ 拆成三个有名字的函数
def _validate_order(order): ...
def _calculate_total(order): ...
def _format_result(order_id, total): ...

def process_order(order):
    _validate_order(order)
    final = _calculate_total(order)
    return _format_result(order.id, final)

四、Code Review:质量门禁

4.1 看什么

Review 不是"找茬",是理解 + 把关

关注点问的问题
正确性逻辑对吗?边界处理了吗?并发安全吗?
安全有注入/越权/泄露吗?
性能有 N+1、循环 IO、没缓存吗?
可测有测试吗?测试测的是行为吗?
可维护命名/结构/复杂度如何?
与现有代码一致符合项目模式/约定吗?

4.2 反馈的原则

  • 对代码不对人:说"这个函数有问题"不说"你写错了"。
  • 解释为什么:不只说"这里不对",说"这里可能在 X 场景下崩,因为 Y"。
  • 区分阻塞 vs 建议:明确"必须改"和"可以讨论"。
  • 给方向不给答案:问"这里是不是该提取个函数?"优于直接贴代码。
  • 关注重要的事:安全/正确性/性能 > 命名/格式(格式交给 linter)。

4.3 Review 的规模

  • PR 太大(> 400 行)→ Review 质量急剧下降,应拆小。
  • 每 PR 建议 200-400 行改动,一次审 < 60 分钟。
  • Review 速度重要:拖 3 天的 review 比没有 review 更糟(阻塞交付)。目标 < 24h 反馈。

4.4 自动化的边界

交给工具: 格式 / lint / 类型 / 常见 bug(gosec/semgrep/staticcheck)
留给人:   设计 / 语义 / 权衡 / 业务正确性 / 一致性

note

别让 review 花在"该用单引号还是双引号"上——那是 linter 的事。人的价值在判断,机器的事交给机器。


五、复杂度治理

5.1 圈复杂度(Cyclomatic Complexity)

定义:代码中独立路径的数量 = 1 + 分支数(if/for/case/&&/||)

def f(x):
    if x > 0: return "pos"    # +1
    elif x < 0: return "neg"  # +1
    return "zero"             # 基础 1
# 圈复杂度 = 3

治理标准

  • ≤ 10:正常
  • 11-20:需要解释,考虑拆分
  • 20:必须重构(几乎不可测)

工具:radon(Python)、gocyclo(Go)、SonarQube(通用)。

5.2 其他复杂度信号

信号阈值参考含义
函数行数> 30-50太多职责
函数参数> 4缺参数对象
类方法数> 20上帝类
依赖数异常高耦合过重
重复率> 10-15%DRY 违规

5.3 复杂度的平衡

复杂度治理不是"越简单越好"——过度抽象(掉进简化主义)也是复杂度。平衡点:

  • 代码是要读懂的(可读性优先),不是要"最短/最聪明"。
  • 一个 5 行的 if 分支,比一个需要跳 3 个文件的抽象类更简单。
  • "三法则"(Rule of Three):代码用到第三次才提取抽象。前两次复制粘贴是合理的。

warning

复杂度治理最大的敌人是过度设计(YAGNI)。"也许以后要用多态/接口/框架" → 现在不要。等第三处需要时再抽。


六、DDD(领域驱动设计)落地

6.1 DDD 解决什么

复杂业务领域(电商、金融、医疗)里,代码和业务语言脱节 → 沟通成本高、实现和需求漂移。DDD 让代码说业务的语言

6.2 战略设计(宏观)

  • 限界上下文(Bounded Context):把业务切成独立领域(订单、库存、支付各一个上下文),各自有语言和模型。
  • 上下文映射:上下文间的关系(防腐层 ACL、共享内核等)。
  • 通用语言(Ubiquitous Language):领域术语在代码里和业务里用同一个词。

6.3 战术设计(微观)

概念说明例子
实体(Entity)有唯一标识、有生命周期Order(有 order_id)
值对象(Value Object)无标识、靠值相等Money、Address
聚合(Aggregate)实体 + 值对象的边界,外部只能碰聚合根Order 聚合(含 OrderItem)
聚合根(Aggregate Root)聚合的入口,保证内部一致性Order
领域服务(Domain Service)不属于单个实体的业务逻辑OrderService.placeOrder()
仓储(Repository)聚合的持久化接口OrderRepository
领域事件(Domain Event)领域里发生的事OrderPlaced

6.4 何时该用 DDD

不用
业务复杂、规则多、会长期演进CRUD 简单、领域逻辑少
团队够大、多领域协作小团队、一次性脚本
需要和业务方深度协作纯技术项目

tip

DDD 不是银弹。80% 的项目是 CRUD(增删改查),上 DDD 是过度设计。判断标准:业务规则是否复杂到"光靠数据表/CRUD 表达不清"。是才用。


七、落地:工程实践清单

[ ] 代码有测试(单元覆盖关键逻辑)
[ ] 命名表达意图(无魔法数/烂名/死代码)
[ ] 函数短小、单一职责
[ ] 圈复杂度可控(< 15 主要函数)
[ ] 无上帝对象、无深耦合
[ ] 重复代码已提取(Rule of Three)
[ ] code review 是门禁不是走过场
[ ] PR 小步(< 400 行)、Review 快(< 24h)
[ ] 重构与功能开发分开 commit
[ ] 复杂领域有清晰模型(DDD 或分层)

八、结束 + 速查表

tip

一页快速唤回:

  • 质量 = 可修改性:可读 / 可测 / 可改 / 可维护。
  • 坏味道先扫:魔法数、长函数、重复代码、上帝对象、烂命名。
  • 重构 = 行为不变的改进,每步测试兜底;和功能开发分开 commit
  • 常用手法:Extract Function / Rename / Extract Class / Parameter Object。
  • Review:对代码不对人、解释为什么、区分阻塞/建议、格式交给 linter、小 PR 快反馈。
  • 圈复杂度 ≤ 10 正常,> 20 必须重构;工具 radon / gocyclo。
  • Rule of Three:第三次才抽象,避免过度设计(YAGNI)。
  • DDD:复杂领域才用;战略(限界上下文/通用语言)+ 战术(实体/值对象/聚合/仓储)。
  • CRUD 项目别硬上 DDD

下一篇: 7. 可观测性实操: metrics/logs/traces 打点 / OpenTelemetry / SLO.