【軟件構造】第三章第二節 設計規約

第三章第二節 軟件規約

  這一節咱們轉向關注「方法/函數/操做」是如何定義的,即討論編程中的動詞,規約。html

Outline

  • 一個完整的方法
  • 什麼是設計規約,咱們爲何須要他
  • 行爲等價性
  • 規約的結構:前置條件與後置條件
    • 規約的結構
    • 可變方法的規約
  • 規約的評價
    • 規約的肯定性
    • 規約的陳述性
    • 規約的強度
    • 如何設計一個好的規約
    • 是否使用前置條件

Notes

## 一個完整的方法

  • 一個完整的方法包括規約spec和實現體implementation;
  • "方法"是程序的積木,它能夠被獨立的開發、測試、複用;
  • 使用「方法」的客戶端,無需瞭解方法內部如何工做,這就是抽象的概念;
  • 參數類型和返回值類型的檢查都是在靜態類型檢查階段完成的。
  • 更多關於方法的內容,請參考 RUNBOOB Java 方法

 

## 什麼是設計規約,咱們爲何須要他

  • 爲何要有設計規約
    • 不少bug來自於雙方之間的誤解;沒有規約,那麼不一樣開發者的理解就可能不一樣
    • 代碼慣例增長了軟件包的可讀性,使工程師們更快、更完整的理解軟件
    • 能夠幫助程序員養成良好的編程習慣,提升代碼質量
    • 沒有規約,難以定位錯誤
  • 使用設計規約的好處
    • 規約起到了契約的做用。表明着程序與客戶端之間達成的一致;客戶端無需閱讀調用函數的代碼,只需理解spec便可。
    • 精確的規約,有助於區分責任,給「供需雙方」肯定了責任,在調用的時候雙方都要遵照。
  • 實例

    • 規約能夠隔離「變化」,無需通知客戶端
    • 規約也能夠提升代碼效率
  • 實例參考 阿里Java開發手冊之編程規約

 

 ## 行爲等價性

行爲等價性就是站在客戶端的角度考量兩個方法是否能夠互換java

參考下述兩個函數:程序員

 1 static int findFirst(int[] arr, int val) {  2     for (int i = 0; i < arr.length; i++) {  3         if (arr[i] == val) return i;  4  }  5     return arr.length;  6 }  7 
 8 static int findLast(int[] arr, int val) {  9     for (int i = arr.length - 1 ; i >= 0; i--) { 10         if (arr[i] == val) return i; 11  } 12     return -1; 13 }
  • 行爲等價性分析:
    • 當val不存在時,findFirst返回arr的長度,findLast返回-1; 
    • 當val出現兩次時,findFirst返回較低的索引,findLast返回較高的索引。 
    • 可是,當val剛好出如今數組的一個索引處時,這兩個方法表現相同。 
    • 故,若是調用方法時,都傳入一個 正好具備一個val的arr ,那麼這兩種方法是同樣的。
  • 另外,咱們也能夠根據規約判斷是否行爲等價注:規約與實現無關,規範無需討論方法類的局部變量或方法類的私有字段。
    • static int findExactlyOne(int[] arr, int val) requires: val occurs exactly once in arr effects: returns index i such that arr[i] = val
    • 兩個函數附和同一個規約,故兩者等價編程

 

## 規約的結構:前置條件與後置條件

【規約的結構】數組

  •  一個方法的規約常由如下幾個短句組成契約:若是前置條件知足了,後置條件必須知足。若是沒有知足,將產生不肯定的異常行爲
    • 前置條件(precondition):對客戶端的約束,在使用方法時必須知足的條件。由關鍵字 requires 表示;
    • 後置條件(postcondition):對開發者的約束,方法結束時必須知足的條件。由關鍵字 effects 表示
    • 異常行爲(Exceptional behavior):若是前置條件被違背,會發生什麼
  • 靜態類型聲明是一種規約,可據此進行靜態類型檢查。 
  • 方法前的註釋也是一種規約,但需人工斷定其是否知足。
    • 數由@param 描述
    • 子句和結果用 @return 和 @ throws子句 描述
    • 儘量的將前置條件放在 @param 中
    • 儘量的將後置條件放在 @return 和 @throws 中

