4代码整洁之道

注释

注释艺术

好注释坏注释文档化API

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

第4章 注释

一、导读

1.1 本章学习目标

第4章讨论了注释的正确使用方式。Uncle Bob 提出了一个激进的观点:注释不是越多越好,最好的注释是不需要注释。本章的核心学习目标包括:

  • 理解注释不能美化糟糕的代码:应该重构代码。
  • 掌握什么是好注释:法律信息、信息性注释、解释意图。
  • 识别什么是坏注释:多余的注释、误导性注释、注释掉的代码。
  • 学会用代码表达意图:好的命名胜过注释。
  • 掌握文档化API的方法:Javadoc等工具。
  • 理解注释的维护成本:过时的注释比没有注释更糟。

1.2 为什么本章如此重要

很多程序员认为"注释越多越好"。Uncle Bob 反驳了这种观点:注释是失败的表现。如果我们能用代码清晰地表达意图,就不需要注释。

注释的问题在于:代码会变化,但注释往往不会同步更新。过时的注释会误导读者,比没有注释更糟糕。

1.3 核心问题引导

你的代码中有多少注释?这些注释真的有用吗?

有没有注释是在"美化"糟糕的代码?

什么是好注释?什么是坏注释?

如何用代码本身来表达意图?

注释的维护成本是什么?


二、核心概念详解

2.1 注释不能美化糟糕的代码

2.1.1 重构而非注释

当你觉得需要注释来解释代码时,首先应该考虑的是:能否重构代码使其自解释?

java
// 差:用注释解释糟糕的代码
// 检查用户是否满足活跃条件
// 活跃条件:最近30天内有登录记录
if (user.getLastLoginDate() != null && 
    user.getLastLoginDate().isAfter(LocalDate.now().minusDays(30))) {
  // ...
}

// 好:重构代码使其自解释
private static final int ACTIVE_THRESHOLD_DAYS = 30;

if (isUserActive(user)) {
  // ...
}

private boolean isUserActive(User user) {
  if (user.getLastLoginDate() == null) {
    return false;
  }
  return user.getLastLoginDate().isAfter(
      LocalDate.now().minusDays(ACTIVE_THRESHOLD_DAYS));
}

2.2 好注释

2.2.1 法律信息

java
// 版权和许可信息是必要的
/*
 * Copyright 2024 Example Corp. All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 */

2.2.2 信息性注释

java
// 解释代码背后的决策或业务规则
// 由于历史原因,活跃用户的阈值是30天
// 而不是行业标准的90天
private static final int ACTIVE_THRESHOLD_DAYS = 30;

// 解释不直观的算法
// 使用位运算检查权限,每个bit代表一种权限
// bit 0: READ, bit 1: WRITE, bit 2: DELETE
public boolean hasPermission(int permissions, Permission permission) {
  return (permissions & (1 << permission.ordinal())) != 0;
}

2.2.3 解释意图

java
// 解释"为什么"而非"做什么"
// 使用 synchronized 而非 ReentrantLock
// 因为这里的竞争不激烈,synchronized 性能更好
public synchronized void incrementCounter() {
  counter++;
}

2.2.4 警示性注释

java
// 警告其他程序员注意的陷阱
// WARNING: 这个方法不是线程安全的
// 如果在多线程环境中使用,需要外部同步
public void processOrder(Order order) {
  // ...
}

2.3 坏注释

2.3.1 多余的注释

java
// 差:注释重复了代码
// 获取用户名称
String name = user.getName();

// 差:注释解释了显而易见的代码
// 循环遍历所有订单
for (Order order : orders) {
  // ...
}

2.3.2 误导性注释

java
// 差:注释与代码不一致
// 计算税费(实际没有计算税费)
double total = subtotal + shipping;
// 注释说"计算税费",但代码没有计算税费

2.3.3 注释掉的代码

java
// 差:注释掉的代码
public void processOrder(Order order) {
  // double discount = calculateDiscount(order);
  // if (discount > 0) {
  //   order.setDiscount(discount);
  // }
  order.setTotal(calculateTotal(order));
}

// 好:删除注释掉的代码,用版本控制追踪历史
public void processOrder(Order order) {
  order.setTotal(calculateTotal(order));
}

2.4 用代码表达意图

2.4.1 好的命名胜过注释

java
// 差:用注释解释
// 检查员工是否有资格获得奖金
// 条件:工作超过5年且绩效评分超过8
if (employee.getYearsOfService() > 5 && employee.getPerformanceRating() > 8) {
  // ...
}

