代码坏味道之非必要的

:notebook: 本文已归档到:「blogjava

翻译自:https://sourcemaking.com/refactoring/smells/dispensablesgit

非必要的(Dispensables)这组坏味道意味着:这样的代码无关紧要,它的存在反而影响总体代码的整洁和可读性。程序员

冗余类

冗余类(Lazy Class)github

理解和维护老是费时费力的。若是一个类不值得你花费精力,它就应该被删除。算法



问题缘由

也许一个类的初始设计是一个功能彻底的类,然而随着代码的变迁,变得没什么用了。 又或者类起初的设计是为了支持将来的功能扩展,然而却一直未派上用场。设计模式

解决方法

  • 没什么用的类能够运用 将类内联化(Inline Class) 来干掉。



  • 若是子类用处不大,试试 折叠继承体系(Collapse Hierarchy)

收益

  • 减小代码量
  • 易于维护

什么时候忽略

  • 有时,建立冗余类是为了描述将来开发的意图。在这种状况下,尝试在代码中保持清晰和简单之间的平衡。

重构方法说明

将类内联化(Inline Class)

问题bash

某个类没有作太多事情。框架



解决ide

将这个类的全部特性搬移到另外一个类中,而后移除原类。函数



折叠继承体系(Collapse Hierarchy)

问题

超类和子类之间无太大区别。



解决

将它们合为一体。



夸夸其谈将来性

夸夸其谈将来性(Speculative Generality)

存在未被使用的类、函数、字段或参数。



问题缘由

有时,代码仅仅为了支持将来的特性而产生,然而却一直未实现。结果,代码变得难以理解和维护。

解决方法

  • 若是你的某个抽象类其实没有太大做用,请运用 折叠继承体系(Collapse Hierarch)



  • 没必要要的委托可运用 将类内联化(Inline Class) 消除。
  • 无用的函数可运用 内联函数(Inline Method) 消除。
  • 函数中有无用的参数应该运用 移除参数(Remove Parameter) 消除。
  • 无用字段能够直接删除。

收益

  • 减小代码量。
  • 更易维护。

什么时候忽略

  • 若是你在一个框架上工做,建立框架自己没有使用的功能是很是合理的,只要框架的用户须要这个功能。
  • 删除元素以前,请确保它们不在单元测试中使用。若是测试须要从类中获取某些内部信息或执行特殊的测试相关操做,就会发生这种状况。

重构方法说明

折叠继承体系(Collapse Hierarchy)

问题

超类和子类之间无太大区别。



解决

将它们合为一体。



将类内联化(Inline Class)

问题

某个类没有作太多事情。



解决

将这个类的全部特性搬移到另外一个类中,而后移除原类。



内联函数(Inline Method)

问题

一个函数的本体比函数名更清楚易懂。

class PizzaDelivery {
  //...
  int getRating() {
    return moreThanFiveLateDeliveries() ? 2 : 1;
  }
  boolean moreThanFiveLateDeliveries() {
    return numberOfLateDeliveries > 5;
  }
}
复制代码

解决

在函数调用点插入函数本体,而后移除该函数。

class PizzaDelivery {
  //...
  int getRating() {
    return numberOfLateDeliveries > 5 ? 2 : 1;
  }
}
复制代码

移除参数(Remove Parameter)

问题

函数本体再也不须要某个参数。



解决

将该参数去除。



纯稚的数据类

纯稚的数据类(Data Class) 指的是只包含字段和访问它们的 getter 和 setter 函数的类。这些仅仅是供其余类使用的数据容器。这些类不包含任何附加功能,而且不能对本身拥有的数据进行独立操做。



问题缘由

当一个新建立的类只包含几个公共字段(甚至可能几个 getters / setters)是很正常的。可是对象的真正力量在于它们能够包含做用于数据的行为类型或操做。

解决方法

  • 若是一个类有公共字段,你应该运用 封装字段(Encapsulated Field) 来隐藏字段的直接访问方式。
  • 若是这些类含容器类的字段,你应该检查它们是否是获得了恰当的封装;若是没有,就运用 封装集合(Encapsulated Collection) 把它们封装起来。
  • 找出这些 getter/setter 函数被其余类运用的地点。尝试以 搬移函数(Move Method) 把那些调用行为搬移到 纯稚的数据类(Data Class) 来。若是没法搬移这个函数,就运用 提炼函数(Extract Method) 产生一个可搬移的函数。



  • 在类已经充满了深思熟虑的函数以后,你可能想要摆脱旧的数据访问方法,以提供适应面较广的类数据访问接口。为此,能够运用 移除设置函数(Remove Setting Method)隐藏函数(Hide Method)

