歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

不要if else的編程

Michael Feathers是Object Mentor International公司的技術顧問。他的工作不僅是技術開發,他還參與對世界各地技術團隊進行培訓、指導等工作。他曾開發了將JUnit遷移到C++的CppUnit的初始部分,還有FitCpp——一個C++版的FIT基礎測試框架。他是《Working Effectively with Legacy Code》一書的作者。

條件控制是編程中與生俱來的一種結構,但對於我來說,除了給我帶來麻煩外,沒有發現任何的用處。一次又一次,我不斷發現,越少的if語句,越少的switch語句,越少的循環,就會是越好的代碼。通常這其中的原因是程序員用編程語言實現了更好的抽象歸納。他們並不是有意識的避免使用控制結構。但他們確實做到了這些。

如果是使用一種面向對象編程語言,我們可以用多態(polymorphism)來代替switch。同樣的技巧也能用在if語句上,但如果邏輯太簡單,這樣做就有點得不償失。當使用一種有函數式特征的編程語言時,大部分的循環執行任務我們都可以用map,filter,fold等實現。控制結構最終從代碼中消失,這是對代碼大有好處的事。

條件控制結構的問題是,它很容易導致你把代碼修改的亂七八糟。讓我們看看下面一個簡單的if語句:

 if ...
    ...
  else
    ...
  end 

代碼中所有打省略號的地方都是你可以不斷添加代碼的地方。這些地方可以訪問if外面的變量。這很容易造成高耦合。更糟糕的是,人們會習慣性的在條件控制裡嵌套條件。我見過的最糟糕的代碼,裡面的嵌套之深的就像是噩夢裡的無底洞。我想,條件控制結構的真正問題所在是,它把各種任務混合到了一起。我相信,你能從某種角度上看出,它是和任務單一編程原則相沖突的。

我們該怎麼做?我們可不可以完全不要控制結構?我想不行,但我們可以做一些實驗來看看如何能減少對它們的使用。通常這樣做會讓我們從中學到一些新技巧,讓我們的代碼更整潔。

不久前,我開發了一些Ruby程序,我需要寫一個‘take’函數,用它從一個數組裡取出一些元素。Ruby裡有一些針對Enumerable的這樣的函數,但我需要一些特殊的功能。如果我需要的數組的大小超出了目標數組的大小,需要把多余的數組空間都置為0。

這看起來可以用簡單的if語句實現:

 def padded_take ary, n
    if n <= ary.length
      ary.take(n)
    else
      ary + [0] * (n - ary.length)
    end
  end

讓我們認真的看一看這段代碼。它沒有向我們顯示任何填充動作的信息,沒有顯示數組跟填充的關系。如果認真看,可以看出其中的邏輯,但我們看不出這段代碼的意圖。

我們引入一些函數來讓這段代碼更清楚些,使用guard語句來簡化if語句:

 def padded_take ary, n
    return ary.take(n) unless needs_padding?(ary, n)
    ary + pad(ary, n)
  end

這個短小精悍,但不是更簡單——我們可以使用一個null對象來去掉條件語句。空的數組就是很好的null對象。讓我們在來一次。

我們不需要用一個條件語句來計算填充的長度。這個長度我們可以取兩個數組中的最大值,如果我們想要的長度超出了數組的長度,填充的長度就是它們的差值:

 pad_length = [0, n - ary.length].max

有了這個長度,我們可以先填充數組,然後取出我們想要的元素:

 def pad ary, n
    pad_length = [0, n - ary.length].max
    ary + [0] * pad_length
  end

於是,我們可以這樣定義取出動作:

 def padded_take ary, n
    pad(ary, n).take(n)
  end

我們通過先進行填充從而避免了使用if語句。當然,有時候填充的是一個空數組。

我不想去爭論這樣的寫法是否比最初的if-then-else代碼更簡單,但現在的代碼的意圖更清晰了,而且我不認為這種策略在這種代碼裡使用是過度技術化。

從提取歸納的層面看,代碼經過處理後的好處是明顯的。當遇到更復雜問題時,它帶來的益處將會更明顯。

本文作者介紹

Michael Feathers

Copyright © Linux教程網 All Rights Reserved