1. 程式人生 > >程式碼整潔之道 讀書筆記 - 第3章 函式

程式碼整潔之道 讀書筆記 - 第3章 函式

短小

函式的第一規則是要短小。第二條規則是還要更短小。

函式20行封頂最佳

if語句、else語句、while語句等,其中的程式碼塊應該只有一行,而且,塊內呼叫的函式擁有較具說明性的名稱,還能起到文件的作用。

只做一件事

函式應該做一件事。做好這件事。只做這一件事。

每個函式一個抽象層級

自頂向下讀程式碼:向下規則.

switch語句

只出現一次,用於建立多型物件,且隱藏在某個繼承關係中,系統其他部分看不到。

使用描述性的名稱

沃德原則:如果每個例程都讓你感到深合己意,那就是整潔程式碼。

長而具有描述性的名稱,要比短而令人費解的名稱好,要比描述性的長註釋好。

函式引數

理想的引數數量是越少越好,儘量避免三個以上的引數。

引數帶有太多概念性,includeSetupPage()要比includeSetupPageInto(newPage-Content)易於理解。

從測試的角度看,要編寫能確保引數的各種組合執行正常的測試用例。

1、一元函式的普遍形式

    1)有輸入引數也有輸出引數

boolean fileExists("MyFile")
InputStream fileOpen("MyFile")

    2)有輸入引數沒有輸出引數

void passwordAttemptFailedNtimes(int
attempts)

    3)儘量避免編寫不遵循這些形式的一元函式

void includeSetupPageInto(StringBuffere pageText)

    對於轉換,使用輸出引數而非返回值令人迷惑。

    如果函式要對輸入引數進行轉換操作,轉換結果就該體現為返回值。實際上,StringBuffer transform(StringBuffer in) 比 void transform(StringBuffer out) 好。

2、標識引數

    向函式傳入布林值就是說明本函式不止做一件事。如果標識為true將會這樣做,標識為false則會那樣做。

    將render(Boolean isSuite)函式一分為二:renderForSuite() 和 renderForSingleTest()

3、二元函式

    1)兩個引數的函式要比一元函式難懂

writeField(name)  //一個引數的可讀性更好
writeField(outoutStream, name)

    2)有些時候兩個引數正好

Point p = new Point(0, 0)

    笛卡兒點天生擁有兩個引數。如果看到new Point(0)會反而感到奇怪。

    3)應該儘量利用一些機制將其轉換成一元函式

outputStream.writeField(name)  //把writeField方法寫成outputStream的成員之一

4、三元函式

    有三個引數的函式要比二元函式難懂得多,建議在寫三元函式前一定要想清楚。

5、引數物件

    如果函式看來需要兩個、三個或三個以上引數,就說明其中一些引數應該封裝為類了

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

6、引數列表

7、動詞與關鍵字

    給函式取個好名字,能較好地解釋函式的意圖,以及引數的順序和意圖。

    對於一元函式,函式和引數應當形成一種非常良好的動詞/名詞對形式

write(name)  //不管這個“name”是什麼,都要被“write”
writeField(name)  //更好的名稱,它告訴我們,“name”是一個“field”

無副作用

副作用是一種謊言。函式承諾只做一件事,但還是會做其他被藏起來的事

public class UserValidator {
  private Cryptographer cryptographer;

  public boolean checkPassword(String userName, String password) {
    User user = UserGateway.findByName(userName);
    if (user != User.NULL) {
      String codedPhrase = user.getPhraseEncodedByPassword();
      String phrase = cryptographer.decrypt(codedPhrase, password);
      if ("Valid Password".equals(phrase)) {
        Session.initialize();
        return true;
      }
    }
    return false;
  }
}

程式碼的副作用在於對Session.initialize()的呼叫。checkPassword函式,就是用來檢查密碼的。並未暗示它會初始化該次會話。當某個誤信了函式名的呼叫者想要檢查使用者有效性時,就得冒抹除現有會話資料的風險。

這一副作用造出了一次時序性耦合。checkPassword如果在不合適的時候呼叫,會話資料就有可能沉默地丟失。

如果一定要時序性耦合,就應該在函式名稱中說明。重新命名函式為checkPasswordAndInitializeSession,但這還是違反了“只做一件事”的規則。

分隔指令與詢問

函式要麼做什麼事,要麼回答什麼事,但二者不可兼得

public boolean set(String attribute, String value);

 該函式設定某個指定屬性,如果成功就返回true,如果不存在那個屬性則返回false。這樣就導致了以下語句

if (set("username", "unclebob")) ...

它是在問username屬性值是否之前已設定為unclebob,或是在問username屬性值是否成功設定為unclebob。

要解決這個問題可以將set函式重新命名為setAndCheckIfExists,但真正的解決方案是把指令與詢問分隔開來,防止混淆的發生

if (attributeExists("username")) {
  setAttribute("username", "unclebob");
  ...
}

使用異常替代返回錯誤碼

1、抽離Try/Catch程式碼塊

    把Try/catch程式碼塊的主體部分抽離出來,另外形成函式。

2、錯誤處理就是一件事

    函式應該只做一件事。錯誤處理就是一件事。因此,處理錯誤的函式不該做其他事。

    關鍵字try在某個函式中存在,它就該是這個函式的第一個單詞,而且在catch/finally程式碼塊後面也不該有其他內容。

3、依賴磁鐵

    返回錯誤碼通常暗示某處有個類或是列舉定義了所有錯誤碼

public enum Error {
  OK,
  INVALID,
  NO_SUCH,
  LOCKED,
  OUT_OF_RESOURCES,
  WAITING_FOR_EVENT;
}

    這樣的類就是一塊依賴磁鐵(dependency magnet);當Error列舉修改時,所有使用它的類都需要重新編譯和部署。所以程式設計師寧願複用舊的錯誤碼,而不新增新的。

別重複自己

重複可能是軟體中一切邪惡的根源。許多原則與實踐規則都是為控制與消除重複而建立。

考德(Codd)的資料庫正規化都是為消滅資料重複而服務。

面向物件程式設計將程式碼集中到基類,從而避免了冗餘。

面向方面程式設計(Aspect Oriented Programming)、面向元件程式設計(Component Oriented Programming)多少也都是消除重複的一種策略。

結構化程式設計

有些程式設計師遵循Edsger Dijkstra的結構化程式設計規則。Dijkstra認為,每個函式、函式中的每個程式碼塊都應該有一個入口、一個出口

遵循這些規則,意味著每個函式中只該有一個return語句,迴圈中不能有break或continue語句,而且永永遠遠不能有任何goto語句

對於小函式,這些規則助益不大,而且偶爾出現return、break或continue語句沒有壞處,甚至還比單入單出原則更具有表達力。