基本类型偏执(Primitive Obsession)
- 使用基本类型而不是小对象来实现简单任务(例如货币、范围、电话号码字符串等)。
- 使用常量编码信息(例如一个用于引用管理员权限的常量
USER_ADMIN_ROLE = 1
)。- 使用字符串常量作为字段名在数组中使用。
大多数编程语言都支持基本数据类型和结构类型(类、结构体等)。结构类型允许程序员将基本数据类型组织起来,以代表某一事物的模型。
基本数据类型可以看成是机构类型的积木块。当基本数据类型数量成规模后,将它们有组织地结合起来,可以更方便的管理这些数据。
以类取代类型码(Replace Type Code with Class)
。引入参数对象(Introduce Parameter Object)
或 保持对象完整(Preserve Whole Object)
。以类取代类型码(Replace Type Code with Class)
将它替换掉。如果你有与类型码相关的条件表达式,可运用 以子类取代类型码(Replace Type Code with Subclass)
或 以状态/策略模式取代类型码(Replace Type Code with State/Strategy)
加以处理。以对象取代数组(Replace Array with Object)
。问题
类之中有一个数值类型码,但它并不影响类的行为。
解决
以一个新的类替换该数值类型码。
问题
某些参数总是很自然地同时出现。
解决
以一个对象来取代这些参数。
问题
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解决
改为传递整个对象。
boolean withinPlan = plan.withinRange(daysTempRange);
问题
你有一个不可变的类型码,它会影响类的行为。
解决
以子类取代这个类型码。
问题
你有一个类型码,它会影响类的行为,但你无法通过继承消除它。
解决
以状态对象取代类型码。
问题
你有一个数组,其中的元素各自代表不同的东西。
String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";
解决
以对象替换数组。对于数组中的每个元素,以一个字段来表示。
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
数据泥团(Data Clumps)
有时,代码的不同部分包含相同的变量组(例如用于连接到数据库的参数)。这些绑在一起出现的数据应该拥有自己的对象。
通常,数据泥团的出现时因为糟糕的编程结构或“复制-粘贴式编程”。
有一个判断是否是数据泥团的好办法:删掉众多数据中的一项。这么做,其他数据有没有因而失去意义?如果它们不再有意义,这就是个明确的信号:你应该为它们产生一个新的对象。
提炼类(Extract Class)
将它们提炼到一个独立对象中。引入参数对象(Introduce Parameter Object)
将它们组织成一个类。保持对象完整(Preserve Whole Object)
将整个数据对象传入到函数中。问题
某个类做了不止一件事。
解决
建立一个新类,将相关的字段和函数从旧类搬移到新类。
问题
某些参数总是很自然地同时出现。
解决
以一个对象来取代这些参数。
问题
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解决
改为传递整个对象。
boolean withinPlan = plan.withinRange(daysTempRange);
过大的类(Large Class)
一个类含有过多字段、函数、代码行。
类通常一开始很小,但是随着程序的增长而逐渐膨胀。
类似于过长函数,程序员通常觉得在一个现存类中添加新特性比创建一个新的类要容易。
设计模式中有一条重要原则:职责单一原则。一个类应该只赋予它一个职责。如果它所承担的职责太多,就该考虑为它减减负。
提炼类(Extract Class)
。提炼子类(Extract Subclass)
。提炼接口(Extract Interface)
。复制被监视数据(Duplicate Observed Data)
可以告诉你怎么做。问题
某个类做了不止一件事。
解决
建立一个新类,将相关的字段和函数从旧类搬移到新类。
问题
一个类中有些特性仅用于特定场景。
解决
创建一个子类,并将用于特殊场景的特性置入其中。
问题
多个客户端使用一个类部分相同的函数。另一个场景是两个类中的部分函数相同。
解决
移动相同的部分函数到接口中。
问题
如果存储在类中的数据是负责 GUI 的。
解决
一个比较好的方法是将负责 GUI 的数据放入一个独立的类,以确保 GUI 数据与域类之间的连接和同步。
过长函数(Long Method)
一个函数含有太多行代码。一般来说,任何函数超过 10 行时,你就可以考虑是不是过长了。 函数中的代码行数原则上不要超过 100 行。
通常情况下,创建一个新函数的难度要大于添加功能到一个已存在的函数。大部分人都觉得:“我就添加这么两行代码,为此新建一个函数实在是小题大做了。”于是,张三加两行,李四加两行,王五加两行。。。函数日益庞大,最终烂的像一锅浆糊,再也没人能完全看懂了。于是大家就更不敢轻易动这个函数了,只能恶性循环的往其中添加代码。所以,如果你看到一个超过 200 行的函数,通常都是多个程序员东拼西凑出来的。
一个很好的技巧是:寻找注释。添加注释,一般有这么几个原因:代码逻辑较为晦涩或复杂;这段代码功能相对独立;特殊处理。 如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。如果函数有一个描述恰当的名字,就不需要去看内部代码究竟是如何实现的。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数中。
提炼函数(Extract Method)
。以查询取代临时变量(Replace Temp with Query)
,引入参数对象(Introduce Parameter Object)
或 保持对象完整(Preserve Whole Object)
。以函数对象取代函数(Replace Method with Method Object)
尝试移动整个函数到一个独立的对象中。分解条件表达式(Decompose Conditional)
。至于循环,应该使用 提炼函数(Extract Method)
将循环和其内的代码提炼到独立函数中。是否像许多人说的那样,增加函数的数量会影响性能?在几乎绝大多数情况下,这种影响是可以忽略不计,所以不用担心。 此外,现在有了清晰和易读的代码,在需要的时候,你将更容易找到真正有效的函数来重组代码和提高性能。
问题
你有一段代码可以组织在一起。
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
解决
移动这段代码到一个新的函数中,使用函数的调用来替代老代码。
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
问题
将表达式的结果放在局部变量中,然后在代码中使用。
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
解决
将整个表达式移动到一个独立的函数中并返回结果。使用查询函数来替代使用变量。如果需要,可以在其他函数中合并新函数。
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
问题
某些参数总是很自然地同时出现。
解决
以一个对象来取代这些参数。
问题
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解决
改为传递整个对象。
boolean withinPlan = plan.withinRange(daysTempRange);
问题
你有一个过长函数,它的局部变量交织在一起,以致于你无法应用提炼函数(Extract Method) 。
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
解决
将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将函数分割成这个类中的多个函数。
class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// copy relevant information from order object.
//...
}
public double compute() {
// long computation.
//...
}
}
问题
你有复杂的条件表达式。
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
解决
根据条件分支将整个条件表达式分解成几个函数。
if (notSummer(date)) {
charge = winterCharge(quantity);
}
else {
charge = summerCharge(quantity);
}
过长参数列(Long Parameter List)
一个函数有超过 3、4 个入参。
过长参数列可能是将多个算法并到一个函数中时发生的。函数中的入参可以用来控制最终选用哪个算法去执行。
过长参数列也可能是解耦类之间依赖关系时的副产品。例如,用于创建函数中所需的特定对象的代码已从函数移动到调用函数的代码处,但创建的对象是作为参数传递到函数中。因此,原始类不再知道对象之间的关系,并且依赖性也已经减少。但是如果创建的这些对象,每一个都将需要它自己的参数,这意味着过长参数列。
太长的参数列难以理解,太多参数会造成前后不一致、不易使用,而且一旦需要更多数据,就不得不修改它。
以函数取代参数(Replace Parameter with Methods)
。在这里,,“已有的对象”可能是函数所属类里的一个字段,也可能是另一个参数。保持对象完整(Preserve Whole Object)
将来自同一对象的一堆数据收集起来,并以该对象替换它们。引入参数对象(Introduce Parameter Object)
为它们制造出一个“参数对象”。问题
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。
int basePrice = quantity * itemPrice; double seasonDiscount = this.getSeasonalDiscount(); double fees = this.getFees(); double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
解决
让参数接受者去除该项参数,并直接调用前一个函数。
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
问题
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解决
改为传递整个对象。
boolean withinPlan = plan.withinRange(daysTempRange);
问题
某些参数总是很自然地同时出现。
解决
以一个对象来取代这些参数。