第1章 整洁代码
一、导读
1.1 本章学习目标
第1章是《代码整洁之道》的开篇,Robert C. Martin(Uncle Bob)提出了整洁代码的定义和重要性。本章的核心学习目标包括:
- 理解整洁代码的定义:易于阅读、易于理解、易于修改的代码。
- 掌握童子军规则:每次提交都让代码变得更干净。
- 认识代码质量的重要性:影响开发效率和软件寿命。
- 理解破窗理论:糟糕的代码会引发更多糟糕的代码。
- 建立专业程序员的素养:对代码质量负责。
- 理解整洁代码的经济价值:降低维护成本。
1.2 为什么本章如此重要
Uncle Bob 在开篇就提出了一个尖锐的问题:你有多少时间花在阅读代码上? 答案是:阅读代码的时间远远超过写代码的时间。比例大约是 10:1。这意味着,让代码易于阅读,就是在节省10倍的时间。
本章的重要性在于它重新定义了"好代码"的标准。好代码不仅仅是能工作的代码,更是易于理解、易于修改、易于维护的代码。Uncle Bob 强调:编写整洁代码不是奢侈品,而是生存必需品。
1.3 核心问题引导
什么是整洁代码?你能给出自己的定义吗?
为什么代码质量如此重要?它对开发效率有什么影响?
破窗理论如何应用于代码?你见过"破窗"效应吗?
童子军规则意味着什么?如何在日常工作中实践?
专业程序员对代码质量负有什么责任?
二、核心概念详解
2.1 整洁代码的定义
2.1.1 多位大师的定义
Uncle Bob 邀请了多位编程大师给出整洁代码的定义:
Grady Booch:整洁的代码简单而直接,像一篇精心编写的散文。
Dave Thomas:整洁的代码是可以被其他人修改的代码。它可以通过测试,有清晰的命名,只做一件事。
Michael Feathers:整洁的代码看起来像是编写它的人关心它。它有人照看,有人注意保持它的清洁。
Ron Jeffries:整洁的代码没有重复,每个函数、类或模块都只做一件事。
综合这些定义,整洁代码的核心特征是:
- 可读性:代码像散文一样易读。
- 可修改性:修改一处不会破坏其他部分。
- 可测试性:容易编写测试来验证正确性。
- 简洁性:没有重复,没有不必要的复杂性。
// 差的代码:难以阅读和理解
public class O {
public List<int[]> g() {
List<int[]> l = new ArrayList<>();
for (int[] a : m) {
if (a[0] == 1) l.add(a);
}
return l;
}
}
// 整洁的代码:清晰表达意图
public class MineSweeperBoard {
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<>();
for (Cell cell : cells) {
if (cell.isFlagged()) {
flaggedCells.add(cell);
}
}
return flaggedCells;
}
}2.2 童子军规则
2.2.1 规则的含义
童子军规则:让营地比你来时更干净。
应用到编程中:每次提交代码时,都让代码比你发现它时更干净。
这不意味着每次都要大规模重构。即使是很小的改善也是有价值的:
- 重命名一个模糊的变量
- 拆分一个过长的函数
- 删除一段重复的代码
- 添加一个缺失的测试
- 修复一个格式问题
// 发现这样的代码
public void processOrder(Order o) {
// ... 处理订单
double t = 0;
for (var i : o.getItems()) {
t += i.getP() * i.getQ();
}
// ... 保存订单
}
// 应用童子军规则:小步改善
// 改善1:重命名变量
public void processOrder(Order order) {
// ... 处理订单
double total = 0;
for (OrderItem item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
// ... 保存订单
}
// 改善2:提取函数
public void processOrder(Order order) {
Money total = calculateTotal(order);
// ... 保存订单
}
private Money calculateTotal(Order order) {
return order.getItems().stream()
.map(item -> item.getPrice().multiply(item.getQuantity()))
.reduce(Money.ZERO, Money::add);
}2.3 破窗理论
2.3.1 理论的含义
破窗理论源自犯罪学:如果一栋建筑的一扇窗户被打破而不修复,其他窗户也会很快被打破。
在代码中:如果代码中有一处是糟糕的,其他部分也会很快变得糟糕。
- 一个命名不好的变量会鼓励更多命名不好的变量。
- 一段重复的代码会鼓励更多重复的代码。
- 一个缺失的测试会鼓励更多缺失的测试。
- 一个格式混乱的文件会鼓励更多格式混乱的文件。
// 第一扇"破窗":一个命名不好的变量
public void processOrder(Order order) {
String x = order.getCustomer().getName(); // x 是什么?
// ...
}
// 破窗效应:更多糟糕的命名
public void processOrder(Order order) {
String x = order.getCustomer().getName();
String y = order.getCustomer().getEmail();
int z = order.getItems().size();
double w = order.getTotalAmount();
// 代码变得越来越难以理解
}
// 修复破窗:立即改善
public void processOrder(Order order) {
String customerName = order.getCustomer().getName();
String customerEmail = order.getCustomer().getEmail();
int itemCount = order.getItems().size();
Money totalAmount = order.getTotalAmount();
// 代码清晰可读
}2.4 代码质量的经济价值
2.4.1 糟糕代码的代价
Uncle Bob 用数据证明:糟糕的代码会显著降低开发效率。
- 初期:糟糕的代码可能开发更快(因为不花时间设计)。
- 中期:开发速度急剧下降(因为代码难以理解和修改)。
- 后期:开发几乎停滞(因为每次修改都引入新 bug)。
开发效率
↑
│ 好的代码:持续高效
│ ──────────────────────
│ ╲
│ ╲ 糟糕的代码:效率急剧下降
│ ╲
│ ╲
│ ╲
└──────────────────────────────→ 时间2.4.2 整洁代码的投资回报
整洁代码是一种投资,它的回报是:
- 更快的开发速度:代码易读易改,开发效率持续提升。
- 更少的 bug:清晰的代码更容易发现错误。
- 更低的维护成本:代码结构清晰,修改不会引入新问题。
- 更好的团队协作:代码风格一致,新人容易上手。
2.5 专业程序员的素养
2.5.1 对代码质量负责
Uncle Bob 强调:代码质量是程序员的专业责任。你不能说"经理让我写快一点"来为糟糕的代码辩护。
专业程序员应该:
- 不交付知道有缺陷的代码:自己先测试,确保代码正确。
- 不交付没有测试的代码:测试是代码的一部分。
- 不交付没有文档的代码:文档是代码的一部分。
- 持续改善代码质量:每次提交都让代码更好。
// 专业程序员的代码
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) {
validateRequest(request);
Order order = createOrder(request);
orderRepository.save(order);
PaymentResult payment = paymentGateway.charge(
order.getCustomer(), order.getTotalAmount());
if (payment.isSuccess()) {
order.confirm();
orderRepository.save(order);
notificationService.sendConfirmation(order);
return OrderResult.success(order.getId());
} else {
order.cancel();
orderRepository.save(order);
return OrderResult.failure(payment.getErrorMessage());
}
}
private void validateRequest(CreateOrderRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request must not be null");
}
if (request.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
}
private Order createOrder(CreateOrderRequest request) {
return new Order(
OrderId.generate(),
request.getCustomerId(),
request.getItems()
);
}
}三、实战案例
3.1 案例一:从混乱到整洁的改善
// 改善前:混乱的代码
public class S {
public void m(Order o) {
if (o != null) {
double t = 0;
for (int i = 0; i < o.getI().size(); i++) {
t += o.getI().get(i).getP() * o.getI().get(i).getQ();
}
if (o.getC() != null && o.getC().isVip()) {
t *= 0.9;
}
o.setT(t);
// save
new DB().save(o);
// send email
new Email().send(o.getC().getE(), "Order", "Total: " + t);
}
}
}
// 改善后:整洁的代码
public class OrderProcessor {
private final OrderRepository orderRepository;
private final EmailService emailService;
public OrderProcessor(OrderRepository orderRepository,
EmailService emailService) {
this.orderRepository = orderRepository;
this.emailService = emailService;
}
public void processOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order must not be null");
}
Money total = calculateTotal(order);
order.setTotal(total);
orderRepository.save(order);
sendConfirmationEmail(order);
}
private Money calculateTotal(Order order) {
Money subtotal = calculateSubtotal(order);
if (order.getCustomer() != null && order.getCustomer().isVip()) {
return subtotal.multiply(0.9);
}
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) {
Customer customer = order.getCustomer();
if (customer != null && customer.getEmail() != null) {
emailService.send(
customer.getEmail(),
"Order Confirmation",
"Your order total: " + order.getTotal()
);
}
}
}四、本章小结
第1章为全书奠定了基调,核心要点包括:
整洁代码的定义:易读、易理解、易修改的代码。
童子军规则:每次提交都让代码更干净。
破窗理论:糟糕的代码会引发更多糟糕的代码。
代码质量的经济价值:整洁代码是投资,不是成本。
专业素养:程序员对代码质量负有专业责任。
Uncle Bob 的核心信息是:编写整洁代码是程序员的专业责任,也是对自己和团队的尊重。
五、思考与练习
思考题
你能给出自己对"整洁代码"的定义吗?
你见过"破窗效应"在代码中的表现吗?后果是什么?
童子军规则在你的团队中可行吗?如何实践?
你如何向非技术人员解释"代码质量"的重要性?
编程练习
找到一段"混乱"的代码,应用本章原则进行改善。
在一个现有项目中实践"童子军规则"一周,记录改善效果。
与同事讨论"什么是整洁代码",比较各自的定义。