气味代码与重构

整理自:https://sourcemaking.com/refactoring/smells

类型一:Bloaters

代码、方法和类增长到庞大的量。

Long Method

一般来说,长于10行的方法就应该考虑是否需要重构。如果方法中有些地方需要注释,就应该考虑是否把这些代码放入新方法内,即使该代码只有一行,原因是如果方法名是描述性的,就不需要阅读进行实际操作的源码。

解决方案:

  • 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() {
  if (basePrice() > 1000) {
    return basePrice() * 0.95;
  }
  else {
    return basePrice() * 0.98;
  }
}
double basePrice() {
  return quantity * itemPrice;
}
  • 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

例如:

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 (isSummer(date)) {
  charge = summerCharge(quantity);
}
else {
  charge = winterCharge(quantity);
}
Large Class

一个包含了很多字段/方法/代码行的类。

解决方案:

  • Extract Class

When one class does the work of two, awkwardness results.

  • Extract Subclass

A class has features that are used only in certain cases.


  • Extract Interface

Multiple clients are using the same part of a class interface. Another case: part of the interface in two classes is the same.

  • Duplicate Observed Data

Is domain data stored in classes responsible for the GUI?

Primitive Obsession

用primitive field而不是small object代表简单事物;用常数来表示信息等。

解决方案:

  • Replace Data Value with Object

A class (or group of classes) contains a data field. The field has its own behavior and associated data.

  • 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 Class

A class has a field that contains type code. The values of this type are not used in operator conditions and do not affect the behavior of the program.

  • Replace Type Code with Subclasses

You have a coded type that directly affects program behavior (values of this field trigger various code in conditionals).

  • Replace Array with Object

例如:

String[] row = new String[2];
row[0] = "Liverpool";
row[1] = "15";

改为

Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
Long Parameter List

一个方法包含了三个或四个以上的参数。

解决方案:

  • Replace Parameter with Method Call

例如:

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
Data Clumps

不同部分的代码都包含了相同的一堆变量(即数据块)。如果要确定某些数据是否为数据块,只需删除其中一个数据值,然后查看其他值是否仍然有意义。如果不是,表明这组变量应该组合成一个对象。

  • Extract Class

When one class does the work of two, awkwardness results.

  • 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);

类型二:Object-Orientation Abusers

对OOP法则的不完整或是不正确的运用。

Switch Statements

存在复杂的switch操作或是一系列if语句。

解决方案:

  • 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);
}
  • Move Method

A method is used more in another class than in its own class.

  • Replace Type Code with Subclasses

You have a coded type that directly affects program behavior (values of this field trigger various code in conditionals).

  • Replace Conditional with Polymorphism

例如:

class Bird {
  //...
  double getSpeed() {
    switch (type) {
      case EUROPEAN:
        return getBaseSpeed();
      case AFRICAN:
        return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return (isNailed) ? 0 : getBaseSpeed(voltage);
    }
    throw new RuntimeException("Should be unreachable");
  }
}

改为

abstract class Bird {
  //...
  abstract double getSpeed();
}

class European extends Bird {
  double getSpeed() {
    return getBaseSpeed();
  }
}
class African extends Bird {
  double getSpeed() {
    return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  }
}
class NorwegianBlue extends Bird {
  double getSpeed() {
    return (isNailed) ? 0 : getBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.getSpeed();
  • Replace Parameter with Explicit Methods

例如:

void setValue(String name, int value) {
  if (name.equals("height")) {
    height = value;
    return;
  }
  if (name.equals("width")) {
    width = value;
    return;
  }
  Assert.shouldNeverReachHere();
}

改为

void setHeight(int arg) {
  height = arg;
}
void setWidth(int arg) {
  width = arg;
}
  • Introduce Null Object

例如:

if (customer == null) {
  plan = BillingPlan.basic();
}
else {
  plan = customer.getPlan();
}

改为

class NullCustomer extends Customer {
  boolean isNull() {
    return true;
  }
  Plan getPlan() {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
customer = (order.customer != null) ?
  order.customer : new NullCustomer();

// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
Temporary Field

字段只在某些情况下被赋值,而在剩下的情况下,都不会被赋值。

解决方案:

  • Extract Class

When one class does the work of two, awkwardness results.

  • Replace Method with Method Object

例如:

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.
    //...
  }
}
  • Introduce Null Object

例如:

if (customer == null) {
  plan = BillingPlan.basic();
}
else {
  plan = customer.getPlan();
}

改为

class NullCustomer extends Customer {
  boolean isNull() {
    return true;
  }
  Plan getPlan() {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
customer = (order.customer != null) ?
  order.customer : new NullCustomer();

// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
Refused Bequest

如果子类仅使用从其父项继承的某些方法和属性,则说明使用这种层次结构是不合适的。

解决方案:

  • Replace Inheritance with Delegation

You have a subclass that uses only a portion of the methods of its superclass (or it’s not possible to inherit superclass data).

  • Extract Superclass

You have two classes with common fields and methods.

Alternative Classes with Different Interfaces

两个类的功能几乎一样只是方法名不同。

  • Rename Method

The name of a method does not explain what the method does.

  • Move Method

A method is used more in another class than in its own class.

  • Add Parameter

A method does not have enough data to perform certain actions.

  • Parameterize Method

Multiple methods perform similar actions that are different only in their internal values, numbers or operations.

  • Extract Superclass

You have two classes with common fields and methods.

类型三:Change Preventers

如果你需要在代码中的某个位置更改某些内容,则必须在其他位置进行许多更改。

Divergent Change

当对某个类进行更改时,你发现自己必须更改许多不相关的方法。

解决方案:

  • Extract Class

When one class does the work of two, awkwardness results.

  • Extract Superclass

You have two classes with common fields and methods.

  • Extract Subclass

A class has features that are used only in certain cases.


Shotgun Surgery

进行任何修改都需要对许多不同的类进行许多小的更改。

解决方案:

  • Move Method

A method is used more in another class than in its own class.

  • Move Field

A field is used more in another class than in its own class.

  • Inline Class

A class does almost nothing and is not responsible for anything, and no additional responsibilities are planned for it.

Parallel Inheritance Hierarchies

每当为类创建子类时,你发现自己需要为另一个类创建子类。

解决方案:

  • Move Method

A method is used more in another class than in its own class.

  • Move Field

A field is used more in another class than in its own class.

类型四:Dispensables

指毫无意义或是不必要的东西,将其移除会使代码更清晰,更有效,更容易理解。

Comments

方法里面充满了解释性注释。

解决方案:

  • 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

The name of a method does not explain what the method does.

  • 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

两个或多个代码片段看起来几乎相同。

解决方案:

  • 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 Field

Two classes have the same field.

  • 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

Your subclasses implement algorithms that contain similar steps in the same order.

  • 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

You have two classes with common fields and methods.

  • Extract Class

When one class does the work of two, awkwardness results.

  • Consolidate Conditional Expression

You have multiple conditionals that lead to the same result or action.

例如:

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
  //...
}

解决方案:

  • 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);
}
  • 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();
Lazy Class

如果一个类的几乎不会被用上,就应该被删除。

解决方案:

