10代码大全(第2版)

变量

变量管理

变量作用域生命周期

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

第10章 变量

一、导读

1.1 本章学习目标

第10章深入讲解了变量的正确使用方式。McConnell 指出,变量是程序中最基本的数据载体,其使用方式直接影响代码的可读性和可维护性。本章的核心学习目标包括:

  • 掌握变量命名的艺术:有意义的名称,避免缩写。
  • 理解变量作用域的原则:尽可能缩小作用域。
  • 学会变量初始化的最佳实践:在声明时初始化。
  • 理解变量生命周期的概念:变量的存活时间和影响范围。
  • 掌握避免全局变量的策略:减少耦合。
  • 学会常量的使用:替代魔术数字。

1.2 为什么本章如此重要

变量看似简单,但其使用方式对代码质量有着深远影响。McConnell 指出:好的变量命名让代码自解释,好的作用域控制让代码更安全,好的初始化习惯让 bug 更少。

本章的重要性在于它提供了变量使用的系统化指南。你会理解如何通过良好的变量使用习惯来提升代码质量。

1.3 核心问题引导

变量命名应该遵循什么规则?如何做到"名副其实"?

变量作用域应该多大?为什么要"尽可能缩小"?

变量应该在什么时候初始化?为什么?

全局变量有什么危害?如何避免?

常量和魔术数字有什么区别?如何使用常量?


二、核心概念详解

2.1 变量命名

2.1.1 命名原则

好的变量名应该:

  • 名副其实:反映变量的本质
  • 避免误导:不使用含义模糊的名称
  • 可搜索:避免单字母变量(循环变量除外)
  • 使用英文:避免拼音或中文
java
// 差:含义模糊
int d;           // 表示什么?
String s;        // 什么字符串?
List<int[]> list; // 什么的列表?

// 好:名副其实
int elapsedTimeInDays;
String customerName;
List<int[]> cellGrid;

// 差:误导
int accountCount; // 实际是 Account 对象的计数,不是 int 的计数
// 应该用:numberOfAccounts

// 差:不可搜索
String e; // 在代码中搜索 "e" 会找到大量无关结果
// 应该用:errorMessage

2.1.2 命名约定

java
// 布尔变量:使用 is/has/can/should 前缀
boolean isActive;
boolean hasPermission;
boolean canEdit;
boolean shouldRetry;

// 集合变量:使用复数名词
List<User> users;
Map<String, Order> ordersById;
Set<String> tags;

// 临时变量:使用描述性名称
// 差
String tmp = user.getName();
// 好
String userName = user.getName();

// 循环变量:可以使用单字母(短循环)
for (int i = 0; i < items.size(); i++) {
  // ...
}

// 但长循环应该用描述性名称
for (int rowIndex = 0; rowIndex < matrix.length; rowIndex++) {
  for (int colIndex = 0; colIndex < matrix[rowIndex].length; colIndex++) {
    // ...
  }
}

2.1.3 避免缩写

java
// 差:过度缩写
int qty;        // quantity
String custNm;  // customerName
Date crtDt;     // createDate

// 好:完整名称
int quantity;
String customerName;
Date createDate;

// 例外:广泛认可的缩写
int id;         // identifier
String url;     // uniform resource locator
String http;    // hypertext transfer protocol

2.2 变量作用域

2.2.1 作用域原则

变量作用域应该尽可能缩小。原因:

  • 减少耦合:变量影响范围越小,与其他代码的耦合越小。
  • 提高可读性:变量在附近使用,容易理解。
  • 减少错误:变量不会在不该使用的地方被使用。
java
// 差:作用域过大
public class BadExample {
  private int counter; // 类级别作用域
  
  public void method1() {
    counter = 0;
    for (int i = 0; i < 10; i++) {
      counter++;
    }
    System.out.println(counter);
  }
  
  public void method2() {
    // counter 可以被意外访问和修改
    counter = 100;
  }
}

// 好:作用域最小化
public class GoodExample {
  
  public void method1() {
    int counter = 0; // 方法级别作用域
    for (int i = 0; i < 10; i++) {
      counter++;
    }
    System.out.println(counter);
  }
  
  public void method2() {
    // 没有 counter 变量,不会意外访问
  }
}

