返回

重构之代码的坏味道(一)

前端

在软件开发过程中,我们常常会遇到各种各样的代码坏味道。这些坏味道会使代码难以理解、难以维护,并且容易出错。本文介绍了10种常见的代码坏味道,以及如何通过重构来消除这些坏味道。

1. 神秘命名

神秘命名是指使用一些晦涩难懂的变量名、函数名或类名。这样的命名方式会使代码难以理解,也容易造成错误。例如,以下代码中的变量名就非常神秘:

int x = 10;
int y = 20;
int z = x + y;

这段代码中,变量名x、y和z都没有任何意义,读者很难理解这段代码的含义。为了消除这个坏味道,我们可以将这些变量名改成更有意义的名称,例如:

int width = 10;
int height = 20;
int area = width + height;

这样,读者就可以很容易地理解这段代码的含义了。

2. 重复代码

重复代码是指在代码中出现多处相同的代码段。这样的代码会使代码难以理解、难以维护,并且容易出错。例如,以下代码中就存在重复代码:

if (x > 0) {
  // do something
} else {
  // do something else
}

if (y > 0) {
  // do something
} else {
  // do something else
}

这段代码中,if语句的结构完全相同,只是变量x和y不同。为了消除这个坏味道,我们可以使用循环来代替重复的if语句:

for (int i = 0; i < 2; i++) {
  int x = i == 0 ? x : y;
  if (x > 0) {
    // do something
  } else {
    // do something else
  }
}

这样,代码就更加简洁和易于理解了。

3. 过长函数

过长函数是指代码行数过多的函数。这样的函数难以理解、难以维护,并且容易出错。一般来说,函数的代码行数最好不要超过100行。如果一个函数的代码行数超过了100行,那么就应该考虑将其分解成多个更小的函数。

4. 全局数据

全局数据是指在程序中所有函数都可以访问的数据。这样的数据会使代码难以理解、难以维护,并且容易出错。例如,以下代码中的全局变量x就可以被程序中的任何函数访问:

int x = 10;

void f1() {
  x++;
}

void f2() {
  x--;
}

这段代码中,函数f1()和函数f2()都可以修改全局变量x的值。这样,如果一个函数不小心修改了全局变量x的值,那么就会影响到其他函数的运行。为了消除这个坏味道,我们可以使用局部变量来代替全局变量。例如,以下代码中的局部变量x只能被函数f1()访问:

void f1() {
  int x = 10;
  x++;
}

void f2() {
  int x = 20;
  x--;
}

这样,函数f1()和函数f2()就不会互相影响了。

5. 发散式变化

发散式变化是指当修改代码时,需要在多个地方进行修改。这样的代码难以理解、难以维护,并且容易出错。例如,以下代码中的类Customer需要在多个地方进行修改,才能支持新的地址类型:

public class Customer {
  private String name;
  private String address;

  public Customer(String name, String address) {
    this.name = name;
    this.address = address;
  }

  public String getName() {
    return name;
  }

  public String getAddress() {
    return address;
  }

  public void setAddress(String address) {
    this.address = address;
  }
}

为了消除这个坏味道,我们可以使用继承或组合来将代码组织成更小的模块。例如,以下代码中的类CustomerAddress将地址信息封装成一个单独的模块,这样当需要支持新的地址类型时,只需要修改这个模块就可以了:

public class Customer {
  private String name;
  private CustomerAddress address;

  public Customer(String name, CustomerAddress address) {
    this.name = name;
    this.address = address;
  }

  public String getName() {
    return name;
  }

  public CustomerAddress getAddress() {
    return address;
  }

  public void setAddress(CustomerAddress address) {
    this.address = address;
  }
}

public class CustomerAddress {
  private String street;
  private String city;
  private String state;
  private String zip;

  public CustomerAddress(String street, String city, String state, String zip) {
    this.street = street;
    this.city = city;
    this.state = state;
    this.zip = zip;
  }

  public String getStreet() {
    return street;
  }

  public String getCity() {
    return city;
  }

  public String getState() {
    return state;
  }

  public String getZip() {
    return zip;
  }

  public void setStreet(String street) {
    this.street = street;
  }

  public void setCity(String city) {
    this.city = city;
  }

  public void setState(String state) {
    this.state = state;
  }

  public void setZip(String zip) {
    this.zip = zip;
  }
}

这样,代码就更加简洁和易于理解了。

6. 霰弹式修改

霰弹式修改是指当修改代码时,需要在多个地方进行小的修改。这样的代码难以理解、难以维护,并且容易出错。例如,以下代码中的类Customer需要在多个地方进行小的修改,才能支持新的客户类型:

public class Customer {
  private String name;
  private String address;
  private String phone;
  private String email;

  public Customer(String name, String address, String phone, String email) {
    this.name = name;
    this.address = address;
    this.phone = phone;
    this.email = email;
  }

  public String getName() {
    return name;
  }

  public String getAddress() {
    return address;
  }

  public String getPhone() {
    return phone;
  }

  public String getEmail() {
    return email;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setAddress(String address) {
    this.address = address;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  public void setEmail(String email) {
    this.email = email;
  }
}

为了消除这个坏味道,我们可以使用组合或继承来将代码组织成更小的模块。例如,以下代码中的类CustomerPersonalInfo将客户的个人信息封装成一个单独的模块,这样当需要支持新的客户类型时,只需要修改这个模块就可以了:

public class Customer {
  private String name;
  private CustomerPersonalInfo personalInfo;

  public Customer(String name, CustomerPersonalInfo personalInfo) {
    this.name = name;
    this.personalInfo = personalInfo;
  }

  public String getName() {
    return name;
  }

  public CustomerPersonalInfo getPersonalInfo() {
    return personalInfo;
  }

  public void setPersonalInfo(CustomerPersonalInfo personalInfo) {
    this.personalInfo = personalInfo;
  }
}

public class CustomerPersonalInfo {
  private String address;
  private String phone;
  private String email;

  public CustomerPersonalInfo(String address, String phone, String email) {
    this.address = address;
    this.phone = phone;
    this.email = email;
  }

  public String getAddress() {
    return address;
  }

  public String getPhone() {
    return phone;
  }

  public String getEmail() {
    return email;
  }

  public void setAddress(String address) {
    this.address = address;
  }