1代码大全(第2版)

软件构建

构建的本质

构建开发周期上游

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

第1章 欢迎进入软件构建的世界

一、导读

1.1 本章学习目标

第1章是《代码大全》的开篇之作,Steve McConnell 以宏大的视角介绍了软件构建的全貌。软件构建(Software Construction)是开发过程中唯一"动手写代码"的活动,但它远不止"敲键盘"那么简单。本章的核心学习目标包括:

  • 理解软件构建的完整定义:构建不仅仅是编写代码,它包括详细设计、编码、调试、测试、代码审查和重构等一系列活动。
  • 认识构建在开发过程中的核心位置:构建是连接上游活动(需求分析、架构设计)和下游活动(系统测试、维护)的枢纽。
  • 理解构建质量对软件产品的决定性影响:构建是创造产品本身的活动,其质量直接决定了最终软件的缺陷密度和可维护性。
  • 掌握"上游决策对构建的影响":需求错误和设计缺陷在构建阶段被发现时,修复成本已经成倍增长。
  • 理解构建实践的系统化方法:优秀的构建不是靠个人天赋,而是靠可学习的原则、技术和实践。
  • 建立"软件构建是核心技能"的认知:构建是程序员最需要精进的技艺,它值得系统化的学习和持续的练习。

1.2 为什么本章如此重要

很多程序员认为"写代码"是自然而然的事情,不需要专门学习。这种想法是极其危险的。McConnell 在开篇就指出:软件构建是开发过程中唯一产生最终产品的活动。无论需求分析做得多好,架构设计得多精妙,如果构建阶段出了问题,最终产品必然失败。

本章的重要性在于它重新定义了"构建"的含义。构建不是孤立的编码行为,而是一个系统化的工程活动。它需要方法论的指导,需要工具的支持,需要实践的积累。McConnell 用大量数据和案例证明:优秀的构建实践可以将软件缺陷密度降低一个数量级,可以将开发效率提升数倍。

对于程序员而言,本章传递了一个关键信息:你的核心专业能力不是使用某种编程语言,而是掌握软件构建的系统化方法。语言会过时,框架会更迭,但构建的原则和实践是持久的。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

软件构建到底包含哪些活动?它仅仅是"写代码"吗?

为什么有些项目的需求和设计都很好,但最终产品仍然失败?构建阶段扮演了什么角色?

上游活动(需求、设计)的质量如何影响构建的效率和结果?

为什么说"软件构建的复杂性"被严重低估了?这种复杂性来自哪里?

优秀的程序员和普通的程序员在构建实践上有什么本质区别?

如何系统性地提升自己的构建能力?有哪些可学习的原则和技术?


二、核心概念详解

2.1 软件构建的定义与范围

软件构建在软件开发过程中占据着独特的位置。McConnell 将其定义为:通过编写、验证和组合代码来创造软件产品的活动。这个定义看似简单,实则包含了丰富的内涵。

2.1.1 构建活动的完整组成

构建不仅仅是"敲代码"。一个完整的构建过程包括:

  • 详细设计:在编码之前,对每个模块、每个类、每个函数进行详细的设计。这是构建的蓝图阶段。
  • 编码实现:将设计转化为特定编程语言的源代码。这是构建的核心活动。
  • 代码审查:通过同行评审发现代码中的缺陷和改进点。这是构建质量的重要保障。
  • 单元测试:编写和运行测试来验证代码的正确性。这是构建的自我验证。
  • 调试:定位和修复代码中的缺陷。这是构建的问题解决环节。
  • 集成测试:验证多个模块组合后的正确性。这是构建的系统验证。
  • 重构:在不改变外部行为的前提下改善代码的内部结构。这是构建的持续改进。
java
// 一个完整的构建活动示例
public class OrderProcessor {
  
  // 1. 详细设计:明确类的职责和接口
  
  // 2. 编码实现
  private final OrderRepository repository;
  private final PaymentService paymentService;
  private final NotificationService notificationService;
  
  public OrderProcessor(OrderRepository repository,
                        PaymentService paymentService,
                        NotificationService notificationService) {
    this.repository = repository;
    this.paymentService = paymentService;
    this.notificationService = notificationService;
  }
  