// 更好:作用域进一步缩小
public void processItems(List<Item> items) {
  // 差:在循环外声明
  // Item currentItem;
  // for (int i = 0; i < items.size(); i++) {
  //   currentItem = items.get(i);
  //   process(currentItem);
  // }
  
  // 好:在循环内声明
  for (Item item : items) {
    process(item); // item 只在循环内可见
  }
}

2.2.2 作用域与生命周期

java
// 作用域:变量在代码中可见的范围
// 生命周期:变量在内存中存在的时间

// 理想情况:作用域和生命周期一致
public void processOrder(Order order) {
  Money total = calculateTotal(order); // total 的作用域从这里开始
  saveTotal(total);                     // total 在这里使用
  // total 的作用域到这里结束
}

// 不理想:作用域大于生命周期
public void processOrder(Order order) {
  Money total;                          // total 在这里声明
  // ... 20行代码,total 未被使用 ...
  total = calculateTotal(order);        // total 在这里初始化
  saveTotal(total);
  // ... 20行代码,total 未被使用 ...
  // total 的作用域到这里结束
}

2.3 变量初始化

2.3.1 初始化原则

变量应该在声明时初始化,或者在使用前立即初始化。

java
// 差:延迟初始化
public void processOrder(Order order) {
  Money total;
  Money discount;
  Money tax;
  
  // ... 很多代码 ...
  
  total = calculateTotal(order);
  discount = calculateDiscount(order);
  tax = calculateTax(total, discount);
  
  // ... 使用变量 ...
}

// 好:声明时初始化
public void processOrder(Order order) {
  Money total = calculateTotal(order);
  Money discount = calculateDiscount(order);
  Money tax = calculateTax(total, discount);
  
  // ... 使用变量 ...
}

// 更好:在使用前立即初始化
public void processOrder(Order order) {
  Money total = calculateTotal(order);
  Money discount = calculateDiscount(order);
  Money netAmount = total.subtract(discount);
  Money tax = calculateTax(netAmount);
  Money finalAmount = netAmount.add(tax);
  
  saveOrder(order, finalAmount);
}

2.3.2 避免未初始化变量

java
// 差:可能使用未初始化的变量
public String getUserName(User user) {
  String name;
  if (user != null) {
    name = user.getName();
  }
  return name; // 如果 user 为 null,name 未初始化!
}

// 好:确保初始化
public String getUserName(User user) {
  String name = ""; // 默认值
  if (user != null) {
    name = user.getName();
  }
  return name;
}

// 更好:使用 Optional
public Optional<String> getUserName(User user) {
  if (user == null) {
    return Optional.empty();
  }
  return Optional.of(user.getName());
}

2.4 避免全局变量

2.4.1 全局变量的危害

全局变量的问题:

  • 耦合:任何代码都可以访问和修改,导致高度耦合。
  • 不可预测:不知道何时何地会被修改。
  • 难以测试:测试时需要处理全局状态。
  • 并发问题:多线程环境下容易出现竞态条件。
java
// 差:全局变量
public class GlobalState {
  public static User currentUser;
  public static List<Order> pendingOrders;
}

// 任何地方都可以修改
public void processOrder(Order order) {
  GlobalState.currentUser = order.getCustomer(); // 隐式依赖
  GlobalState.pendingOrders.add(order);           // 隐式修改
}

// 好:通过参数传递
public void processOrder(Order order, User currentUser) {
  // 明确的依赖关系
  validateOrder(order, currentUser);
  orderRepository.save(order);
}

2.4.2 替代全局变量的方案

java
// 方案1:依赖注入
public class OrderService {
  private final UserRepository userRepository;
  private final OrderRepository orderRepository;
  
  public OrderService(UserRepository userRepository, 
                      OrderRepository orderRepository) {
    this.userRepository = userRepository;
    this.orderRepository = orderRepository;
  }
  
  public void processOrder(OrderId orderId, UserId userId) {
    User user = userRepository.findById(userId);
    Order order = orderRepository.findById(orderId);
    // ...
  }
}

// 方案2:单例模式(谨慎使用)
public class Configuration {
  private static final Configuration INSTANCE = new Configuration();
  
  private final Map<String, String> settings;
  
