代码坏味道之非必要的

栏目: 后端 · 发布时间: 5年前

内容简介:也许一个类的初始设计是一个功能完全的类,然而随着代码的变迁,变得没什么用了。 又或者类起初的设计是为了支持未来的功能扩展,然而却一直未派上用场。某个类没有做太多事情。

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

解决方法

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

代码坏味道之非必要的

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

收益

  • 减少代码量
  • 易于维护

何时忽略

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

重构方法说明

将类内联化(Inline Class)

问题

某个类没有做太多事情。

代码坏味道之非必要的

解决

将这个类的所有特性搬移到另一个类中,然后移除原类。

代码坏味道之非必要的

折叠继承体系(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();
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

冒号课堂

冒号课堂

郑晖 / 电子工业出版社 / 2009-10 / 65.00元

《冒号课堂》以课堂对话的形式,借六位师生之口讲述编程要义。上篇对编程范式作了入门性的介绍,并对一些流行的编程语言进行了简评;下篇侧重阐发软件设计思想,其中在范式上以OOP为主,在语言上以C++、Java和C#为主。全书寓庄于谐,深入浅出,既可开阔眼界,又能引发思考,值得编程爱好者品读。一起来看看 《冒号课堂》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具