  public OrderResult processOrder(Order order) {
    // 3. 防御式编程:验证输入
    if (order == null) {
      throw new IllegalArgumentException("Order cannot be null");
    }
    
    try {
      // 4. 核心业务逻辑
      repository.save(order);
      PaymentResult payment = paymentService.charge(order);
      
      if (payment.isSuccess()) {
        notificationService.sendConfirmation(order);
        return OrderResult.success(order.getId());
      } else {
        repository.cancel(order);
        return OrderResult.failure(payment.getErrorMessage());
      }
    } catch (Exception e) {
      // 5. 错误处理
      repository.markFailed(order, e);
      return OrderResult.error(e.getMessage());
    }
  }
  
  // 6. 单元测试覆盖(在测试类中)
  // 7. 代码审查通过同行评审
  // 8. 重构持续改善结构
}

2.1.2 构建在开发过程中的位置

软件开发生命周期通常包括以下阶段:

问题定义 → 需求分析 → 架构设计 → 详细设计 → 构建 → 系统测试 → 维护
           ↑ 上游活动 ↑                    ↑ 下游活动 ↑

构建处于整个过程的中心位置。它接收上游活动的输出(需求规格、设计文档),产生下游活动的输入(可执行代码、测试报告)。这种中心位置意味着:

  • 上游的缺陷会在构建阶段被放大:一个需求错误如果在需求阶段修复,成本可能是1;如果在构建阶段修复,成本可能是10-100。
  • 构建的质量直接决定下游的成本:构建阶段引入的缺陷越多,测试和维护阶段的成本越高。
  • 构建是"创造"活动:需求分析和设计是"规划"活动,测试和维护是"验证"活动,只有构建是真正"创造"产品的活动。

2.2 软件构建的复杂性

2.2.1 复杂性的来源

McConnell 强调,软件构建的复杂性被严重低估了。这种复杂性来自多个维度:

问题域的复杂性:软件要解决的问题本身可能是复杂的。业务规则、用户需求、法规约束等因素交织在一起,使得正确的实现远非"显而易见"。

java
// 看似简单的"计算订单总价",实际上涉及复杂的业务规则
public double calculateTotal(Order order) {
  double subtotal = 0;
  for (OrderItem item : order.getItems()) {
    double itemPrice = item.getBasePrice();
    
    // 复杂性1:数量折扣
    if (item.getQuantity() >= 100) {
      itemPrice *= 0.85;
    } else if (item.getQuantity() >= 50) {
      itemPrice *= 0.90;
    }
    
    // 复杂性2:会员折扣(与数量折扣可叠加)
    if (order.getCustomer().isPremiumMember()) {
      itemPrice *= 0.95;
    }
    
    // 复杂性3:季节性促销(不可与其他促销叠加)
    if (order.getSeasonalPromotion() != null 
        && !order.getCustomer().isPremiumMember()) {
      itemPrice = order.getSeasonalPromotion().apply(itemPrice);
    }
    
    // 复杂性4:税费计算(不同地区不同税率)
    double taxRate = taxService.getRate(order.getShippingAddress());
    double itemTotal = itemPrice * item.getQuantity() * (1 + taxRate);
    
    subtotal += itemTotal;
  }
  
  // 复杂性5:运费计算
  double shipping = shippingService.calculate(order);
  
  return subtotal + shipping;
}

管理过程的复杂性:软件开发不是一个人的活动。团队协作、进度管理、沟通成本等因素增加了构建的难度。

人才的复杂性:不同程序员的能力差异巨大。研究表明,最优秀的程序员与最差的程序员之间的生产力差异可以达到10倍以上。

2.2.2 隐喻的力量

McConnell 在本章引入了"软件隐喻"的概念。隐喻不仅是修辞手法,更是我们理解复杂事物的认知工具。不同的隐喻引导我们采用不同的构建方法:

隐喻核心思想对构建的影响
建筑软件像建筑物强调前期设计、蓝图、严格规划
农业软件像农作物强调渐进式生长、迭代、适应环境
编程软件像编程强调逻辑、结构、可预测性
牡蛎软件像珍珠强调精心培育、逐步完善

没有一种隐喻是完美的,但每种隐喻都能帮助我们理解软件开发的某些方面。优秀的程序员能够灵活运用多种隐喻,而不是被单一隐喻所限制。

2.3 前期准备的重要性

2.3.1 准备工作的投入产出比

McConnell 用大量数据证明:前期准备工作的投入与构建阶段的效率成正比。

  • 需求质量的影响:如果需求存在缺陷,构建阶段不得不频繁修改,效率大幅下降。
  • 设计质量的影响:良好的设计为构建提供清晰的蓝图,减少"边做边想"的浪费。
  • 技术选型的影响:选择合适的编程语言、框架和工具,可以显著提升构建效率。
