第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。
找到代码中所有注释掉的代码,删除它们。