怎么让代码再也不臃肿,写的像诗同样优雅

基本类型偏执

基本类型偏执(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)

收益

  • 多亏了使用对象替代基本数据类型,使得代码变得更加灵活。
  • 代码变得更加易读和更加有组织。特殊数据能够集中进行操做,而不像以前那样分散。不用再猜想这些陌生的常量的意义以及它们为何在数组中。
  • 更容易发现重复代码。

重构方法说明

以类取代类型码(Replace Type Code with Class)

问题bash

类之中有一个数值类型码,但它并不影响类的行为。session

解决编程语言

以一个新的类替换该数值类型码。函数

引入参数对象(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);复制代码

以子类取代类型码(Replace Type Code with Subclass)

问题

你有一个不可变的类型码,它会影响类的行为。

解决

以子类取代这个类型码。

以状态/策略模式取代类型码(Replace Type Code with State/Strategy)

问题

你有一个类型码,它会影响类的行为,但你没法经过继承消除它。

解决

以状态对象取代类型码。

以对象取代数组(Replace Array with Object)

问题

你有一个数组,其中的元素各自表明不一样的东西。

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) 将整个数据对象传入到函数中。
  • 检视一下使用这些字段的代码,也许,将它们移入一个数据类是个不错的主意。

收益

  • 提升代码易读性和组织性。对于特殊数据的操做,能够集中进行处理,而不像之前那样分散。
  • 减小代码量。

什么时候忽略

  • 有时为了对象中的部分数据而将整个对象做为参数传递给函数,可能会产生让两个类之间不收欢迎的依赖关系,这中状况下能够不传递整个对象。

重构方法说明

提炼类(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)
  • 若是你的过大类是个 GUI 类,可能须要把数据和行为移到一个独立的领域对象去。你可能须要两边各保留一些重复数据,并保持两边同步。 复制被监视数据(Duplicate Observed Data) 能够告诉你怎么作。

收益

  • 重构过大的类可使程序员没必要记住一个类中大量的属性。
  • 在大多数状况下,分割过大的类能够避免代码和功能的重复。

重构方法说明

提炼类(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) 将循环和其内的代码提炼到独立函数中。

收益

  • 在全部类型的面向对象代码中,函数比较短小精悍的类每每生命周期较长。一个函数越长,就越不容易理解和维护。
  • 此外,过长函数中每每含有难以发现的重复代码。

性能

是否像许多人说的那样,增长函数的数量会影响性能?在几乎绝大多数状况下,这种影响是能够忽略不计,因此不用担忧。 此外,如今有了清晰和易读的代码,在须要的时候,你将更容易找到真正有效的函数来重组代码和提升性能。

重构方法说明

提炼函数(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);
}复制代码

以查询取代临时变量(Replace Temp with Query)

问题

将表达式的结果放在局部变量中,而后在代码中使用。

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;
  }
}复制代码

引入参数对象(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);复制代码

以函数对象取代函数(Replace Method with Method Object)

问题

你有一个过长函数,它的局部变量交织在一块儿,以至于你没法应用提炼函数(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.
    //...
  }
}复制代码

分解条件表达式(Decompose Conditional)

问题

你有复杂的条件表达式。

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)

一个函数有超过 三、4 个入参。

问题缘由

过长参数列多是将多个算法并到一个函数中时发生的。函数中的入参能够用来控制最终选用哪一个算法去执行。

过长参数列也多是解耦类之间依赖关系时的副产品。例如,用于建立函数中所需的特定对象的代码已从函数移动到调用函数的代码处,但建立的对象是做为参数传递到函数中。所以,原始类再也不知道对象之间的关系,而且依赖性也已经减小。可是若是建立的这些对象,每个都将须要它本身的参数,这意味着过长参数列。

太长的参数列难以理解,太多参数会形成先后不一致、不易使用,并且一旦须要更多数据,就不得不修改它。

解决方案

  • 若是向已有的对象发出一条请求就能够取代一个参数,那么你应该使用 以函数取代参数(Replace Parameter with Methods) 。在这里,,“已有的对象”多是函数所属类里的一个字段,也多是另外一个参数。
  • 你还能够运用 保持对象完整(Preserve Whole Object) 未来自同一对象的一堆数据收集起来,并以该对象替换它们。
  • 若是某些数据缺少合理的对象归属,可以使用 引入参数对象(Introduce Parameter Object) 为它们制造出一个“参数对象”。

收益

  • 更易读,更简短的代码。
  • 重构可能会暴露出以前未注意到的重复代码。

什么时候忽略

  • 这里有一个重要的例外:有时候你明显不想形成"被调用对象"与"较大对象"间的某种依赖关系。这时候将数据从对象中拆解出来单独做为参数,也很合情理。可是请注意其所引起的代价。若是参数列太长或变化太频繁,就须要从新考虑本身的依赖结构了。

重构方法说明

以函数取代参数(Replace Parameter with Methods)

问题

对象调用某个函数,并将所得结果做为参数,传递给另外一个函数。而接受该参数的函数自己也可以调用前一个函数。

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);复制代码

保持对象完整(Preserve Whole Object)

问题

你从某个对象中取出若干值,将它们做为某一次函数调用时的参数。

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);复制代码

解决

改成传递整个对象。

boolean withinPlan = plan.withinRange(daysTempRange);复制代码

引入参数对象(Introduce Parameter Object)

问题

某些参数老是很天然地同时出现。

解决

以一个对象来取代这些参数。

相关文章
相关标签/搜索