java
// 没有充分准备的构建:边做边想
public class UserService {
  // 开始写代码时才发现需求不清楚
  public void createUser(String name, String email) {
    // 写到一半发现还需要手机号
    // 又发现还需要地址
    // 又发现还需要验证规则
    // ...不断返工
  }
}

// 充分准备后的构建:思路清晰
public class UserService {
  private final UserValidator validator;
  private final UserRepository repository;
  private final EmailService emailService;
  
  public CreateUserResult createUser(CreateUserRequest request) {
    // 验证输入
    ValidationResult validation = validator.validate(request);
    if (!validation.isValid()) {
      return CreateUserResult.validationError(validation.getErrors());
    }
    
    // 检查唯一性
    if (repository.existsByEmail(request.getEmail())) {
      return CreateUserResult.duplicateError("Email already exists");
    }
    
    // 创建用户
    User user = User.create(request.getName(), request.getEmail());
    repository.save(user);
    
    // 发送欢迎邮件
    emailService.sendWelcome(user);
    
    return CreateUserResult.success(user.getId());
  }
}

2.3.2 准备工作的层次

准备工作分为多个层次,每个层次都有其独特的价值:

问题定义:明确要解决什么问题,为什么要解决。

需求分析:详细描述系统应该做什么,包括功能需求和非功能需求。

架构设计:确定系统的整体结构、关键组件和它们之间的关系。

详细设计:为每个模块设计详细的接口、数据结构和算法。

技术选型:选择编程语言、框架、工具和开发环境。

2.4 构建决策的影响

2.4.1 编程语言的选择

编程语言的选择是构建过程中最重要的决策之一。McConnell 指出,语言选择应该基于以下因素:

  • 项目类型:Web 应用、嵌入式系统、数据分析等不同领域有不同的最佳语言选择。
  • 团队技能:团队对语言的熟悉程度直接影响开发效率。
  • 性能要求:某些语言在性能方面具有天然优势。
  • 生态系统:可用的库、框架和工具的数量和质量。
  • 可维护性:语言的清晰度、类型安全性和文档支持。
java
// Java 的优势:强类型、丰富的生态系统、良好的可维护性
public class InventoryService {
  private final Map<ProductId, Integer> stock = new ConcurrentHashMap<>();
  
  public synchronized boolean deductStock(ProductId productId, int quantity) {
    int current = stock.getOrDefault(productId, 0);
    if (current < quantity) {
      return false;
    }
    stock.put(productId, current - quantity);
    return true;
  }
}

2.4.2 编程约定的制定

编程约定(Coding Conventions)是团队协作的基础。 McConnell 强调,约定应该在项目开始时就制定好,并且所有团队成员都应该遵守。

编程约定包括:

  • 命名规范:变量、函数、类、包的命名规则。
  • 代码格式:缩进、空格、括号、换行的规则。
  • 注释规范:何时写注释、注释的格式和内容。
  • 错误处理:异常处理、错误码、日志记录的规范。
java
// 良好的编程约定示例

// 命名规范:类名用 PascalCase,方法名用 camelCase,常量用 UPPER_SNAKE_CASE
public class CustomerOrderService {
  private static final int MAX_RETRY_ATTEMPTS = 3;
  private static final long RETRY_DELAY_MS = 1000;
  
  // 方法命名:动词 + 名词,表达意图
  public OrderResult submitOrder(OrderRequest request) {
    // ...
  }
  
  // 变量命名:有意义、可搜索
  int elapsedTimeInSeconds = calculateElapsedTime(startTime, endTime);
  
  // 错误处理约定:使用异常而非错误码
  if (request == null) {
    throw new IllegalArgumentException("Request cannot be null");
  }
}

2.5 构建实践的系统化方法

2.5.1 从"技艺"到"工程"

McConnell 的核心观点是:软件构建不应该依赖个人的天赋和灵感,而应该建立在系统化的原则和实践之上。这包括:

  • 可复用的设计模式:将常见的设计问题总结为可复用的解决方案。
  • 经过验证的编码技术:如防御式编程、断言、错误处理等。
  • 系统化的测试方法:单元测试、集成测试、回归测试等。
  • 规范的代码审查流程:同行评审、代码走查等。
  • 持续的重构实践:在不改变外部行为的前提下持续改善代码结构。
java
// 系统化的构建实践示例:使用设计模式 + 防御式编程 + 错误处理