收益

  • 提升代码的可读性和组织性。特定数据的操做如今被集中在一个地方,而不是在分散在代码各处。
  • 帮助你发现客户端代码的重复处。

重构方法说明

封装字段(Encapsulated Field)

问题

你的类中存在 public 字段。

class Person {
  public String name;
}
复制代码

解决

将它声明为 private,并提供相应的访问函数。

class Person {
  private String name;

  public String getName() {
    return name;
  }
  public void setName(String arg) {
    name = arg;
  }
}
复制代码

封装集合(Encapsulated Collection)

问题

有个函数返回一个集合。



解决

让该函数返回该集合的一个只读副本,并在这个类中提供添加、移除集合元素的函数。



搬移函数(Move 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);
}
复制代码

移除设置函数(Remove Setting Method)

问题

类中的某个字段应该在对象建立时被设值,而后就再也不改变。



解决

去掉该字段的全部设值函数。



隐藏函数(Hide Method)

问题

有一个函数,历来没有被其余任何类用到。



解决

将这个函数修改成 private。



过多的注释

过多的注释(Comments)

注释自己并非坏事。可是经常有这样的状况:一段代码中出现长长的注释,而它之因此存在,是由于代码很糟糕。



问题缘由

注释的做者意识到本身的代码不直观或不明显,因此想使用注释来讲明本身的意图。这种状况下,注释就像是烂代码的除臭剂。

最好的注释是为函数或类起一个恰当的名字。

若是你以为一个代码片断没有注释就没法理解,请先尝试重构,试着让全部注释都变得多余。

解决方法

  • 若是一个注释是为了解释一个复杂的表达式,能够运用 提炼变量(Extract Variable) 将表达式切分为易理解的子表达式。
  • 若是你须要经过注释来解释一段代码作了什么,请试试 提炼函数(Extract Method)
  • 若是函数已经被提炼,但仍须要注释函数作了什么,试试运用 函数更名(Rename Method) 来为函数起一个能够自解释的名字。
  • 若是须要对系统某状态进行断言,请运用 引入断言(Introduce Assertion)

收益

  • 代码变得更直观和明显。

什么时候忽略

注释有时候颇有用:

  • 当解释为何某事物要以特殊方式实现时。
  • 当解释某种复杂算法时。
  • 当你实在不知能够作些什么时。

重构方法说明

提炼变量(Extract Variable)

问题

你有个难以理解的表达式。

void renderBanner() {
  if ((platform.toUpperCase().indexOf("MAC") > -1) &&
       (browser.toUpperCase().indexOf("IE") > -1) &&
        wasInitialized() && resize > 0 )
  {
    // do something
  }
}
复制代码

解决

将表达式的结果或它的子表达式的结果用不言自明的变量来替代。

void renderBanner() {
  final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
  final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
  final boolean wasResized = resize > 0;

  if (isMacOs && isIE && wasInitialized() && wasResized) {
    // do something
  }
}
复制代码

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

函数更名(Rename Method)

问题

函数的名称未能恰当的揭示函数的用途。

class Person {
  public String getsnm();
}
复制代码

解决

修改函数名。

class Person {
  public String getSecondName();
}
复制代码

引入断言(Introduce Assertion)

问题

某一段代码须要对程序状态作出某种假设。

double getExpenseLimit() {
  // should have either expense limit or a primary project
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}
复制代码

解决

以断言明确表现这种假设。

double getExpenseLimit() {
  Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}
复制代码

注:请不要滥用断言。不要使用它来检查”应该为真“的条件,只能使用它来检查“必定必须为真”的条件。实际上,断言更可能是用于自我检测代码的一种手段。在产品真正交付时,每每都会消除全部断言。

重复代码

重复代码(Duplicate Code)

重复代码堪称为代码坏味道之首。消除重复代码老是有利无害的。



问题缘由

重复代码一般发生在多个程序员同时在同一程序的不一样部分上工做时。因为他们正在处理不一样的任务,他们可能不知道他们的同事已经写了相似的代码。

还有一种更隐晦的重复,特定部分的代码看上去不一样但实际在作同一件事。这种重复代码每每难以找到和消除。

有时重复是有目的性的。当急于知足 deadline,而且现有代码对于要交付的任务是“几乎正确的”时,新手程序员可能没法抵抗复制和粘贴相关代码的诱惑。在某些状况下,程序员只是太懒惰。

