0%

WET稀释了性能瓶颈

WET Dilutes Performance Bottlenecks

DRY原则(Don’t Repeat Yourself)的重要性是它整理了这样一个概念:系统中的每个知识片段都应该有单一的表现形式。换而言之,每个知识点应该被包含在单独的实现中。DRY的对立面是WET(Write Every Time)。当知识点被整理在几个不同的实现中,我们的代码就是WET的。当你考虑DRY和WET的性能曲线的多种影响时,它们之间的对比就会非常明显。

让我们开始吧,假设我们系统的某个功能X是CPU的瓶颈。X功能占用30%的CPU。现在假设X功能有分散在10个不同的实现中。平均来看,每个实现都会占用3%的CPU。如果我们想快速查找问题,这种级别的CPU利用率根本不值一提。但是,假设我们以某种方式辨认出了X是性能瓶颈。现在我们剩下的问题就知识找出并修复每个单独的实现。采用WET,我们要找出并修复10处不同的实现。采用DRY,我们能清楚地看到30%的CPU利用率并在第十个代码处修复。此外,我们不必花大量时间来追踪每个实现。

有一个情况是我们经常违反DRY罪魁祸首:我们对集合的运用。实现查询功能的通用技术是通过遍历集合,然后将查询应用到每个元素:

1
2
3
4
5
6
7
8
9
10
11
12
public class UsageExample {
private ArrayList<Customer> allCustomers = new ArrayList<Customer>();
// ...
public ArrayList<Customer> findCustomersThatSpendAtLeast(Money amount) {
ArrayList<Customer> customersOfInterest = new ArrayList<Customer>();
for (Customer customer: allCustomers) {
if (customer.spendsAtLeast(amount))
customersOfInterest.add(customer);
}
return customersOfInterest;
}
}

将原始集合暴露给客户端,我们就违反了封装。这不仅会限制我们的重构能力,还会强制我们的代码用户每次都用潜在的相同方式实现它们,从而违反DRY。这种状况可以通过已出原始集合暴露的API来简单避免。例如,我们可以引入一个新的领域专用集合类型CustomerList。这个新类更符合我们语义上的领域。所有的查询行为也更自然。

有用这个新的集合类型也可以让我们更轻松地察看是这些查询否有性能瓶颈。将查询写入类中,我们就消除了代表选择的需求,例如ArrayList给我们的客户端。这可以使我们更自由地改变实现,而不必担心会违反合同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CustomerList {
private ArrayList<Customer> customers = new ArrayList<Customer>();
private SortedList<Customer> customersSortedBySpendingLevel = new SortedList<Customer)();
// ...
public CustomerList findCustomersThatSpendAtLeast(Money amount) {
return new CustomerList( customersSortedBySpendingLevel.elementsLargerThan(amount));
}
}

public class UsageExample {
public static void main(String[] args) {
CustomerList customers = new CustomerList();
// ...
CustomerList customersOfInterest = // ...
}
}

这上述例子中,遵循DRY,通过采用客户消费水平作为键的SortedList,允许我们来引入索引的替代方案。这个案例中更重要的细节是,遵循DRY能够帮助我们找到并修复性能瓶颈,而WET代码下是难以被发现的。

小小鼓励,大大心意!