  private Configuration() {
    settings = loadSettings();
  }
  
  public static Configuration getInstance() {
    return INSTANCE;
  }
  
  public String getSetting(String key) {
    return settings.get(key);
  }
}

// 方案3:线程局部变量
public class UserContext {
  private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
  
  public static void setCurrentUser(User user) {
    currentUser.set(user);
  }
  
  public static User getCurrentUser() {
    return currentUser.get();
  }
  
  public static void clear() {
    currentUser.remove();
  }
}

2.5 常量的使用

2.5.1 替代魔术数字

魔术数字(Magic Number)是指代码中没有解释的数字常量。

java
// 差:魔术数字
public double calculateTax(double amount) {
  return amount * 0.08; // 0.08 是什么?
}

public void setSessionTimeout(int timeout) {
  if (timeout > 86400) { // 86400 是什么?
    throw new IllegalArgumentException("Timeout too long");
  }
}

// 好:使用常量
public class TaxConstants {
  public static final double STANDARD_TAX_RATE = 0.08;
}

public double calculateTax(double amount) {
  return amount * TaxConstants.STANDARD_TAX_RATE;
}

public class TimeConstants {
  public static final int SECONDS_PER_DAY = 86400;
}

public void setSessionTimeout(int timeout) {
  if (timeout > TimeConstants.SECONDS_PER_DAY) {
    throw new IllegalArgumentException("Timeout too long");
  }
}

2.5.2 常量的命名

java
// 常量命名:全大写,下划线分隔
public class OrderConstants {
  public static final int MAX_ITEMS_PER_ORDER = 100;
  public static final double MIN_ORDER_AMOUNT = 10.0;
  public static final String DEFAULT_CURRENCY = "USD";
  
  // 枚举也是常量的好选择
  public enum OrderStatus {
    CREATED, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
  }
}

// 使用常量
public class Order {
  private final List<OrderItem> items;
  
  public void addItem(OrderItem item) {
    if (items.size() >= OrderConstants.MAX_ITEMS_PER_ORDER) {
      throw new IllegalStateException(
          "Cannot add more than " + OrderConstants.MAX_ITEMS_PER_ORDER + " items");
    }
    items.add(item);
  }
}

三、实战案例

3.1 案例一:改善变量使用

java
// 改善前:糟糕的变量使用
public class BadVariableUsage {
  private static List<Order> orders; // 全局变量
  
  public void process(String s, int i, boolean b) { // 模糊的参数名
    String t = "";
    double d = 0;
    
    if (b) {
      t = s + "_processed";
      d = i * 0.08;
    }
    
    orders.add(new Order(t, d)); // 隐式依赖全局变量
  }
}

// 改善后:良好的变量使用
public class GoodVariableUsage {
  
  public OrderResult processOrder(String orderName, int quantity, 
                                  boolean applyTax) {
    String processedName = orderName + "_processed";
    double totalAmount = quantity * OrderConstants.UNIT_PRICE;
    
    if (applyTax) {
      double tax = totalAmount * TaxConstants.STANDARD_TAX_RATE;
      totalAmount += tax;
    }
    
    Order order = new Order(processedName, totalAmount);
    return OrderResult.success(order);
  }
}

四、本章小结

第10章讲解了变量的正确使用方式,核心要点包括:

变量命名要名副其实:名称反映变量的本质,避免模糊和误导。

作用域尽可能缩小:减少耦合,提高可读性和安全性。

声明时初始化:避免使用未初始化的变量。

避免全局变量:使用依赖注入、参数传递等替代方案。

常量替代魔术数字:提高代码可读性和可维护性。

McConnell 的核心观点是:变量的使用方式反映了程序员的专业素养。好的变量习惯是写出高质量代码的基础。


五、思考与练习

思考题

你的变量命名是否清晰?有没有模糊的命名?

你的变量作用域是否合理?有没有过大的作用域?

你的代码中有魔术数字吗?如何替换为常量?

你的代码中有全局变量吗?如何消除?

编程练习

找到一段代码,改善其中所有的变量命名。

重构一个使用全局变量的类,使用依赖注入替代。

找到代码中所有的魔术数字,替换为命名常量。

10/12变量
0%