// 策略模式:可复用的设计方案
public interface PricingStrategy {
  double calculatePrice(Order order);
}

public class StandardPricingStrategy implements PricingStrategy {
  @Override
  public double calculatePrice(Order order) {
    // 防御式编程
    Objects.requireNonNull(order, "Order must not be null");
    
    if (order.getItems().isEmpty()) {
      throw new IllegalStateException("Order must contain at least one item");
    }
    
    return order.getItems().stream()
        .mapToDouble(item -> item.getPrice() * item.getQuantity())
        .sum();
  }
}

// 使用 Builder 模式构建复杂对象
Order order = Order.builder()
    .customerId(customerId)
    .addItem(product1, 2)
    .addItem(product2, 1)
    .shippingAddress(address)
    .build();

2.5.2 构建质量的度量

McConnell 强调,构建质量应该是可度量的。常用的度量指标包括:

  • 缺陷密度:每千行代码的缺陷数量。
  • 代码覆盖率:测试覆盖的代码比例。
  • 圈复杂度:衡量代码的复杂程度。
  • 代码重复率:重复代码的比例。
  • 构建时间:从代码修改到可用的时间。

2.6 构建与人的因素

2.6.1 程序员的心理模型

McConnell 指出,理解程序员的心理模型对于提升构建质量至关重要。程序员在构建过程中需要同时处理多个层次的信息:

  • 业务逻辑层:代码要实现的业务规则。
  • 技术实现层:具体的算法、数据结构和 API。
  • 质量保障层:错误处理、边界条件、性能考虑。
  • 团队协作层:代码的可读性、可维护性、文档。

优秀的程序员能够在这些层次之间自如切换,而不会顾此失彼。

2.6.2 构建中的常见误区

McConnell 总结了构建中的常见误区:

"代码能跑就行":忽视代码质量,导致后期维护成本剧增。

"先写代码再说":忽视前期准备,导致频繁返工。

"我不用写测试":忽视测试,导致缺陷在后期才被发现。

"重构太浪费时间":忽视代码的持续改善,导致代码腐化。

"注释越多越好":用注释弥补糟糕的代码,而不是重构代码。


三、实战案例

3.1 案例一:一个电商系统的订单处理模块

以下是一个完整的订单处理模块的构建过程,展示了系统化的构建实践。

java
// 步骤1:明确需求和设计
// 需求:处理用户下单、支付、发货通知
// 设计:使用分层架构,分离业务逻辑和基础设施

// 步骤2:定义领域模型
public class Order {
  private final OrderId id;
  private final CustomerId customerId;
  private final List<OrderItem> items;
  private OrderStatus status;
  private final LocalDateTime createdAt;
  
  public Order(OrderId id, CustomerId customerId, List<OrderItem> items) {
    // 防御式编程:在构造时验证
    this.id = Objects.requireNonNull(id);
    this.customerId = Objects.requireNonNull(customerId);
    if (items == null || items.isEmpty()) {
      throw new IllegalArgumentException("Order must have at least one item");
    }
    this.items = List.copyOf(items);
    this.status = OrderStatus.CREATED;
    this.createdAt = LocalDateTime.now();
  }
  
  public double getTotalAmount() {
    return items.stream()
        .mapToDouble(item -> item.getPrice() * item.getQuantity())
        .sum();
  }
  
  public void confirm() {
    if (status != OrderStatus.CREATED) {
      throw new IllegalStateException(
          "Cannot confirm order in status: " + status);
    }
    this.status = OrderStatus.CONFIRMED;
  }
  
  public void cancel() {
    if (status == OrderStatus.SHIPPED) {
      throw new IllegalStateException("Cannot cancel shipped order");
    }
    this.status = OrderStatus.CANCELLED;
  }
  
  // Getters...
}

// 步骤3:实现业务逻辑
public class OrderService {
  private final OrderRepository orderRepository;
  private final PaymentGateway paymentGateway;
  private final NotificationService notificationService;
  
  public OrderService(OrderRepository orderRepository,
                      PaymentGateway paymentGateway,
                      NotificationService notificationService) {
    this.orderRepository = orderRepository;
    this.paymentGateway = paymentGateway;
    this.notificationService = notificationService;
  }
  