解决方法

  • 同一个类的两个函数含有相同的表达式,这时能够采用 提炼函数(Extract Method) 提炼出重复的代码,而后让这两个地点都调用被提炼出来的那段代码。



  • 若是两个互为兄弟的子类含有重复代码:
    • 首先对两个类都运用 提炼函数(Extract Method) ,而后对被提炼出来的函数运用 函数上移(Pull Up Method) ,将它推入超类。
    • 若是重复代码在构造函数中,运用 构造函数本体上移(Pull Up Constructor Body)
    • 若是重复代码只是类似但不是彻底相同,运用 塑造模板函数(Form Template Method) 得到一个 模板方法模式(Template Method)
    • 若是有些函数以不一样的算法作相同的事,你能够选择其中较清晰地一个,并运用 替换算法(Substitute Algorithm) 将其余函数的算法替换掉。
  • 若是两个绝不相关的类中有重复代码:
    • 请尝试运用 提炼超类(Extract Superclass) ,以便为维护全部先前功能的这些类建立一个超类。
    • 若是建立超类十分困难,能够在一个类中运用 提炼类(Extract Class) ,并在另外一个类中使用这个新的组件。
  • 若是存在大量的条件表达式,而且它们执行彻底相同的代码(仅仅是它们的条件不一样),能够运用 合并条件表达式(Consolidate Conditional Expression) 将这些操做合并为单个条件,并运用 提炼函数(Extract Method) 将该条件放入一个名字容易理解的独立函数中。
  • 若是条件表达式的全部分支都有部分相同的代码片断:能够运用 合并重复的条件片断(Consolidate Duplicate Conditional Fragments) 将它们都存在的代码片断置于条件表达式外部。

收益

  • 合并重复代码会简化代码的结构,并减小代码量。
  • 代码更简化、更易维护。

重构方法说明

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

函数上移(Pull Up Method)

问题

有些函数,在各个子类中产生彻底相同的结果。



解决

将该函数移至超类。



构造函数本体上移(Pull Up Constructor Body)

问题

你在各个子类中拥有一些构造函数,它们的本体几乎彻底一致。

class Manager extends Employee {
  public Manager(String name, String id, int grade) {
    this.name = name;
    this.id = id;
    this.grade = grade;
  }
  //...
}
复制代码

解决

在超类中新建一个构造函数,并在子类构造函数中调用它。

class Manager extends Employee {
  public Manager(String name, String id, int grade) {
    super(name, id);
    this.grade = grade;
  }
  //...
}
复制代码

塑造模板函数(Form Template Method)

问题

你有一些子类,其中相应的某些函数以相同的顺序执行相似的操做,但各个操做的细节上有所不一样。



解决

将这些操做分别放进独立函数中,并保持它们都有相同的签名,因而原函数也就变得相同了。而后将原函数上移至超类。



注:这里只提到具体作法,建议了解一下模板方法设计模式。

替换算法(Substitute Algorithm)

问题

你想要把某个算法替换为另外一个更清晰的算法。

String foundPerson(String[] people){
  for (int i = 0; i < people.length; i++) {
    if (people[i].equals("Don")){
      return "Don";
    }
    if (people[i].equals("John")){
      return "John";
    }
    if (people[i].equals("Kent")){
      return "Kent";
    }
  }
  return "";
}
复制代码

解决

将函数本体替换为另外一个算法。

String foundPerson(String[] people){
  List candidates =
    Arrays.asList(new String[] {"Don", "John", "Kent"});
  for (int i=0; i < people.length; i++) {
    if (candidates.contains(people[i])) {
      return people[i];
    }
  }
  return "";
}
复制代码

提炼超类(Extract Superclass)

问题

两个类有类似特性。



解决

为这两个类创建一个超类,将相同特性移至超类。



提炼类(Extract Class)

问题

某个类作了不止一件事。



解决

创建一个新类,将相关的字段和函数从旧类搬移到新类。



合并条件表达式(Consolidate Conditional Expression)

问题

你有一系列条件分支,都获得相同结果。

double disabilityAmount() {
  if (seniority < 2) {
    return 0;
  }
  if (monthsDisabled > 12) {
    return 0;
  }
  if (isPartTime) {
    return 0;
  }
  // compute the disability amount
  //...
}
复制代码

解决

将这些条件分支合并为一个条件,并将这个条件提炼为一个独立函数。

double disabilityAmount() {
  if (isNotEligableForDisability()) {
    return 0;
  }
  // compute the disability amount
  //...
}
复制代码

合并重复的条件片断(Consolidate Duplicate Conditional Fragments)

问题

在条件表达式的每一个分支上有着相同的一段代码。

if (isSpecialDeal()) {
  total = price * 0.95;
  send();
}
else {
  total = price * 0.98;
  send();
}
复制代码

解决

将这段重复代码搬移到条件表达式以外。

if (isSpecialDeal()) {
  total = price * 0.95;
}
else {
  total = price * 0.98;
}
send();
复制代码

扩展阅读

参考资料

相关文章
相关标签/搜索