// 好:用方法名表达
if (employee.isEligibleForBonus()) {
  // ...
}

private boolean isEligibleForBonus() {
  return yearsOfService > 5 && performanceRating > 8;
}

2.4.2 好的结构胜过注释

java
// 差:用注释解释复杂逻辑
// 如果是VIP客户,折扣20%
// 如果是普通客户,折扣10%
// 如果是新客户,没有折扣
double discount;
if (customer.isVip()) {
  discount = 0.2;
} else if (customer.isRegular()) {
  discount = 0.1;
} else {
  discount = 0;
}

// 好:用策略模式
public interface DiscountStrategy {
  double calculateDiscount(Customer customer);
}

public class VipDiscountStrategy implements DiscountStrategy {
  @Override
  public double calculateDiscount(Customer customer) {
    return 0.2;
  }
}

// 使用
DiscountStrategy strategy = discountStrategyFactory.getStrategy(customer);
double discount = strategy.calculateDiscount(customer);

2.5 Javadoc

2.5.1 公共API的文档

java
/**
 * 计算订单的总价,包括商品金额、折扣、税费和运费。
 *
 * @param order 要计算的订单,不能为 null
 * @return 订单总价,永远不为 null
 * @throws IllegalArgumentException 如果 order 为 null
 */
public Money calculateTotal(Order order) {
  Objects.requireNonNull(order, "Order must not be null");
  // ...
}

三、实战案例

3.1 案例一:从注释到代码

java
// 改善前:大量注释
public class OrderProcessor {
  // 处理订单
  public void process(Order order) {
    // 验证订单
    if (order == null) throw new IllegalArgumentException();
    if (order.getItems().isEmpty()) throw new IllegalArgumentException();
    
    // 计算总价
    double total = 0;
    for (Item item : order.getItems()) {
      total += item.getPrice() * item.getQuantity(); // 价格 * 数量
    }
    
    // 如果有VIP折扣
    if (order.getCustomer().isVip()) {
      total *= 0.9; // 打9折
    }
    
    // 保存订单
    repository.save(order);
    
    // 发送确认邮件
    emailService.send(order.getCustomer().getEmail(), 
                      "Order Confirmation", 
                      "Total: $" + total);
  }
}

// 改善后:代码自解释
public class OrderProcessor {
  private final OrderRepository repository;
  private final EmailService emailService;
  
  public void process(Order order) {
    validateOrder(order);
    Money total = calculateTotal(order);
    repository.save(order);
    sendConfirmationEmail(order, total);
  }
  
  private void validateOrder(Order order) {
    if (order == null) {
      throw new IllegalArgumentException("Order must not be null");
    }
    if (order.getItems().isEmpty()) {
      throw new IllegalArgumentException("Order must have items");
    }
  }
  
  private Money calculateTotal(Order order) {
    Money subtotal = calculateSubtotal(order);
    if (order.getCustomer().isVip()) {
      return subtotal.multiply(VIP_DISCOUNT_RATE);
    }
    return subtotal;
  }
  
  private Money calculateSubtotal(Order order) {
    return order.getItems().stream()
        .map(item -> item.getPrice().multiply(item.getQuantity()))
        .reduce(Money.ZERO, Money::add);
  }
  
  private void sendConfirmationEmail(Order order, Money total) {
    emailService.send(
        order.getCustomer().getEmail(),
        "Order Confirmation",
        "Your order total: " + total
    );
  }
  
  private static final double VIP_DISCOUNT_RATE = 0.9;
}

四、本章小结

第4章讲解了注释的正确使用方式,核心要点包括:

注释不能美化糟糕的代码:应该重构代码使其自解释。

好注释:法律信息、信息性注释、解释意图、警示。

坏注释:多余的、误导的、注释掉的代码。

用代码表达意图:好的命名和结构胜过注释。

Javadoc:用于公共API的文档。

注释有维护成本:过时的注释比没有注释更糟。

Uncle Bob 的核心观点是:最好的注释是不需要注释。用代码本身来表达意图。


五、思考与练习

思考题

你的代码中有多少注释?这些注释真的有用吗?

有没有注释是在"美化"糟糕的代码?

如何用代码替代注释?

编程练习

找到一段有大量注释的代码,尝试通过重构消除注释。

为一段复杂的业务逻辑编写Javadoc。

找到代码中所有注释掉的代码,删除它们。

4/10注释
0%