代碼壞味道之非必要的

: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();
複製代碼

擴展閱讀

參考資料

相關文章
相關標籤/搜索