我能召喚遙遠的精靈。
那又怎麼樣,我也可以,誰都可以,問題是你真的召喚的時候,它們會來嗎?
莎士比亞,《亨利四世》,第一部分
I can call spirits from the vasty deep.
Why, so can I, or so can any man; but will they come when you do call for them?
SHAKESPEARE, KING HENRY IV, Part I
和古老的神話裡一樣,現代神話裡也總有一些愛吹噓的人:“我可以編寫控制航空貨運、
攔截彈道飛彈、管理銀行賬戶、控制生産線的系統。”對這些人,回答很簡單,“我也可以,
任何人都可以,但是其他人成功了嗎?”
如何開發一個可以運作的系統?如何測試系統?如何将經過測試的一系列構件內建到
已測試過、可以依賴的系統?對這些問題,我們以前或多或少地提到了一些方法,現在就來
更加系統地考慮一下。
剔除 bug 的設計
防範 bug 的定義。系統各個組成部分的開發者都會做出一些假設,而這些假設之間的
不比對,是大多數緻命和難以察覺的 bug 的主要來源。第 4、5、6 章所讨論的擷取概念完整
性的途徑,就是直接面對這些問題。簡言之,産品的概念完整性在使它易于使用的同時,也
使開發更容易進行以及 bug 更不容易産生。
上述方法所意味的詳盡體系結構設計正是出于這個目的。Bell 實驗室安全監控系統項
目的 V.A.Vyssotsky 提出,“關鍵的工作是産品定義。許許多多的失敗完全源于那些産品未
1
精确定義的地方。 ”細緻的功能定義、詳細的規格說明、規範化的功能描述說明以及這些
方法的實施,大大減少了系統中必須查找的 bug 數量。
測試規格說明。在編寫任何代碼之前,規格說明必須送出給測試小組,以詳細地檢查
說明的完整性和明确性。如同 Vyssotsky 所述,開發人員自己不會完成這項工作:“他們不
會告訴你他們不懂。相反,他們樂于自己摸索出解決問題和澄清疑惑的辦法。”
自頂向下的設計。在 1971 年的一篇論文中,Niklaus Wirth 把一種被很多最優秀的編
2
程人員所使用的設計流程 形式化。盡管他的理念是為了程式設計,同樣也完全适用于複雜
系統的軟體開發設計。他将程式開發劃分成體系結構設計、設計實作和實體編碼實作,每個
步驟可以使用自頂向下的方法很好地實作。
簡言之,Wirth 的流程将設計看成一系列精化步驟。開始是勾畫出能得到主要結果的,
但比較粗略的任務定義和大概的解決方案。然後,對該定義和方案進行細緻的檢查,以判斷
結果與期望之間的差距。同時,将上述步驟的解決方案,在更細的步驟中進行分解,每一項
任務定義的精化變成了解決方案中算法的精化,後者還可能伴随着資料表達方式的精化。
在這個過程中,當識别出解決方案或者資料的子產品時,對這些子產品的進一步細化可以
和其他的工作獨立,而子產品的大小程度決定了程式的适用性和可變化的程度。
Wirth 主張在每個步驟中,盡可能使用級别較高的表達方法來表現概念和隐藏細節,除
非有必要進行進一步的細化。
好的自頂向下設計從幾個方面避免了 bug。首先,清晰的結構和表達方式更容易對需求
和子產品功能進行精确的描述。其次,子產品分割和子產品獨立性避免了系統級的 bug。另外,細
節的隐藏使結構上的缺陷更加容易識别。第四,設計在每個精化步驟的層次上是可以測試的,
是以測試可以盡早開始,并且每個步驟的重點可以放在合适的級别上。
當遇到一些意想不到的問題時,按部就班的流程并不意味着步驟不能反過來,直到推
翻頂層設計,重新開始整個過程。實際上,這種情況經常發生。至少,它讓我們更加清楚在
什麼時候和為什麼抛棄了某個臃腫的設計,并重新開始。一些糟糕的系統往往就是試圖挽救
一個基礎很差的設計,而對它添加了很多表面裝飾般的更新檔。自頂向下的方法減少了這樣的
企圖。
我确信在十年内,自頂向下進行設計将會是最重要的新型形式化軟體開發方法。
3
結構化程式設計。另外一系列減少 bug 數量的新方法很大程度上來自 Dijkstra 。Bohm 和
Jacopini 的為其提供了理論證明 。
基本上,該方法所設計程式的控制結構,僅包含語句形式的循環結構,例如 DO WHILE,
以及 IF...THEN...ELSE 的條件判斷結構,而具體的條件部分在 IF...THEN...ELSE 後的花括
号中描述。Bohm 和 Jacopini 展示了這些結構在理論上是可以證明的。而 Dijkstra 認為另
外一種方法,即通過 GO TO 不加限制的分支跳轉,會産生導緻自身邏輯錯誤的結構。
這種方法的基本理念非常優秀,但仍有人提出了一些反面的意見。一些附加的控制結
構非常有效,例如,在多個條件下的多路分支(CASE、SWITCH 語句),異常跳轉等(GO TO
ABNORMAL END)。此外,關于完全避免 GO TO 語句的說法顯得有些教條主義,而且似乎有些
吹毛求疵。
關鍵的地方和建構無 bug 程式的核心,是把系統的結構作為控制結構來考慮,而不是
獨立的跳轉語句。這種思考方法是我們在程式設計發展史上向前邁出的一大步。
構件單元調試
程式調試過程在過去的二十年中有過很多反複,甚至在某些方面,它們又回到了出發
的起點。整個調試過程有四個步驟,跟随這個過程來檢驗每個步驟各自的動機是一件很有趣
的事情。
本機調試。早期的機器的輸入和輸出裝置很差,延遲也很長。典型的情況是,機器采
用紙帶或者錄音帶的方式來讀寫,采用離線裝置來完成錄音帶的準備和列印工作。這使得錄音帶輸
入/輸出對于調試是不可忍受的。是以,在一次機器互動會話中會盡可能多地包含試驗性操
作。
在那種情況下,程式員仔細地設計他的調試過程——計劃停止的地點,檢驗記憶體的位
置,需要檢查的東西以及如果沒有預期結果時的對策。花費在編寫調試程式上的時間,可能
是程式編制時間的一半。
這個步驟的“重大罪過”是在沒有把程式劃分成測試段,并對執行終止位置進行計劃
的前提下,粗暴地按下“開始(START)”。
記憶體轉儲。本機調試非常有效。在兩小時的互動過程中可能會發現一打問題,但是計
算機的資源非常匮乏,成本很高。想象一下計算機時間的浪費,那實在是一件可怕的事情。
是以,當使用線上高速列印機時,測試技術發生了變化。某人持續地運作程式,直到
某個檢測失敗,這時所有的記憶體都被轉儲。接着,他将開始艱苦的桌面工作,考慮每個記憶體
位置的内容。桌面工作的時間和本機調試并沒有太大的不同,但它的方式比以前更為含混,
并且發生在測試執行之後。特定使用者調試用的時間更長,因為測試依賴于批處理的周期。總
之,整個過程的設計是為了減少計算機的使用時間,進而盡可能滿足更多的使用者。
快照。采用記憶體轉儲技術的機器往往配有 2000~4000 個字(word 雙位元組),或者8K~
16K 位元組的記憶體。但是,随着記憶體的規模不斷增長,對整個記憶體都進行轉儲變得不大可能。
是以,人們開發了有選擇的轉儲、選擇性跟蹤和将快照插入程式的技術。OS/360 TESTRAN
允許将快照插入程式,無需重新彙編和編譯,它是快照技術方向的終極産品。
5 6
互動式調試。1959 年,Codd 和他的同僚 以及 Strachey 都發表了關于協助分時調試工
作的論文,提出了一種兼有本機調試方式實時性和批處理調試高效使用率的方法。計算機将
多個程式載入到記憶體中準備運作,被調試的程式和一個隻能由程式控制的終端相關聯,由監
督排程程式控制調試過程。當終端前的程式設計人員停止程式,檢查進展情況或者進行修改時,
監督程式可以運作其他程式,進而保證了機器的使用率。
Codd 的多道程式系統已經開發出來,但是它的重點是通過有效地利用輸入/輸出來提高
吞吐量,并沒有實作互動式的調試。Strachy 的想法不斷得到改進,終于在 1963 年由 MIT
7
的 Corbato 和他的同僚在 7090 的實驗性系統上實作 。這個開發結果導緻了 MULTICS、TSS
和現在其他分時系統的出現。
在最初使用的本機調試方法和現在的互動式調試方法之間,使用者可以感覺到的主要差
異是工具性軟體、排程監控程式和其它相關語言解釋編譯器的出現。而現在,已經可以用高
級語言來程式設計和調試,高效的編輯工具使修改和快照更為容易。
互動式調試擁有和本機調試一樣的操作實時性,但前者并沒有象後者要求的那樣,在
調試過程中要預先進行計劃。在某種程度上,像本機調試那樣的預先計劃顯得并不是很必要,
因為在調試人員停頓和思考時,計算機的時間并沒有被浪費。
不過,Gold 實驗得到一個有趣的結果,這個結果顯示在每次調試會話中,第一次互動
8
取得的工作進展是後續互動的三倍 。這強烈地暗示着,由于缺乏對調試會話的計劃,我們
沒有發掘互動式調試的潛力,原有本機調試技術中那段高效率的時間消失了。
我發現對良好終端系統的正确使用,往往要求每兩小時的終端會話對應于兩小時的桌
面工作。一半時間用于上次會話的清理工作:更新調試日志,把更新後的程式清單加入到項
目檔案夾中,研究和解釋調試中出現的奇怪現象。剩餘一半時間用于準備:為下一次操作設
計詳細的測試,進行計劃的變更和改進。如果沒有這樣的計劃,則很難保持兩個小時的高生
産率;而沒有事後的清理工作,則很難保證後續終端會話的系統化和持續推進。
9
測試用例。關于實際調試過程和測試用例的設計,Grunberger 提出了特别好的對策
10,11
在其他的文章中,也有較為簡便的方法 。
系統內建調試
軟體系統開發過程中出乎意料的困難部分是系統內建測試。前面我已經讨論了一些困
難産生和困難不确定的原因。其中需要再次确認的兩件事是:系統調試花費的時間會比預料
的更長,需要一種完備系統化和可計劃的方法來降低它的困難程度。下面來看看這樣的方法
12
所包括的内容 。
使用經過調試的構件單元。盡管并不是普遍的實際情況——不過通常的看法是——系
統內建調試要求在每個部分都能正常運作之後開始。
實際工作中,存在着與上面看法不同的兩種情況。一種是“合在一起嘗試”的方法,
這種方法似乎是基于這樣的觀點:除了構件單元上的 bug之外,還存在系統 bug(如接口)
越早将各個部分合攏,系統 bug 出現得越早。另一種觀念則沒有這麼複雜:使用系統的各個
部分進行互相測試,避免了大量測試輔助平台的搭建工作。這兩種情況顯然都是合理的,但
經驗顯示它們并不完全正确——使用完好的、經過調試的構件,能比搭建測試平台和進行全
面的構件單元測試節省更多的時間。
更微妙的一種方法是“文檔化的 bug”。它申明構件單元所有的缺陷已經被發現,還沒
有被修複,但已經做好了系統調試的準備。在系統測試期間,依照該理論,測試人員知道這
些缺陷造成的後果,進而可以忽略它們,将注意力集中在新出現的問題上。
但是所有這些良好的願望隻是試圖為結果的偏離尋找一些合理理由。實際上,調試人
員并不了解 bug 引起的所有後果;不過,如果系統比較簡單,系統測試倒不會太困難。另外
對文檔記錄 bug 的修複工作本身會注入未知的問題,接下來的系統測試會令人困惑。
搭建充分的測試平台。這裡所說的輔助測試平台,指的是供調試使用的所有程式和數
據,它們不會整合到最終産品中。測試平台可能會有相當于測試對象一半的代碼量,但這是
合乎情理的。
一種測試輔助的形式是僞構件(dummy component),它僅僅由接口和可能的僞資料或
者一些小的測試用例組成。例如,系統包含某種排序程式,但該程式還未完成,這時其他部
分的測試可以通過僞構件來實作,該構件讀入輸入資料,對資料格式進行校驗,輸出格式良
好、但沒有實際意義的有序資料以供使用。
另一種形式是微縮檔案(miniature file)。很常見的一類 bug 來自對錄音帶和磁盤檔案
格式的錯誤了解。是以,建立一個僅包含典型記錄,但涵蓋全部描述的小型檔案是非常值得
的。
微縮檔案的特例是僞檔案(dummy file),實際上并不常見。不過 OS/360 任務控制語
言提供了這種功能,對于構件單元調試非常有用。
還有一種方式是輔助程式(auxiliary program)。用來測試資料發生器、特殊的列印
13
輸出、交叉引用表分析等,這些都是需要另外開發的專用輔助工具的例子 。
控制變更。對測試期間進行嚴密控制是硬體調試中一項令人印象深刻的技術,它同樣
适用于軟體系統。
首先,必須有人負責。他必須控制和負責各個構件單元的變更或者版本之間的替換。
接着,就像前面所讨論的,必須存在系統的受控拷貝:一個是供構件單元測試使用的
最終鎖定版本;一個是測試版本的拷貝,用來進行缺陷的修複;以及一個安全版本,其他人
員可以在該拷貝上工作,進行各自的程式開發工作,例如修複和擴充自己的子產品和子系統等。
在 System/360 工程模型中,在一大堆正常的黃顔色電線中,常常可以不經意地看到紫
色的電線束。在發現 bug 以後,我們會做兩件事情:設計快速修複電路,并安裝到系統,從
而不會妨礙測試的繼續進行。這些更改過的接線使用紫色電線,看上去就像伸着一個受了傷
的大拇指。我們需要把更改記錄到日志中,同時,還要準備一份正式的變更文檔,并啟動設
計自動化流程。最後,在電路圖或者黃色線路中會實作該設計的調整——更新相應的電路圖
和接線表,以及開發一個新的電路闆。現在,實體模型和電路圖重新吻合了,紫色的線束也
就不再需要了。
軟體開發也需要用到“紫色線束”的手法。對于最後成為産品的程式代碼,它更迫切
地需要進行嚴密控制和深層次的關注。上述技巧的關鍵因素是對變更和差異的記載,即在一
個日志中記錄所有的變更,而在源代碼中顯著标記快速更新檔和正式修改之間的差別,正式修
改是完備并經過測試的,而且需要文檔化。
一次添加一個構件。這樣做的好處同樣是顯而易見的,但是樂觀主義和惰性常常誘使
我們破壞這個規則。因為離散構件的添加需要調試僞程式和其他測試平台,有很多工作要做
畢竟,可能我們不需要這些額外工作?可能不會出現什麼 bug?
不!拒絕誘惑!這正是系統測試所關注的方面。我們必須假設系統中存在着許多錯誤
并需要計劃一個有序的過程把它們找出來。
注意必須擁有完整的測試用例,在添加了新構件之後,用它們來測試子系統。因為那
些原來可以在子系統上成功運作的用例,必須在現有系統上重新運作,對系統進行回歸測試
階段(量子)化、定期變更。随着項目的推進,系統構件的開發者會不時出現在我們
面前,帶着他們工作的最新版本——更快、更卓越、更完整,或者公認 bug 更少的版本。将
使用中的構件替換成新版本,仍然需要進行和構件添加一樣的系統化測試流程。這個時候通
常已經具備了更完整有效的測試用例,是以測試時間往往會減少很多。
項目中,其他開發團隊會使用經過測試的最新內建系統,作為調試自己程式的平台。
測試平台的修改,會阻礙他們的工作。當然,這是必須的。但是,變更必須被階段化,并且
定期釋出。這樣,每個使用者擁有穩定的生産周期,其中穿插着測試平台的改變。這種方法比
持續波動所造成的混亂無序要好一些。
14
Lehman和 Belady 出示了證據,階段(量子)要麼很大,間隔很寬;要麼小而頻繁 。
根據他們的模型,小而頻繁的階段很容易變得不穩定,我的經驗也同樣證明了這一點——因
此我決不會在實踐中冒險采用後一種政策。
量子(階段)化變更方法非常優美地容納了紫色線束技術:直到下一次系統構件的定
期釋出之前,都一直使用快速更新檔;而在目前的釋出中,把已經通過測試并進行了文檔化的
修補措施整合到系統平台。<轉載>