第3章:抽象數據類型(ADT)和麪向對象編程(OOP) 3.2設計規約

大綱

1.編程語言中的功能/方法
2.規約:便於交流的編程,爲何須要規約
行爲等同規約結構:前提條件和後條件測試和驗證規約
3.設計規約分類規約圖表規約質量規約
4.總結程序員

編程語言的功能和方法

方法:構建模塊
大型項目由小型方法構建
方法能夠單獨開發,測試和重複使用
方法的用戶不須要知道它是如何工做的 - 這被稱爲「抽象」算法

注意:調用方法時參數類型不匹配 - 靜態檢查
返回值類型是否匹配,也在靜態類型檢查階段完成編程

規約:便於交流的編程

(1)編程中的文檔安全

Java API文檔:一個例子
類層次結構和實現的接口列表。
直接子類,併爲接口實現類。
類的描述
構建思想
方法摘要列出了咱們能夠調用的全部方法
每一個方法和構造函數的詳細描述數據結構

  • 方法簽名:咱們看到返回類型,方法名稱和參數。 咱們也看到例外。 目前,這些一般意味着該方法可能遇到的錯誤。
  • 完整的描述。
  • 參數:方法參數的描述。
  • 以及該方法返回的描述。

記錄假設編程語言

向下寫入變量的類型記錄了一個關於它的假設:例如,此變量將始終引用一個整數。函數

  • Java實際上在編譯時檢查了這個假設,並保證在你的程序中沒有地方違反了這個假設。

聲明一個變量final也是一種形式的文檔,聲明該變量在初始賦值後永遠不會改變。工具

  • Java也會靜態地檢查它。

如何函數/方法的假設?性能

便於交流的編程測試

爲何咱們須要寫下咱們的假設?

  • 由於編程充滿了它們,若是咱們不寫下它們,咱們將不會記住它們,而其餘須要閱讀或更改咱們程序的人將不會知道它們。 他們必須猜想。

程序必須記住兩個目標:

  • 與電腦交流。 首先說服編譯器你的程序是合理的 - 語法正確和類型正確。 而後讓邏輯正確,以便在運行時提供正確的結果。
  • 與其餘人溝通。 使程序易於理解,以便在有人修復,改進或將來適應時,他們能夠這樣作。

黑客與工程

黑客每每以肆無忌憚的樂觀爲標誌

  • 很差:在測試任何代碼以前先寫不少代碼
  • 很差:把全部的細節都留在腦海中,假設你會永遠記住它們,而不是寫在你的代碼中
  • 很差:假設錯誤不存在或者很容易找到並修復

但軟件工程不是黑客行爲。 工程師是悲觀主義者:

  • 好:一次寫一點,隨時測試(第7章中的測試優先編程)。
  • 好:記錄你的代碼依賴的假設
  • 好:捍衛你的代碼免受愚蠢 - 尤爲是你本身的!

靜態檢查有助於此

(2)規約和契約(方法)

規約(或稱爲契約)
規約是團隊合做的關鍵。 沒有規約就不可能委託實施方法的責任。
規約做爲一種契約:實施者負責知足契約,而使用該方法的客戶能夠依賴契約。

  • 說明方法和調用者的責任
  • 定義實現的正確含義

規約對雙方都有要求:當規約有先決條件時,客戶也有責任。

  • 若是你在這個時間表上支付了這筆款項......
  • 我將用下面的詳細規約來構建一個
  • 有些契約有不履行的補救措施

爲何規約?

現實:

  • 程序中最多見的錯誤是因爲對兩段代碼之間的接口行爲的誤解而產生的。
  • 儘管每一個程序員都有規約說明,但並非全部的程序員都把它們寫下來。 所以,團隊中的不一樣程序員有不一樣的規約。
  • 程序失敗時,很難肯定錯誤的位置。

優勢:

  • 代碼中的精確規約讓您分攤代碼片斷的責備,而且能夠免除您在修復應該去的地方使人費解的痛苦。
  • 規約對於一個方法的客戶來講是很好的,由於他們不須要閱讀代碼。

