概览
在软件开发过程中,代码的可读性和可维护性往往是衡量代码质量的重要指标。本文将介绍两个能够显著提升代码质量的设计原则:组合函数模式(Composed Method Pattern)和抽象层次一致性原则(Single Level of Abstraction Principle, SLAP),并通过实例说明如何在实际开发中应用这些原则。
组合函数模式
组合函数模式最早由 Kent Beck 在《Smalltalk Best Practice Patterns》一书中提出。这是一个简单易懂且实用的编程原则,能够对代码的可读性和可维护性产生立竿见影的效果。
组合函数模式要求:
- 所有公有函数(入口函数)应当读起来像一系列执行步骤的概要
- 具体实现细节应当封装在私有函数中
这种模式有助于保持代码精炼并易于复用。阅读这样的代码就像在看一本书,入口函数是目录,指向各自的私有函数,而具体内容则在私有函数中实现。
示例代码
不良实践:
java
public void processOrder(Order order) {
// 验证订单
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must contain at least one item");
}
// 计算总价
double total = 0;
for (OrderItem item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
// 应用折扣
if (total > 1000) {
total *= 0.9; // 10% 折扣
} else if (total > 500) {
total *= 0.95; // 5% 折扣
}
// 更新订单状态
order.setTotal(total);
order.setStatus(OrderStatus.PROCESSED);
orderRepository.save(order);
// 发送确认邮件
String message = "Your order #" + order.getId() + " has been processed. Total: $" + total;
emailService.sendEmail(order.getCustomerEmail(), "Order Confirmation", message);
}
良好实践(应用组合函数模式):
java
public void processOrder(Order order) {
validateOrder(order);
double total = calculateTotal(order);
total = applyDiscount(total);
updateOrderStatus(order, total);
sendConfirmationEmail(order, total);
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must contain at least one item");
}
}
private double calculateTotal(Order order) {
double total = 0;
for (OrderItem item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
return total;
}
private double applyDiscount(double total) {
if (total > 1000) {
return total * 0.9; // 10% 折扣
} else if (total > 500) {
return total * 0.95; // 5% 折扣
}
return total;
}
private void updateOrderStatus(Order order, double total) {
order.setTotal(total);
order.setStatus(OrderStatus.PROCESSED);
orderRepository.save(order);
}
private void sendConfirmationEmail(Order order, double total) {
String message = "Your order #" + order.getId() + " has been processed. Total: $" + total;
emailService.sendEmail(order.getCustomerEmail(), "Order Confirmation", message);
}
抽象层次一致性原则(SLAP)
抽象层次一致性原则与组合函数密切相关。它要求函数体中的内容必须在同一个抽象层次上。如果高层次抽象和底层细节杂糅在一起,代码会显得凌乱,难以理解。
按照组合函数和 SLAP 原则,我们应当:
- 在入口函数中只显示业务处理的主要步骤
- 通过私有方法封装具体实现细节
- 确保一个函数中的抽象在同一个水平上,避免高层抽象和实现细节混杂
代码金字塔结构
满足 SLAP 实际上是构筑了代码结构的金字塔。金字塔结构是一种自上而下的,符合人类思维逻辑的表达方式。在构筑金字塔的过程中,要求金字塔的每一层要属于同一个逻辑范畴、同一个抽象层次。
示例:
java
// 顶层抽象 - 业务流程
public void registerNewUser(UserRegistrationRequest request) {
User user = createUserFromRequest(request);
validateUser(user);
saveUser(user);
sendWelcomeEmail(user);
}
// 中层抽象 - 具体步骤
private User createUserFromRequest(UserRegistrationRequest request) {
User user = new User();
mapBasicInfo(user, request);
mapAddressInfo(user, request);
mapPreferences(user, request);
return user;
}
// 底层抽象 - 实现细节
private void mapBasicInfo(User user, UserRegistrationRequest request) {
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
// 其他基本信息映射
}
如何进行抽象
1. 寻找共性
抽象的过程是合并同类项、归并分类和寻找共性的过程。将有内在逻辑关系的事物放在一起,然后给这个分类进行命名,这个名字就代表了这组分类的抽象。
示例:重构重复代码
java
// 重复代码
public void processCustomerA(Customer customer) {
System.out.println("Processing customer: " + customer.getName());
double discount = customer.getTotal() * 0.1;
customer.applyDiscount(discount);
notifyCustomer(customer);
}
public void processVipCustomer(Customer customer) {
System.out.println("Processing customer: " + customer.getName());
double discount = customer.getTotal() * 0.2;
customer.applyDiscount(discount);
notifyCustomer(customer);
}
// 抽象后的代码
public void processCustomer(Customer customer, double discountRate) {
System.out.println("Processing customer: " + customer.getName());
double discount = customer.getTotal() * discountRate;
customer.applyDiscount(discount);
notifyCustomer(customer);
}
public void processRegularCustomer(Customer customer) {
processCustomer(customer, 0.1);
}
public void processVipCustomer(Customer customer) {
processCustomer(customer, 0.2);
}
2. 提升抽象层次
当我们发现有些东西无法归到一个类别中时,可以通过上升一个抽象层次的方式,让它们在更高的抽象层次上产生逻辑关系。
示例:支付方式抽象
java
// 提升抽象层次前
public class CreditCardPayment {
public void processCreditCardPayment(double amount) {
// 信用卡支付逻辑
}
}
public class PayPalPayment {
public void processPayPalPayment(double amount) {
// PayPal支付逻辑
}
}
// 提升抽象层次后
public interface PaymentMethod {
void processPayment(double amount);
}
public class CreditCardPayment implements PaymentMethod {
@Override
public void processPayment(double amount) {
// 信用卡支付逻辑
}
}
public class PayPalPayment implements PaymentMethod {
@Override
public void processPayment(double amount) {
// PayPal支付逻辑
}
}
3. 构筑金字塔
金字塔原理作为一种强大的结构化思维方法,在编程领域展现出独特价值,它帮助开发者将复杂问题分解为有序的层次结构,使代码更加清晰、逻辑更加严密。在实际编程中,我们遵循"自下而上分析问题,自上而下编写代码"的原则,先收集具体需求并识别共性进行抽象,再从高层接口和主流程开始编写实现。
这种思维方式创造了清晰的抽象层次:
- 顶层代表核心功能和主要意图,
- 中层展示主要逻辑分支和组件关系,
- 底层处理具体实现细节和边界情况
形成了一个既在单个函数内部,也贯穿整个代码库架构设计的金字塔结构。
结论
组合函数模式和抽象层次一致性原则是提升代码可读性和可维护性的有效工具。通过将大函数拆分为多个小函数,并确保每个函数内的抽象层次一致,我们可以构建出更加清晰、易于理解和维护的代码结构。
抽象思维是软件开发人员的核心能力之一,通过不断的训练和实践,我们可以提升自己的抽象能力,从而更好地应对复杂的软件开发挑战。记住,好的抽象能够帮助我们抓住事物本质,简化复杂问题,提高开发效率。
在日常开发中,我们应当有意识地应用这些原则和方法,不断提升自己的代码质量和抽象思维能力。
参考
- 《代码精进之路:从码农到工匠》