气味代码与重构

2019-01-22  本文已影响0人  鹿特丹的风

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

类型一:Bloaters

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

Long Method

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

解决方案:

例如:

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

例如:

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

改为

boolean withinPlan = plan.withinRange(daysTempRange);

例如:

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

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

解决方案:

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

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


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

Is domain data stored in classes responsible for the GUI?

Primitive Obsession

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

解决方案:

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

例如:

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

改为

boolean withinPlan = plan.withinRange(daysTempRange);

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.

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

例如:

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

改为

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

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

解决方案:

例如:

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

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

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

例如:

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语句。

解决方案:

例如:

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

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

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

例如:

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

例如:

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;
}

例如:

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

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

解决方案:

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

例如:

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 (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

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

解决方案:

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

You have two classes with common fields and methods.

Alternative Classes with Different Interfaces

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

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

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

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

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

You have two classes with common fields and methods.

类型三:Change Preventers

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

Divergent Change

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

解决方案:

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

You have two classes with common fields and methods.

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


Shotgun Surgery

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

解决方案:

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

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

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

Parallel Inheritance Hierarchies

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

解决方案:

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

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

类型四:Dispensables

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

Comments

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

解决方案:

例如:

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

解决方案:

例如:

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

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

例如:

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

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

解决方案:

例如:

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

Two classes have the same field.

例如:

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

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

例如:

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 "";
}

You have two classes with common fields and methods.

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

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

解决方案:

例如:

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

例如:

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

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

解决方案:

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

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

Data Class

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

解决方案:

例如:

class Person {
  public String name;
}

改为

class Person {
  private String name;

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

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

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

例如:

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

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

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

Dead Code

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

解决方案:

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

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

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

Couplers

类与类之间过度耦合。

Feature Envy

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

解决方案:

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

例如:

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

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

解决方案:

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

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

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

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

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

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

Message Chains

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

解决方案:

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

例如:

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

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

Middle Man

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

解决方案:

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

上一篇下一篇

猜你喜欢

热点阅读