6A Philosophy of Software Design

注释策略

代码注释

注释设计接口注释实现注释注释风格

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第6章:注释策略(Commenting Strategy)

一、导读

1.1 本章学习目标

第6章讲解注释的设计——什么应该注释,什么不应该注释。

本章的核心学习目标包括:

  • 理解注释的目的
  • 掌握接口注释和实现注释的区别
  • 学会写有价值的注释
  • 理解"代码即文档"的局限

1.2 为什么本章如此重要

好的注释能大幅降低理解代码的成本。坏的注释比没有注释更糟——它们会误导人。Ousterhout 的核心观点是:注释应该描述代码不能表达的信息。

1.3 核心问题引导

注释的目的是什么?

接口注释应该包含什么?

实现注释应该包含什么?

什么时候不需要注释?


二、核心概念详解

2.1 注释的目的

注释的目的:
降低理解代码的成本

注释应该描述:
代码本身无法表达的信息

注释不应该描述:
代码已经清楚表达的信息

// 坏注释:重复代码
int count = 0;    // 设置 count 为 0

// 好注释:解释"为什么"
int count = 0;
// 从 0 开始,因为空集合的计数应该是 0,
// 而不是 -1 或其他值

2.2 接口注释

接口注释:描述用户需要知道的信息

应该包含:
1. 方法做什么(不是如何做)
2. 参数的含义和约束
3. 返回值的含义
4. 副作用
5. 异常条件

// 好的接口注释
/**
 * Transfers money between two accounts.
 * 
 * @param from   Source account (must belong to current user)
 * @param to     Destination account (must be active)
 * @param amount Amount to transfer (must be positive)
 * @throws InsufficientFundsException if from account has insufficient balance
 * @throws AccountFrozenException if either account is frozen
 */
public void transfer(Account from, Account to, Money amount);

不应该包含:
- 实现细节
- 内部数据结构
- 算法步骤

2.3 实现注释

实现注释:帮助理解代码如何工作

应该包含:
1. 为什么这样做(设计决策)
2. 不明显的约束
3. 警告和陷阱
4. 复杂算法的概述

// 好的实现注释
// 使用 Boyer-Moore 算法而不是 KMP,因为
// 在英文文本上 Boyer-Moore 平均快 3 倍
// (参见 Knuth, TAOCP Vol 3, Section 3.6)

// 这个魔法数字来自 IEEE 754 标准
// 不要修改,除非你完全理解浮点数表示
private static final long MAGIC = 0x5f3759dfL;

不应该包含:
- 显而易见的信息
- 过时的信息
- 被注释掉的代码

2.4 不需要注释的情况

代码能表达的信息不需要注释:

// 不需要注释
int maxRetries = 3;    // 最大重试次数
// 变量名已经说明了

// 不需要注释
if (user.isActive()) {
    // 如果用户活跃
    processUser(user);
}
// 代码已经自解释

// 不需要注释
public class Money {
    private final BigDecimal amount;
    private final Currency currency;
}
// 字段名已经说明了

原则:
好的命名胜过好的注释
如果代码需要注释才能理解,先考虑能否改善命名

三、实战案例

3.1 案例一:改善注释

java
// 改善前:注释不好
/**
 * Process the order.
 * 什么都没说明
 */
public void process(Order order) {
    // Check if order is valid
    if (order.getItems().isEmpty()) {
        throw new Exception("Invalid order");    // 没有说明异常
    }
    
    // Calculate total
    double total = 0;
    for (OrderItem item : order.getItems()) {
        total += item.getPrice() * item.getQuantity();    // 没说明折扣
    }
    
    // Save order    // 显而易见的注释
    repo.save(order);
}

// 改善后:注释有价值
/**
 * Confirms and processes an order.
 * 
 * Validates that the order has at least one item,
 * calculates the total (including applicable discounts),
 * and persists the order.
 * 
 * @throws EmptyOrderException if order has no items
 * @throws PriceChangedException if prices changed since order was created
 */
public void process(Order order) {
    if (order.getItems().isEmpty()) {
        // 空订单没有意义,拒绝处理
        throw new EmptyOrderException(order.getId());
    }
    
    // 价格验证:确保订单项的价格与当前价格一致
    // 如果不一致,抛出 PriceChangedException 让调用方决定
    validatePrices(order);
    
    // 折扣计算在 Order.calculateTotal() 中处理
    // 包括会员折扣、优惠券、满减等
    order.calculateTotal();
    
    orderRepository.save(order);
    eventPublisher.publish(new OrderProcessed(order.getId()));
}

四、本章小结

注释目的:描述代码不能表达的信息。

接口注释:做什么、参数约束、返回值、异常。

实现注释:为什么这样做、不明显的约束、警告。

不需要注释:代码已经清楚表达的信息。


五、思考与练习

审查你代码中的注释,哪些是好的,哪些是坏的?

如何判断一个注释是否有价值?

"代码即文档"的观点有什么局限?


6/7注释策略
0%