  • Inline Class

A class does almost nothing and is not responsible for anything, and no additional responsibilities are planned for it.

  • Collapse Hierarchy

You have a class hierarchy in which a subclass is practically the same as its superclass.

Data Class

数据类是指仅包含用于访问它们的字段和粗略方法的类(getter和setter)。这些只是其他类使用的数据的容器。这些类不包含任何其他功能,也不能独立操作它们拥有的数据。

解决方案:

  • Encapsulate Field

例如:

class Person {
  public String name;
}

改为

class Person {
  private String name;

  public String getName() {
    return name;
  }
  public void setName(String arg) {
    name = arg;
  }
}
  • Encapsulate Collection

A class contains a collection field and a simple getter and setter for working with the collection.

  • Move Method

A method is used more in another class than in its own class.

  • 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

The value of a field should be set only when it is created, and not change at any time after that.

  • Hide Method

A method is not used by other classes or is used only inside its own class hierarchy.

Dead Code

不再被使用的变量,参数,字段,方法或类。

解决方案:

  • 使用好的IDE,通过IDE标注找到没被使用的变量,参数,字段,方法或类。

  • Inline Class

A class does almost nothing and is not responsible for anything, and no additional responsibilities are planned for it.

  • Collapse Hierarchy

You have a class hierarchy in which a subclass is practically the same as its superclass.

  • Remove Parameter

A parameter is not used in the body of a method.

Couplers

类与类之间过度耦合。

Feature Envy

方法访问另一个对象的数据多于访问自己的数据。

解决方案:

  • Move Method

A method is used more in another class than in its own class.

  • 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);
}
Inappropriate Intimacy

一个类使用另一个类的内部字段和方法。

解决方案:

  • Move Method

A method is used more in another class than in its own class.

  • Move Field

A field is used more in another class than in its own class.

  • Extract Class

When one class does the work of two, awkwardness results.

  • Hide Delegate

The client gets object B from a field or method of object А. Then the client calls a method of object B.

  • Change Bidirectional Association to Unidirectional

You have a bidirectional association between classes, but one of the classes does not use the other’s features.

  • Replace Delegation with Inheritance

A class contains many simple methods that delegate to all methods of another class.

Message Chains

在代码中你看到类似于这样的一系列的方法调用: $a->b()->c()->d()

解决方案:

  • Hide Delegate

The client gets object B from a field or method of object А. Then the client calls a method of object B.

  • 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);
}
  • Move Method

A method is used more in another class than in its own class.

Middle Man

如果一个类只执行将工作委托给另一个类,那么它就没有存在的必要。

解决方案:

  • Remove Middle Man

A class has too many methods that simply delegate to other objects.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,294评论 0 10
  • 一直以来,都会有意的去避开看青春校园类电影,像《那些年》《致青春》《匆匆那年》《同桌的你》《微微一笑》…… 这些电...
    邹小芝阅读 313评论 0 0
  • 定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定...
    萌妈码码阅读 175评论 0 0
  • 世间有两大悲哀:一种是不读书,一种是读书太多了。读书就跟汽车加油一样,适当加一点,跑得很快,加得太多了,就变成油罐...
    007莫圣书阅读 474评论 0 0
  • 半夜二点十分。 我关灯,爬上床,被子还没揭开。 有人敲门。 我开门,没人。 有人敲门。 我开门,没人。 有人敲门。...
    迷路的浮士德阅读 1,750评论 1 1