  public OrderResult placeOrder(CreateOrderRequest request) {
    // 1. 创建订单
    Order order = new Order(
        OrderId.generate(),
        request.getCustomerId(),
        request.getItems()
    );
    
    // 2. 保存订单
    orderRepository.save(order);
    
    // 3. 处理支付
    try {
      PaymentResult payment = paymentGateway.charge(
          order.getCustomerId(),
          order.getTotalAmount()
      );
      
      if (payment.isSuccess()) {
        order.confirm();
        orderRepository.save(order);
        notificationService.sendOrderConfirmation(order);
        return OrderResult.success(order.getId());
      } else {
        order.cancel();
        orderRepository.save(order);
        return OrderResult.failure(payment.getErrorMessage());
      }
    } catch (PaymentException e) {
      order.cancel();
      orderRepository.save(order);
      return OrderResult.error("Payment failed: " + e.getMessage());
    }
  }
}

// 步骤4:编写单元测试
public class OrderServiceTest {
  private OrderService orderService;
  private OrderRepository orderRepository;
  private PaymentGateway paymentGateway;
  private NotificationService notificationService;
  
  @BeforeEach
  void setUp() {
    orderRepository = mock(OrderRepository.class);
    paymentGateway = mock(PaymentGateway.class);
    notificationService = mock(NotificationService.class);
    orderService = new OrderService(orderRepository, paymentGateway, notificationService);
  }
  
  @Test
  void placeOrder_withValidRequest_shouldSucceed() {
    // Given
    CreateOrderRequest request = new CreateOrderRequest(
        new CustomerId("C001"),
        List.of(new OrderItem("P001", 29.99, 2))
    );
    when(paymentGateway.charge(any(), anyDouble()))
        .thenReturn(PaymentResult.success("TX001"));
    
    // When
    OrderResult result = orderService.placeOrder(request);
    
    // Then
    assertTrue(result.isSuccess());
    verify(orderRepository).save(any(Order.class));
    verify(notificationService).sendOrderConfirmation(any(Order.class));
  }
  
  @Test
  void placeOrder_withPaymentFailure_shouldReturnFailure() {
    // Given
    CreateOrderRequest request = new CreateOrderRequest(
        new CustomerId("C001"),
        List.of(new OrderItem("P001", 29.99, 2))
    );
    when(paymentGateway.charge(any(), anyDouble()))
        .thenReturn(PaymentResult.failure("Insufficient funds"));
    
    // When
    OrderResult result = orderService.placeOrder(request);
    
    // Then
    assertFalse(result.isSuccess());
    assertEquals("Insufficient funds", result.getErrorMessage());
  }
}

3.2 案例二:从"能跑"到"整洁"的重构过程

以下展示了如何通过系统化的构建实践将一个"能跑"的代码改善为"整洁"的代码。

java
// 重构前:能跑但混乱的代码
public class ReportGenerator {
  public String generate(int type, List data) {
    String result = "";
    if (type == 1) {
      // 生成PDF
      result = "PDF Report\n";
      for (int i = 0; i < data.size(); i++) {
        result += data.get(i).toString() + "\n";
      }
      result += "Total: " + data.size() + " items";
    } else if (type == 2) {
      // 生成CSV
      for (int i = 0; i < data.size(); i++) {
        result += data.get(i).toString() + ",";
      }
    } else if (type == 3) {
      // 生成HTML
      result = "<html><body><table>";
      for (int i = 0; i < data.size(); i++) {
        result += "<tr><td>" + data.get(i).toString() + "</td></tr>";
      }
      result += "</table></body></html>";
    }
    return result;
  }
}

// 重构后:整洁的代码
// 使用策略模式消除条件分支
public interface ReportFormatter {
  String format(List<ReportItem> items);
  String getFileExtension();
}

public class PdfReportFormatter implements ReportFormatter {
  @Override
  public String format(List<ReportItem> items) {
    StringBuilder sb = new StringBuilder();
    sb.append("PDF Report\n");
    for (ReportItem item : items) {
      sb.append(item.formatForPdf()).append("\n");
    }
    sb.append("Total: ").append(items.size()).append(" items");
    return sb.toString();
  }
  
  @Override
  public String getFileExtension() {
    return ".pdf";
  }
}

public class CsvReportFormatter implements ReportFormatter {
  @Override
  public String format(List<ReportItem> items) {
    StringBuilder sb = new StringBuilder();
    for (ReportItem item : items) {
      sb.append(item.toCsvLine()).append("\n");
    }
    return sb.toString();
  }
  
  @Override
  public String getFileExtension() {
    return ".csv";
  }
}

public class ReportGenerator {
  private final Map<String, ReportFormatter> formatters;
  