規約對於方法的實現者來講是很好的,由於他們給了實現者自由地改變實現而不告訴客戶。
規約也可使碼代碼更快。
契約充當客戶和實施者之間的防火牆。

  • 它保護客戶免受單位工做細節的影響。
  • 它將執行器從單元使用的細節中屏蔽掉。
  • 這種防火牆會致使解耦,容許單元的代碼和客戶端的代碼獨立更改,只要這些更改符合規約。
  • 解耦,不須要了解具體實現

對象與其用戶之間的協議

  • 方法簽名(型號規約)
  • 功能和正確性預期
  • 性能預期性能

該方法作了什麼,而不是如何作

  • 接口(API),不是實現

(3)行爲等價性

要肯定行爲等同性,問題是咱們是否能夠用另外一個實現替代另外一個實現
等價的概念在客戶眼中。

爲了使一個實現替代另外一個實現成爲可能,而且知道什麼時候能夠接受,咱們須要一個規約來講明客戶端依賴於什麼
注意:規約不該該談論方法類的局部變量或方法類的私有字段。

(4)規約結構:前提條件和後置條件

一個方法的規約由幾個子句組成:

  • 先決條件,由關鍵字require表示
  • 後置條件,由關鍵字效果表示
  • 特殊行爲:若是違反先決條件,會發生什麼?

先決條件是客戶(即方法的調用者)的義務。 這是調用方法的狀態。
後置條件是該方法實施者的義務。
若是前提條件適用於調用狀態,則該方法必須遵照後置條件,方法是返回適當的值,拋出指定的異常,修改或不修改對象等等。

總體結構是一個合乎邏輯的含義:若是在調用方法時前提條件成立,則在方法完成時必須保持後置條件。
若是在調用方法時前提條件不成立,則實現不受後置條件的限制。

  • 能夠自由地作任何事情,包括不終止,拋出異常,返回任意結果,進行任意修改等。

Java中的規約

Java的靜態類型聲明其實是方法的前提條件和後置條件的一部分,該方法是編譯器自動檢查和執行的一部分。
靜態檢查
契約的其他部分必須在該方法以前的評論中進行描述,而且一般取決於人類對其進行檢查並予以保證。
參數由@param子句描述,結果由@return和@throws子句描述。
將前提條件放在@param中,並將後置條件放入@return和@throws。

可變方法的規約

若是效應沒有明確說明輸入能夠被突變,那麼咱們假設輸入的突變是隱式地被禁止的。
幾乎全部的程序員都會承擔一樣的事情。 驚喜突變致使可怕的錯誤。
慣例:

  • 除非另有說明,不然不容許突變。
  • 沒有突變的投入

可變對象可使簡單的規約/合約很是複雜
可變對象下降了可變性

可變對象使簡單的合約變得複雜

對同一個可變對象(對象的別名)的屢次引用可能意味着程序中的多個地方 - 可能至關分散 - 依靠該對象保持一致。
按照規約說明,契約不能再在一個地方執行,例如, 一個類的客戶和一個類的實施者之間。
涉及可變對象的契約如今取決於每一個引用可變對象的每一個人的良好行爲。

做爲這種非本地契約現象的一個症狀,考慮Java集合類,這些類一般記錄在客戶端和實現者之間的很是明確的契約中。

  • 嘗試找到它在客戶端記錄關鍵要求的位置,以便在迭代時沒法修改集合。

對這樣的全局屬性進行推理的須要使得理解難度更大,而且對可變數據結構的程序的正確性有信心。
咱們仍然必須這樣作 - 爲了性能和便利性 - 可是爲了這樣作,咱們在bug安全方面付出了巨大的代價。

可變對象下降了可變性

可變對象使得客戶端和實現者之間的契約更加複雜,而且減小了客戶端和實現者改變的自由。
換句話說,使用容許更改的對象會使代碼難以改變。

(5)*測試和驗證規約

正式契約規約

Java建模語言(JML)
這是一個有優點的理論方法

  • 運行時檢查自動生成
  • 正式驗證的依據
  • 自動分析工具

缺點

  • 須要不少工做
  • 在大的不切實際
  • 行爲的某些方面不符合正式規約

文本說明 - Javadoc

實用方法
記錄每一個參數,返回值,每一個異常(選中和未選中),該方法執行的操做,包括目的,反作用,任何線程安全問題,任何性能問題。
不要記錄實施細節

