契約思想的一個反面案例

來源:互聯網
上載者:User

剛剛發表了《什麼是契約》一文,突然發現自己通篇都在寫理論,沒有執行個體來證明。所以趕快補充一個反面案例——C++ IOStream。說是反面,不是因為IOStream庫設計得不精彩(恰恰相反,你很難找到比IOStream設計更為精彩的C++庫了),而是想展示一下,在沒有契約概念的思想體系裡,組件設計將為權責不清的錯誤處理付出多大的代價。

大家知道,C++ IOStream庫非常經典,最先起源於Bjarne Stroustrup的Stream庫,之後經過Jerry Schwartz、Martin Carroll、Andy Koenig等人的改進,成為IOStream庫,並被併入Bell實驗室發行的USD C++庫中,廣為傳播。後來USD庫逐漸消亡了,而IOStream由於獲得廣泛應用,得以倖免,並以新的形式被置於標準庫中。

對於錯誤處理,當IOStream庫誕生的時候(大約1985-1987),C++還沒有異常機制。因此,Jerry Schwartz發明了這樣一套錯誤處理機制:

例1:經典的IOStream錯誤處理:

  ifstream ifs("filename.txt", ios::in);
  if (!ifs) {  // 這裡實施了向void*轉型的操作
    // 檔案開啟失敗,實施錯誤處理
  }

先測試檔案是否開啟,再實施具體操作,這是經典IOStream庫的一個慣用法(idiom)。

我們現在設想使用者沒有很好地執行這個idiom:

  int val;
  ifstream ifs("filename.txt", ios::in);
  ifs >> val; 

如果filename.txt開啟失敗,會發生什麼情況?

如果哪位還有當年的Borland C++ 3.1,可以試著測試一下。我估計是什麼也不發生,或者說,程式處於極端危險的“undefined behavior”狀態。

這種情況對C++庫開發人員來說是不能接受的。因此,儘管問題的出現是由於使用者的錯誤(他們沒有正確地測試流狀態),但是由於非契約思想體系下的權責不清,IOStream庫的開發人員開始追求足以應對使用者錯誤的組件開發技術。由此,IOStream開始在一個方向上被拖入了複雜性的泥潭之中。

我們看看標準庫中的對付這種情況採用什麼辦法。標準IOStream有一個成員函數叫做exceptions(),專門用來協助程式員切換異常模式。預設情況下,異常觸發並沒有開啟,所以情形跟經典IOStream庫相同。如果你在操作IOStream之前如下調用:

strm.exceptions(std::ios::eofbit | std::ios::failbit |
                std::ios::badbit);
則當流不處於good狀態時,執行類似 strm >> val;這樣的操作時,將會拋出異常。

這樣做看起來不錯,是嗎?

我覺得不是。請恕我C++標準的異議,這是我第一次正式對C++標準中的設計提出異議。

這種設計帶來的缺點,首先是複雜。在Nicolai Josuttis的The C++ Standard Library中,對這個機制整整用了5頁紙來解釋,還意猶未盡。複雜的設計必然帶來複雜的使用規則,而面對複雜的使用規則,使用者是可以投票的,那就是你做你的,我不用!讀這篇文章的人,誰在實際項目中使用過exceptions()?事實上,我個人是害怕exception甚於害怕undefined behavior。

而對於使用者來說,你可以不用,卻不得不為對它付出運行效能和空間的代價。諸位有興趣,不妨追蹤一個IOStream功能的實現,看看為了支援這個異常,IOStream庫的設計這耗費了多大的心力,而你的CPU又為此耗費了多少clock。

缺點之二,是異常本身的問題——即使你抓到了異常,又當如何?程式可能已經完全離開了發生異常時的執行環境,也許你連異常為什麼發生都搞不清楚,談何處理?也無非就是通知使用者一聲:“我完了,因為一個異常發生在XXXXXXXX處,你要報告的話給我發Email吧。” 是啊,你還能做什麼呢?

我們試著用契約觀點來分析這一狀況,如果說“先測試,再使用”在傳統上是一個idiom,那麼在contract思想裡上升為一個契約。對於C++來說,應該將“流處於good狀態”作為一個契約,在每一個成員函數裡進行檢查。甚至你還可以設定一個調試期標誌,專門用來核查使用者是否檢查過流狀態。在必要的操作進行之前,你可以先用斷言檢查使用者是否檢查過流狀態,滿足了契約。這樣一來,在契約之下,使用者將被迫以正確的方式使用組件,從而大幅度簡化組件開發的複雜度。

再來考慮異常。如果真正發生了異常,在Eiffel中提供了retry機制。Bjarne Stroustrup說過,retry可以做到,但是往往沒有意義。為什麼沒有意義呢?因為C++中沒有契約的思想,異常的產生可能根本就是程式員的bug。在這種情況下,無論retry多少次,結果都是一樣的糟。可是在Eiffel裡情形不同。如果各方面對於契約都做到很好的遵循,那麼真正發生異常的時候,我們大可以比較有把握的說,這可能是一個很偶然的事件導致的。比如說網路環境下,另一個使用者在那一瞬間突然對檔案實施了一個操作,或者硬體的一次偶然異常。對於這種情況,“再試一次”成了合情合理的選擇。我們很可能將異常扼殺在搖籃之中,從而不給上層模組帶來任何影響。

誰說契約思想不偉大呢?

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.