  public ReportGenerator() {
    formatters = new HashMap<>();
    formatters.put("pdf", new PdfReportFormatter());
    formatters.put("csv", new CsvReportFormatter());
    formatters.put("html", new HtmlReportFormatter());
  }
  
  public String generate(String formatType, List<ReportItem> items) {
    ReportFormatter formatter = formatters.get(formatType.toLowerCase());
    if (formatter == null) {
      throw new IllegalArgumentException("Unknown format: " + formatType);
    }
    return formatter.format(items);
  }
}

3.3 案例三:构建过程中的防御式编程

java
// 防御式编程的完整示例
public class BankAccountService {
  
  public TransferResult transferMoney(TransferRequest request) {
    // 1. 断言:验证内部假设(开发阶段检查)
    assert request != null : "TransferRequest must not be null";
    
    // 2. 输入验证:不信任外部输入
    if (request.getFromAccountId() == null) {
      return TransferResult.error("Source account ID is required");
    }
    if (request.getToAccountId() == null) {
      return TransferResult.error("Target account ID is required");
    }
    if (request.getAmount() <= 0) {
      return TransferResult.error("Transfer amount must be positive");
    }
    if (request.getFromAccountId().equals(request.getToAccountId())) {
      return TransferResult.error("Cannot transfer to the same account");
    }
    
    // 3. 防御性复制:保护对象不被意外修改
    List<Transaction> transactions = new ArrayList<>(request.getTransactions());
    
    // 4. 局部化风险:使用 try-catch 包裹可能失败的操作
    try {
      BankAccount fromAccount = accountRepository.findById(request.getFromAccountId());
      if (fromAccount == null) {
        return TransferResult.error("Source account not found");
      }
      
      BankAccount toAccount = accountRepository.findById(request.getToAccountId());
      if (toAccount == null) {
        return TransferResult.error("Target account not found");
      }
      
      // 5. 检查业务规则
      if (fromAccount.getBalance() < request.getAmount()) {
        return TransferResult.error("Insufficient balance");
      }
      
      // 6. 执行操作
      fromAccount.debit(request.getAmount());
      toAccount.credit(request.getAmount());
      
      accountRepository.save(fromAccount);
      accountRepository.save(toAccount);
      
      return TransferResult.success(generateTransactionId());
      
    } catch (DatabaseException e) {
      // 7. 提供有意义的错误信息
      logger.error("Database error during transfer: {}", e.getMessage());
      return TransferResult.error("System error, please try again later");
    }
  }
}

四、本章小结

第1章为全书奠定了基调,核心要点包括:

软件构建是开发过程中的核心活动:它是唯一"创造"产品的活动,连接了上游的需求设计和下游的测试维护。

构建远不止"写代码":它包括详细设计、编码、调试、测试、代码审查和重构等一系列系统化活动。

前期准备决定构建效率:需求质量、设计质量和技术选型直接影响构建阶段的效率和结果。

构建决策影响深远:编程语言选择、编程约定制定和团队规模确定都是关键的构建决策。

构建质量应该是可度量的:缺陷密度、代码覆盖率、圈复杂度等指标帮助量化构建质量。

系统化的方法优于个人天赋:优秀的构建不依赖灵感,而依赖可学习的原则、技术和实践。

McConnell 在开篇就传递了一个核心信息:编写高质量代码是每个程序员的专业责任,而这种能力是可以通过系统化的学习获得的。后续章节将逐一展开构建的各个方面:设计、类、函数、变量、防御式编程、测试、重构等。每一章都建立在前一章的基础上,逐步构建完整的软件构建知识体系。


五、思考与练习

思考题

在你参与过的项目中,构建阶段遇到了哪些问题?如果回到过去,你会如何改善?

"软件构建的复杂性"在你的日常工作中是如何体现的?你能列举哪些复杂性的来源?

你认为哪种软件隐喻最能描述你当前的项目?这种隐喻如何影响了你的开发方式?

McConnell 说"构建是创造产品的活动"。你如何理解这句话?它与需求分析、架构设计有什么本质区别?

编程练习

选择一个你最近编写的项目,列出其中所有的构建活动(编码、测试、调试、审查等)。评估哪些活动做得好,哪些需要改善。

为一个简单的"待办事项"应用编写详细设计文档,包括类设计、接口定义和关键算法的伪代码。然后实现代码,比较设计与实现的差异。

找到一段"能跑但混乱"的代码(可以是自己的或开源项目的),使用本章学到的原则进行重构,并记录重构的每一步。

1/12软件构建
0%