語義正確性遵照契約

編譯器確保類型正確(靜態類型檢查)

  • 防止許多運行時錯誤,例如「未找到方法」和「沒法將布爾值添加到int」

靜態分析工具(如FindBugs)能夠識別許多常見問題(錯誤模式)

  • 例如:覆蓋equals而不覆蓋hashCode

可是,如何確保語義的正確性?

正式驗證

使用數學方法證實正式規約的正確性
正式證實一個實現的全部可能的執行符合規約
手動努力; 部分自動化; 不能自動肯定

測試

使用受控環境中的選定輸入執行程序
目標

  • 顯示錯誤,所以能夠修復(主要目標)
  • 評估質量
  • 明確說明書,文件

黑盒測試:以獨立於實現的方式檢查測試的程序是否遵循指定的規約。

設計規約

(1)按規約分類

比較規約

它是如何肯定性的。 該規約是否僅爲給定輸入定義了單個可能的輸出,或容許實現者從一組合法輸出中進行選擇?
它是如何聲明的。 規約是否只是表徵輸出的結果,仍是明確說明如何計算輸出?
它有多強大。 規約是否只有一小部分法律實施或一大套?
「什麼使一些規約比其餘規約更好?」

如何比較兩種規約的行爲來決定用新規約替換舊規約是否安全?

規約S2強於或等於規約S1若是

  • S2的先決條件弱於或等於S1
  • 對於知足S1的先決條件的狀態,S2的後置條件強於或等於S1。

那麼知足S2的實現也能夠用來知足S1,在程序中用S2代替S1是安全的。

規則:

  • 削弱先決條件:減小對客戶的要求永遠不會讓他們感到不安。
  • 增強後續條件,這意味着作出更多的承諾。

若是S3既不強於也不弱於S1,則規約可能會重疊(所以存在僅知足S1,僅S3,以及S1和S3的實現)或者可能不相交。
在這兩種狀況下,S1和S3都是沒法比較的。

(2)圖表規約

這個空間中的每一個點表明一個方法實現。
規約在全部可能的實現的空間中定義了一個區域。
一個給定的實現要麼按照規約行事,要知足前置條件 - 隱含 - 後置契約(它在區域內),或者不(在區域外)。
實現者能夠自由地在規約中移動,更改代碼而不用擔憂會破壞客戶端。
這對於實現者可以提升其算法的性能,代碼的清晰度或者在發現錯誤時改變他們的方法等而言是相當重要的。
客戶不知道他們會獲得哪些實現。

  • 他們必須尊重規約,但也有自由改變他們如何使用實現而不用擔憂會忽然中斷。

當S2比S1強時,它在此圖中定義了一個較小的區域。
較弱的規約定義了一個更大的區域。
強化實施者的後置條件意味着他們自由度較低,對產出的要求更強。
弱化前提意味着:實現必須處理先前被規約排除的新輸入。

(3)設計好的規約
規約的質量

什麼是一個好方法? 設計方法意味着主要編寫一個規約。
關於規約的形式:它顯然應該簡潔,清晰,結構良好,以便閱讀。
然而,規約的內容很難規定。 沒有一個可靠的規則,但有一些有用的指導方針。

規約應該是連貫的(內聚的)

該規約不該該有不少不一樣的狀況。 冗長的參數列表,深層嵌套的if語句和布爾型標誌都是麻煩的跡象。
除了可怕地使用全局變量和打印而不是返回以外,規約不是一致的 - 它執行兩個不一樣的事情,計算單詞並找出最長的單詞。
調用的結果應該是信息豐富的
若是返回null,則沒法肯定密鑰是否先前未綁定,或者其實是否綁定爲null。這不是一個很好的設計,由於返回值是無用的,除非您肯定沒有插入null。

規約應該足夠強大

規約應給予客戶在通常狀況下足夠強大的保證 - 它須要知足其基本要求。 - 在規定特殊狀況時,咱們必須格外當心,確保它們不會破壞原本是有用的方法。例如,對於一個不合理的論證拋出異常,但容許任意的突變是沒有意義的,由於客戶端將沒法肯定實際發生了什麼樣的突變。