【mutating methods(可變方法)的規約】app

  • 除非在後置條件裏聲明過,不然方法內部不該該改變輸入參數。 
  • 應儘可能遵循此規則,儘可能不設計 mutating的spec,不然就容易引起bugs。 
  • 程序員之間應達成的默契:除非spec必須如此,不然不該修改輸入參數 。 
  • 儘可能避免使用可變(mutable)的對象 。
    • 對可變對象的多引用,須要程序維護一致性,此時合同再也不是單純的在用戶和實現者之間維持,須要每個引用者都有良好的習慣,這就使得簡單的程序變得複雜;
    • 可變對象使得程序難以理解,也難以保證正確性;
    • 可變數據類型還會致使程序修改變得異常困難; 

## 規約的評價

規約評價的三個標準函數

  • 規約的肯定性
  • 規約的陳述性
  • 規約的強度

【規約的肯定性】oop

  肯定的規約:給定一個知足前置條件的輸入,其輸出是惟一的、明確的post

1 static int findExactlyOne(int[] arr, int val) 2  requires: val occurs exactly once in arr 3   effects:  returns index i such that arr[i] = val

  欠定的規約:同一個輸入能夠有多個輸出測試

1 static int findOneOrMore,AnyIndex(int[] arr, int val) 2  requires: val occurs in arr 3   effects:  returns index i such that arr[i] = val

  未肯定的規約:同一個輸入,屢次執行時獲得的輸出可能不一樣;但爲了不分歧,咱們一般將不是肯定的spec統必定義爲欠定的規約。

【規約的陳述性】

  • 操做式規約(Operational specs):僞代碼 。 
  • 聲明式規約(Declarative specs):沒有內部實現的描述,只有 「初-終」狀態 。 
  • 聲明式規約更有價值 ; 內部實現的細節不在規約裏呈現,而放在代碼實現體內部註釋裏呈現。

舉一個栗子:

static String join(String delimiter, String[] elements) effects : returns the result of adding all elements to a new : StringJoiner(delimiter) // Operational specs
 effects:returns the result of looping through elements and alternately appending an element and the delimiter // Operational specs
 effects: returns concatenation of elements in order, with delimiter inserted between each pair of adjacent elements // Declarative specs

【規約的強度】

  • 經過比較規約的強度來判斷是否能夠用一個規約替換另外一個;
  • 若是規約的強度 S2>=S1,就能夠用S2代替S1,體現有二:一個更強的規約包括更輕鬆的前置條件和更嚴格的後置條件;越強的規約,意味着實現者(implementor)的自由度和責任越重,而客戶(client)的責任越輕。
    • S2的前置條件更弱
    • S2的後置條件更強

舉一個栗子:

  • Original spec:
1 static int findExactlyOne(int[] a, int val) 2  requires: val occurs exactly once in a 3   effects:  returns index i such that a[i] = val
  • A stronger spec:
1 static int findOneOrMore,AnyIndex(int[] a, int val) 2  requires: val occurs at least once in a 3   effects:  returns index i such that a[i] = val
  • A much stronger spec:
1 static int findOneOrMore,FirstIndex(int[] a, int val) 2  requires: val occurs at least once in a 3   effects:  returns lowest index i such that a[i] = val

 

【如何設計一個好的規約】

  • 規約應該是簡潔的:整潔,具備良好的結構,易於理解。
  • 規約應該是內聚的:Spec描述的功能應單1、簡單、易理解。
  • 規約應該是信息豐富的:不能讓客戶端產生理解的歧義。
  • 規約應該是強度足夠的:須要知足客戶端基本需求,也必須考慮特殊狀況。
  • 規約的強度也不能太強:太強的spec,在不少特殊狀況下難以達到。
  • 規約應該使用抽象類型:在規約裏使用抽象類型,能夠給方法的實現體與客戶端更大的自由度。

【是否使用前置條件】

  • 是否使用前置條件取決於若是隻在類的內部使用該方法(private),那麼能夠不使用前置條件,在使用該方法的各個位置進行check——責任交給內部client。
    • check的代價;
    • 方法的使用範圍;
  • 若是在其餘地方使用該方法(public),那麼必需要使用前置條件,若client端不知足則方法拋出異常。
相關文章
相關標籤/搜索