規約也應該足夠薄弱

這是一個很差的規約。

  • 它缺乏重要的細節:打開閱讀或寫做文件? 它是否已經存在或被建立?
  • 它太強大了,由於它沒法保證打開文件。 它運行的過程可能缺乏打開文件的權限,或者文件系統可能存在一些超出程序控制範圍的問題。相反,說明書應該說更弱一些:它試圖打開一個文件,若是成功,文件具備某些屬性。

規約應該使用抽象類型

用抽象類型編寫咱們的規約爲客戶和實現者提供了更多的自由。
在Java中,這一般意味着使用接口類型,如Map或Reader,而不是像HashMap或FileReader這樣的特定實現類型。

  • 像列表或集合這樣的抽象概念
  • 特定的實現像ArrayList或HashSet。

這強制客戶端傳入一個ArrayList,並強制實現返回一個ArrayList,即便可能存在他們但願使用的替代List實現。

先決條件仍是後置條件?

是否使用前提條件,若是是,則在繼續以前,方法代碼是否應該嘗試確保先決條件已知足?
對於程序員:

  • 前提條件最多見的用法是要求提供一個屬性,由於該方法檢查該屬性會很困難或昂貴。

若是檢查一個條件會使方法變得難以接受,那麼一般須要一個先決條件。

對用戶而言:

  • 一個不平凡的先決條件會給客戶帶來不便,由於他們必須確保他們不會以不良狀態調用該方法(違反前提條件); 若是他們這樣作,沒有可預測的方法來從錯誤中恢復。

因此方法的用戶不喜歡先決條件。

  • 所以,Java API類傾向於指定(做爲後置條件),當參數不合適時,它們會拋出未經檢查的異常。
  • 這使得在調用者代碼中找到致使傳遞錯誤參數的錯誤或不正確的假設更容易。
  • 一般狀況下,儘量靠近錯誤的地點快速失敗,而不是讓糟糕的價值觀經過遠離其原始緣由的程序傳播。

關鍵因素是檢查的費用(編寫和執行代碼)以及方法的範圍。

若是隻在類本地調用,則能夠經過仔細檢查調用該方法的全部類來解決前提條件。
若是該方法是公開的,而且被其餘開發人員使用,那麼使用前提條件將不太明智。 相反,像Java API類同樣,您應該拋出一個異常。

總結
規約做爲程序實現者與其客戶之間的關鍵防火牆。
它使得單獨的開發成爲可能:客戶端能夠自由地編寫使用該過程的代碼,而無需查看其源代碼,而且實現者能夠自由地編寫實現該過程的代碼而不知道它將如何使用。

減小錯誤保證安全

  • 一個好的規約清楚地記錄了客戶和實施者依賴的相互假設。錯誤一般來自界面上的分歧,而且規約的存在會下降這一點。
  • 在你的規約中使用機器檢查的語言特性,好比靜態類型和異常,而不只僅是一我的類可讀的評論,能夠更多地減小錯誤。容易理解
  • 一個簡短的規約比實現自己更容易理解,而且使其餘人沒必要閱讀代碼。

準備好改變

  • 規約在代碼的不一樣部分之間創建契約,容許這些部分獨立更改,只要它們繼續知足合同的要求。

聲明性規約在實踐中是最有用的。
先決條件(削弱了規約)使客戶的生活更加艱難,但明智地應用它們是軟件設計師的重要工具,容許實施者作出必要的假設。

減小錯誤保證安全

  • 沒有規約,即便是咱們程序中任何部分的細微變化,均可能成爲敲打整個事情的尖端多米諾骨牌。
  • 良好的結構,一致的規約最大限度地減小了誤解,並最大限度地提升了咱們在靜態檢查,謹慎推理,測試和代碼審查的幫助下編寫正確代碼的能力。

容易理解

  • 寫得很好的聲明性規約意味着客戶端沒必要閱讀或理解代碼。

準備好改變

  • 適當的規約賦予實現者自由,適當的強壯規約賦予客戶自由。
  • 咱們甚至能夠本身改變規約,而沒必要從新審視每一個地方的使用狀況,只要咱們只是增強它們:削弱先決條件並增強後置條件。
相關文章
相關標籤/搜索