1980年 ACM 圖靈獎演講 於1980年10月27日在田納西州納許維爾 ACM '80 會議上發表
1980年 ACM 圖靈獎由頒獎委員會主席 Walter Carlson 在田納西州納許維爾舉行的 ACM 年度會議上頒發給英國牛津大學計算學教授 Charles Antony Richard Hoare。
Hoare 教授因其對程式語言定義和設計的基礎性貢獻而被通用技術成就獎委員會選中。他的工作以不同尋常的洞察力、原創性、優雅性和影響力相結合為特點。他最為人所知的是通過俗稱公理語義學(axiomatic semantics)的技術,對程式語言進行公理化定義的工作。他開發了巧妙的演算法,例如 Quicksort,並在科學程式語言中負責發明和推廣先進的資料結構技術。他還通過研究 monitors 為作業系統做出了重要貢獻。他最近的工作是關於通訊序列行程(communicating sequential processes)。
C.A.R. Hoare
在1977年被任命為牛津大學教授之前,Hoare 教授於1968年至1977年在愛爾蘭貝爾法斯特女王大學擔任計算機科學教授,並於1973年在 Stanford University 擔任訪問教授。從1960年至1968年,他在英國 Elliott Brothers, Ltd. 擔任過多個職位。
Hoare 教授著作豐富,並在多本世界領先的計算機科學期刊擔任編輯委員會成員。1973年,他獲得 ACM 程式系統與語言論文獎。1978年,Hoare 教授成為 British Computer Society 的傑出會士,並於1979年獲得 University of Southern California 榮譽科學博士學位。
圖靈獎是 Association for Computing Machinery 為紀念英國數學家 Dr. A. M. Turing 而每年頒發的最高技術貢獻獎項,圖靈博士為計算機科學做出了許多重要貢獻。
皇帝的新衣 Charles Antony Richard Hoare Oxford University, England
作者回顧了他在電腦程式語言的實現、設計和標準化方面的經驗,並對未來發出了警告。 關鍵詞:程式語言,程式語言歷史,未來的教訓 CR Categories:1.2, 2.11, 4.2
允許免費複製本資料的全部或部分內容,前提是複製不用於直接商業利益,並註明 ACM 版權聲明、出版物的標題及日期,以及複製經 Association for Computing Machinery 許可。否則,或為重新出版目的而複製,需要付費和/或特定許可。
作者現地址:C. A. R. Hoare, 45 Banbury Road, Oxford OX2 6PE, England. © 1981 ACM 0001-0782/81/0200-0075 $00.75.
引言
我在本次演講中的第一件也是最愉快的事情,就是表達我對 Association for Computing Machinery 的深切謝意,感謝他們授予我的巨大榮譽,以及給予我這次機會就我自己選擇的題目發表演講。這是多麼困難的選擇啊!我的科學成就,已經被這個獎項充分肯定,也已在科學文獻中得到了充分的描述。與其重複我專業領域中晦澀的技術細節,我寧願輕鬆地談談我自己,我的個人經歷,我的希望與恐懼,我微不足道的成功,以及我並不那麼微不足道的失敗。我從失敗中學到的東西,遠比科學文章冷冰冰的文字所能揭示的更多,而現在我也希望你們能從中學習。此外,事後聽起來,失敗會更有趣;當時可一點都不好笑。
在 Elliott 的經歷與語言設計原則
我的故事始於1960年8月,當時我成為了一家小型電腦製造商的程式設計師,這家公司是 Elliott Brothers (London) Ltd. 的一個部門,在接下來的八年裡,我在這裡接受了計算機科學的啟蒙教育。我的第一個任務是為新的 Elliott 803 電腦實現一個函式庫子程式,用於 Shell 剛剛發明的一種新的快速內部排序方法。我非常享受在當時簡單的十進制位址機器碼中將效率最大化的挑戰。我的上司兼導師 Pat Shackleton 對我完成的程式非常滿意。然後我怯怯地說,我認為我發明了一種排序方法,通常比 SHELLSORT 運行得更快,而且不會佔用太多額外存儲空間。他和我打賭六便士說我沒有。雖然我的方法很難解釋,但他最終承認我贏了賭注。
我又寫了幾個編寫緊湊的函式庫子程式,但六個月後,我接到了一個更重要的任務——為公司下一代電腦 Elliott 503 設計一種新的高級程式語言。Elliott 503 將擁有與現有 803 相同的指令集,但速度要快六十倍。儘管我受過古典語言教育,這項任務對我來說比今天承擔它的人資格還要欠缺。幸運的是,我手頭有份 Report on the International Algorithmic Language ALGOL 60 的副本。當然,這種語言對我們的客戶來說顯然太複雜了。既然我們的銷售人員都無法理解那些 begins 和 ends,他們怎麼可能理解呢?
1961年復活節前後,在英國布萊頓舉辦了一場關於 ALGOL 60 的課程,講師包括 Peter Naur、Edsger W. Dijkstra 和 Peter Landin。我與我在語言專案中的同事 Jill Pym、我們的部門技術經理 Roger Cook 和銷售經理 Paul King 一起參加了這門課程。正是在那裡,我第一次學到了遞歸程序,並看到了如何對我之前難以解釋的排序方法進行程式設計。正是在那裡,我寫下了那個程序,我厚顏無恥地將其命名為 QUICKSORT,我的計算機科學家生涯就奠基於此。必須向 ALGOL 60 的設計者們的天才致敬,他們在語言中包含了遞歸,使我能夠如此優雅地向世界描述我的發明。我一直認為,程式語言設計的最高目標就是使好的想法能夠優雅地表達出來。
在布萊頓的 ALGOL 課程之後,Roger Cook 開車載著我和我的同事返回倫敦時,突然問道:「我們為什麼不直接實現 ALGOL 60,而不是設計一門新語言呢?」我們所有人都立即同意——回想起來,這對我來說是一個非常幸運的決定。但我們知道,當時我們還沒有實現整個語言的技能和經驗,所以我被委託設計一個適度的子集。在那個設計中,我採用了一些基本的原則,我相信這些原則在今天與那時一樣有效。
(1) 第一個原則是安全性:這個原則是,每個在語法上不正確的程式都應該被編譯器拒絕,而每個在語法上正確的程式都應該產生一個在原始程式語言程式本身而言是可預測且可理解的結果或錯誤訊息。因此,永遠不應該需要核心轉儲(core dumps)。任何原始程式語言程式在編譯時或運行時導致計算機失控,在邏輯上都是不可能的。這個原則的一個結果是,每當對有下標的變數的每個下標進行每次檢查時,運行時都會對照陣列聲明的上下界進行檢查。很多年後,我們問客戶是否希望我們提供一個選項來關閉這些檢查,以便在生產運行中提高效率。他們一致強烈要求我們不要這樣做——他們已經知道在生產運行中下標錯誤發生的頻率有多高,而未能檢測到這些錯誤可能會帶來災難性的後果。我帶著恐懼和驚慌注意到,即使在1980年,語言設計者和使用者也沒有學到這個教訓。在任何一個體面的工程分支中,未能遵守這種基本預防措施早已應該違法。
(2) 設計實現的第二個原則是編譯器產生的目標程式碼要簡潔,以及運行時工作資料要緊湊。這有明確的原因:任何電腦的主記憶體大小都是有限的,擴展它需要延遲和費用。一個程式超過限制,即使只有一個字,也無法運行,特別是因為我們的許多客戶不打算購買後備存儲(backing stores)。這個目標程式碼緊湊性的原則在今天更加有效,如今處理器相對於它們可以定址的主記憶體容量來說,價格極其低廉,而後備存儲則相對更昂貴,而且慢了許多個數量級。如果在實現中謹慎處理,可用的硬體保持比特定應用程式看起來必需的更強大,應用程式程式設計師幾乎總是能夠利用額外的容量來提高程式的品質、簡潔性、健壯性和可靠性。
(3) 我們設計的第三個原則是,程序和函數的進入和退出約定應該像編寫緊湊的機器碼子程式一樣緊湊和高效。我認為,程序是高級語言中最強大的特性之一,因為它們既簡化了程式設計任務,又縮短了目標程式碼。因此,不應該阻礙它們頻繁使用。
(4) 第四個原則是編譯器應該只使用一次遍歷(single pass)。編譯器被構造為一個由相互遞歸的程序組成的集合,每個程序都能夠分析和翻譯語言的一個主要語法單元——一個語句、一個表達式、一個宣告等等。它按照 ALGOL 60 的概念進行設計和文檔編寫,然後使用顯式堆疊編譯為十進制機器碼以實現遞歸。如果沒有當時極具爭議的 ALGOL 60 的遞歸概念,我們根本無法編寫這個編譯器。
我仍然推薦單遍自上而下遞歸下降(single-pass top-down recursive descent),既作為實現方法,也作為程式語言的設計原則。首先,我們當然希望程式能夠被人閱讀,而人更喜歡一次性閱讀完畢。其次,對於分時或個人電腦系統的使用者來說,鍵入程式(或修改)到開始運行程式之間的間隔是完全無效的。通過高速的單遍編譯器可以將這個間隔最小化。最後,按照其輸入語言的語法來構造編譯器,對於確保其正確性做出了巨大貢獻。除非我們對此有絕對的信心,否則我們對任何程式的結果都不會有信心。
為了遵守這四個原則,我選擇了一個相當小的 ALGOL 60 子集。隨著設計和實現的進展,我逐漸發現了在不損害任何原則的情況下放鬆限制的方法。因此,最終我們能夠實現幾乎整個語言的全部能力,甚至包括遞歸,儘管一些特性被移除,另一些被限制了。
1963年中期,主要是由於 Jill Pym 和 Jeff Hillmore 的工作,我們的編譯器第一個版本交付了。幾個月後,我們開始懷疑是否有人使用這種語言,或者注意到我們偶爾發布的包含了改進的操作方法的版本。只有當客戶抱怨時,他們才會聯繫我們,而且他們中有許多人沒有抱怨。我們的客戶現在已經轉向更現代的電腦和更流行的語言,但許多人告訴我他們對 Elliott ALGOL System 的美好回憶,這種美好不僅僅是出於懷舊,更是因為那個早期簡單的 ALGOL System 的高效、可靠和便利。
IFIP ALGOL 工作組的經驗
由於在 ALGOL 方面的工作,我於1962年8月被邀請加入 IFIP 的新工作組 2.1,該工作組負責 ALGOL 的維護和發展。該工作組的第一個主要任務是設計一個語言子集,以移除其中一些不太成功的特性。即使在那個年代,即使是這樣一個簡單的語言,我們也認識到子集可以是對原始語言的改進。我非常歡迎有機會與許多原來的語言設計者見面並聆聽他們的智慧。我對他們討論的熱烈甚至惡毒感到震驚和沮喪。顯然,ALGOL 60 的原始設計並非在對真理進行冷靜探求的精神下進行的,而語言的品質讓我原本以為是如此。
為了從設計子集這個枯燥且充滿爭議的任務中解脫出來,工作組安排了一個下午來討論應該納入下一代語言設計中的特性。每個成員都被邀請提出他認為最重要的改進建議。1963年10月11日,我的建議是轉達客戶的要求,放寬 ALGOL 60 中強制聲明變數名稱的規則,並採用一些合理的預設約定,例如 FORTRAN 的做法。我對這個看似無辜的建議遭到禮貌但堅定的拒絕感到驚訝:有人指出,ALGOL 60 的冗餘是防止程式設計和編碼錯誤的最佳保護,這些錯誤在運行中的程式中可能非常昂貴才能檢測到,而未能檢測到則更加昂貴。關於 Mariner 火星探測器因 FORTRAN 中缺乏強制聲明而丟失的故事,當時還沒有出版。
我最終被說服,需要設計程式符號,以便最大限度地增加無法發生或即使發生也能在編譯時可靠檢測到的錯誤數量。也許這會使程式文本變長。沒關係!如果你的神仙教母對你的程式揮動魔杖,移除所有錯誤,只要求你將整個程式寫出來並鍵入三次,你會不會很高興?縮短程式的方法是使用程序,而不是省略重要的宣告資訊。
在關於新 ALGOL 發展的其他提案中,有人建議將 ALGOL 60 的 switch 聲明替換為一個更普遍的特性,即一個標號變數組,並且程式應該能夠通過賦值來改變這些變數的值。我非常反對這個想法,它類似於 FORTRAN 的 assigned GO TO,因為我在實現即使是 ALGOL 60 的簡單標號和 switches 時,也發現了許多棘手的問題。我在新特性中看到了更多的問題,包括在離開一個塊後又跳回其中的問題。我也開始懷疑,大量使用標號的程式更難理解和糾正,而對標號變數賦予新值的程式將會更加困難。
我想到了,取代 ALGOL 60 switch 的恰當符號應該基於 ALGOL 60 的條件表達式(conditional expression),它根據布林表達式的值在兩個備選動作之間進行選擇。因此,我建議使用「case expression」的符號,它根據整數表達式的值在任意數量的備選方案之間進行選擇。這是我的第二個語言設計提案。我對它仍然非常自豪,因為它對實現者、程式設計師或程式閱讀者來說,基本上都沒有帶來任何問題。現在,在超過十五年之後,國際標準化一種包含這種符號的語言有了前景——與其他工程分支相比,這是一個非常短的間隔。
Elliott 503 Mark II 軟體專案的失敗與經驗
回到我在 Elliott 的工作。在我們的 ALGOL 編譯器意外成功之後,我們開始考慮一個更雄心勃勃的項目:為具有讀卡機、行式印表機、磁帶甚至核心後備存儲(比主存便宜一半、大一倍,但慢十五倍)的更大配置 503 電腦提供一系列作業系統軟體。這將被稱為 Elliott 503 Mark II 軟體系統。
它包括: (1) 一個用於符號組合語言的組合器,所有其他軟體都將用這種語言編寫。 (2) 一個用於自動管理程式碼和資料覆蓋的方案,無論是從磁帶還是從核心後備存儲。所有其他軟體都將使用這個方案。 (3) 一個用於自動緩衝任何可用週邊設備上的所有輸入和輸出的方案——同樣,所有其他軟體都將使用這個方案。 (4) 一個磁帶上的檔案系統,具有編輯和作業控制功能。 (5) 一個全新的 ALGOL 60 實現,它移除了我們在第一個實現中強加的所有非標準限制。 (6) 一個當時的 FORTRAN 編譯器。
我撰寫了描述相關概念和功能的文檔,並將它們發送給現有和潛在的客戶。工作開始時有一個十五人的程式設計師團隊,交付截止日期定在十八個月後的1965年3月。在啟動 Mark II 軟體設計後,我突然被晉升到令人頭暈目眩的助理總工程師職位,負責公司產品(包括硬體和軟體)的先進開發和設計。儘管我仍然在管理上負責 503 Mark II 軟體,但我對公司新產品的關注更多,幾乎沒有注意到它的交付截止日期過去了。
程式設計師們修改了他們的實現計劃,新的交付日期定在三個月後,即1965年6月。不用說,那天也毫無意外地過去了。到這個時候,我們的客戶開始生氣,我的經理們指示我親自負責這個項目。我再次要求高級程式設計師們制定修訂後的計劃,這些計劃再次表明軟體可以在另外三個月內交付。我拼命想相信它,但我就是無法相信。我無視計劃,開始更深入地研究項目。
結果是,我們未能對我們最有限的資源——主存——進行任何整體規劃。每個程式設計師都期望這會自動完成,要麼通過符號組合器,要麼通過自動覆蓋方案。更糟糕的是,我們甚至未能簡單地計算我們自己的軟體佔用的空間,這些軟體已經佔滿了電腦的主存,沒有留下空間供客戶運行他們的程式。硬體位址長度限制禁止增加更多主存。
顯然,軟體的原始規格無法滿足,必須大幅度削減。經驗豐富的程式設計師甚至經理們被從其他項目調回來。我們決定首先專注於交付新的 ALGOL 60 編譯器,經過仔細計算,這需要另外四個月。我向所有相關的程式設計師強調,這不再僅僅是一個預測;這是一個承諾;如果他們發現自己無法履行承諾,他們有個人責任尋找辦法彌補。
程式設計師們對這個挑戰做出了出色的回應。他們日夜工作,確保完成了 ALGOL 編譯器所需的所有軟體項目。令我們高興的是,他們按時達到了計劃的交付日期;這是公司兩年多來生產的第一個主要可工作的軟體項目。
我們的喜悅是短暫的;編譯器無法交付。它的編譯速度只有每秒兩個字符,與現有版本每秒約一千個字符的編譯速度相比,非常不利。我們很快就確定了問題的原因:它在主存和慢十五倍的擴展核心後備存儲之間頻繁交換(thrashing)。很容易做一些簡單的改進,一週內我們就將編譯速度翻了一番——達到每秒四個字符。在接下來的兩週調查和重新編程中,速度再次翻了一番——達到每秒八個字符。我們可以預見,一個月內速度還可以進一步提高;但所需的重新編程工作量正在增加,而其有效性正在降低;還有很長的路要走。通過增加主存大小的替代方案,這種方法在後來的這類失敗中經常被採用,但由於硬體定址限制而被禁止。
沒有退路:整個 Elliott 503 Mark II 軟體項目必須放棄,隨之放棄的是三十多人年的程式設計工作,相當於一個人的整個活躍工作壽命,而我作為設計師和經理,對此負有責任。
我們召集了所有 503 客戶的會議,當時計算部門經理 Roger Cook 向他們解釋說,長久承諾的軟體一個字都不會交付給他們。他採用了一種非常溫和的語氣,確保沒有客戶能夠打斷、在背景中低語,甚至在座位上挪動。我佩服他的冷靜,但無法分享。午餐時,客戶們親切地試圖安慰我。他們很久以前就意識到,按原始規格設計的軟體永遠不可能交付,即使交付了,他們也不知道如何使用其複雜的功能,而且許多這樣的大型項目在交付前就被取消了。回想起來,我相信我們的客戶是幸運的,因為硬體限制保護了他們免受我們軟體設計任意過度行為的影響。在現今,微處理器的使用者受益於類似的保護——但不會持續太久。
當時,我正在閱讀描述新宣布的 OS/360 和一個名為 Multics 的新分時項目的概念和特性的早期文檔。這些項目比我在 503 Mark II 軟體第一版中所想像的要全面、複雜和精巧得多。顯然,IBM 和 MIT 肯定擁有一些成功軟體設計和實現的秘密,而我連猜都猜不到其本質。直到後來,他們自己也意識到他們並沒有這樣的秘密。
所以我仍然不明白我是如何給公司帶來如此巨大的不幸的。當時我堅信我的經理們正計劃解僱我。但是沒有,他們打算施以更嚴厲的懲罰。「好吧,Tony,」他們說。「你把我們帶入了這個困境,現在你得帶我們出去。」「但我不知道怎麼辦,」我抗議道,但他們的回答很簡單。「那麼,你必須自己找到辦法。」他們甚至表示相信我能做到。我沒有分享他們的信心。我曾想辭職。我所有幸運的逃脫中,最幸運的就是沒有辭職。
當然,公司盡一切可能幫助我。他們解除了我對硬體設計的責任,並縮減了我的程式設計團隊規模。我的每個經理都仔細解釋了他自己關於問題出在哪裡的理論,而所有理論都不同。最後,我們母公司的一位最高級經理,一位總經理 Andrew St. Johnston 輕快地走進我的辦公室。我很驚訝他竟然聽說過我。「你知道問題出在哪裡嗎?」他喊道——他總是喊道——「你讓你的程式設計師們做你自己不了解的事情。」我驚訝地盯著他。他顯然與當今的現實脫節了。一個人怎麼可能了解像 Elliott 503 Mark II 軟體系統這樣一個現代軟體產品的全部呢?
我後來意識到他是絕對正確的;他診斷出了問題的真正原因,並播下了後來解決方案的種子。
我當時還有大約四十名程式設計師的團隊,我們需要維持新機器的客戶善意,甚至重新獲得老機器的客戶信心。但是,當我們只知道一件事——我們之前所有的計劃都失敗了——我們實際應該計劃做什麼呢?因此,我於1965年10月22日召集了我們的高級程式設計師們進行了一整天的會議,以便在我們之間徹底討論這個問題。我仍然保留著那次會議的記錄。我們首先列出了客戶最近的主要不滿:產品取消、未按時交付、軟體規模過大,"...與提供的功能用途不符,"程式運行過慢,未能考慮客戶回饋;"更早地關注客戶的一些微小要求,可能比我們最雄心勃勃的計劃成功帶來同樣大的善意回報。"
我們接著列出了我們自己的不滿:程式測試缺乏機器時間,機器時間不可預測,缺乏合適的週邊設備,硬體即使可用也不可靠,程式設計人員分散,缺乏程式鍵入設備,缺乏明確的硬體交付日期,缺乏文檔編寫的技術寫作人員,程式設計組以外缺乏軟體知識,高級經理的干預,"...在未充分了解事情更複雜含義的情況下強行做出決定,"以及在客戶和銷售部門壓力下過於樂觀。
但我們沒有通過這些不滿來為我們的失敗辯解。例如,我們承認程式設計師有責任通過 "...以簡單易懂的形式呈現必要信息" 來教育他們的經理和公司的其他部門。"...希望原始程式規格中的缺陷可以由技術寫作部門的技能彌補..." 是錯誤的;程式的設計和其規格的設計必須由同一個人并行進行,並且它們必須相互作用。規格中缺乏清晰度是它所描述的程式存在缺陷的最確切跡象之一,這兩個錯誤必須在項目啟動之前同時消除。我希望我在1963年聽從了這個建議;我希望我們今天所有人都聽從這個建議。
我那一天(1965年10月)會議記錄中的一部分專門討論了軟體組內部存在的缺陷;這部分內容堪比中國文化大革命中一個修正主義官員最卑微的自我貶低。我們的主要失敗是野心過大。"我們試圖實現的目標顯然已經遠遠超出了我們的能力範圍。" 還存在預測失敗,對程式大小和速度、所需工作量、程式協調和互動的規劃、未能及早警告情況正在惡化。我們在程式變更控制、文檔、與其他部門的聯絡、與我們的管理層以及與客戶的聯絡方面都存在問題。我們未能對個別程式設計師和項目負責人的職責給予清晰和穩定的定義——哦,我需要繼續說下去嗎?令人驚訝的是,一個由高度智能的程式設計師組成的大團隊竟然在如此沒有希望的項目上如此努力、如此長時間地工作。你知道嗎,你不應該相信我們這些聰明的程式設計師。我們可以想出如此好的論點來讓自己和彼此相信完全荒謬的事情。尤其不要相信我們承諾下次會重複先前的成功,只是規模更大、做得更好。
我們對失敗的調查的最後一部分討論了軟體的品質標準。"在最近爭取交付任何軟體的鬥爭中,第一個犧牲品就是對交付軟體品質的考慮。軟體的品質是由許多完全不相容的標準來衡量的,這些標準必須在每個程式的設計和實現中仔細權衡。" 然後我們列出了不少於十七個標準,這些標準已經發表在 Software Practice and Experience 期刊第2卷的特邀社論中。
我們是如何從災難中恢復過來的?首先,我們根據客戶購買的硬體配置的性質和大小將我們的 503 客戶分組——例如,那些擁有磁帶的客戶都歸為一組。我們為每組客戶分配了一個小型程式設計師團隊,並告訴團隊負責人拜訪客戶,了解他們的需求;選擇最容易滿足的要求,並制定計劃(但不做承諾)來實現它。在任何情況下,我們都不會考慮實現和交付時間超過三個月的需求。然後項目負責人必須說服我,客戶的需求是合理的,新功能的設計是恰當的,以及實現計劃和進度是現實的。最重要的是,我不允許做任何我自己不理解的事情。它奏效了!所需的軟體開始按承諾的日期交付。隨著我們的信心和客戶的信心增加,我們能夠承擔滿足稍微更雄心勃勃的需求。一年之內,我們從災難中恢復過來。兩年之內,我們甚至有了一些比較滿意的客戶。
因此,我們憑藉常識和妥協,磕磕碰碰地走向了接近成功。但我不滿意。我不明白為什麼作業系統的設計和實現會比編譯器困難得多。這就是我後來將研究重心放在並行程式設計和有助於清晰構造作業系統的語言結構上的原因——例如 monitors 和 communicating processes。
形式化方法與 ALGOL 語言的演變
在 Elliott 工作期間,我對程式語言的形式化定義技術非常感興趣。當時,Peter Landin 和 Christopher Strachey 提議使用簡單的函數符號來定義程式語言,這種符號規定了每個命令在數學上定義的抽象機器上的效果。我對這個提案並不滿意,因為我覺得這樣的定義必須包含許多相當隨意的表示決策,並且原則上不會比在真實機器上實現語言更簡單。作為替代,我建議將程式語言定義形式化為一組公理,描述用該語言編寫的程式所期望的屬性。我認為仔細制定的公理將給實現留下必要的自由,以便在不同的機器上高效地實現該語言,並使程式設計師能夠證明其程式的正確性。但我不知道如何實際做到這一點。我認為這需要漫長的研究來開發和應用必要的技術,而大學比工業界更適合進行這種研究。因此,我申請了 Belfast 女王大學的計算機科學教職,我在那裡度過了九年快樂而富有成效的時光。1968年10月,當我在貝爾法斯特的新家中整理文件時,我偶然發現了一篇 Bob Floyd 題為「Assigning Meanings to Programs」的晦澀預印本。真是幸運!我終於看到了實現我的研究希望的途徑。因此,我寫了第一篇關於電腦程式設計公理化方法的論文,發表在1969年10月的 Communications of the ACM 上。
最近,我發現程式證明(program proving)斷言方法(assertional method)的早期倡導者正是 Alan Turing 本人。1950年6月24日在劍橋舉行的一次會議上,他做了一個題為「Checking a Large Routine」的簡短演講,非常清晰地解釋了這個想法。「如何檢查一個大型例程,確保它是正確的?為了讓檢查員的任務不那麼困難,程式設計師應該做出許多明確的斷言,這些斷言可以單獨檢查,並且整個程式的正確性很容易由此得出。」
考慮一下檢查加法的類比。如果只給出總和[就像一列數字,下面是答案],檢查員必須一次性檢查全部。但是如果給出了各列的總計[並單獨加上進位],檢查員的工作就容易得多,分解為檢查各個斷言[即每列都正確相加]和少量加法[將進位加到總計中]。這個原則可以應用於檢查大型例程,但我們將通過一個小型例程來說明這種方法,即一個無需使用乘法器計算 n 階乘的例程。不幸的是,沒有一個編碼系統普遍到足以完全呈現這個例程,但流程圖就足夠說明問題了。
這把我帶回了我的演講主題,程式語言的設計。
從1962年8月到1966年10月期間,我參加了 IFIP ALGOL 工作組的每一次會議。在完成了 IFIP ALGOL 子集的工作後,我們開始設計 ALGOL X,這是 ALGOL 60 的預期後繼者。提出了更多關於新功能的建議,1965年5月,Niklaus Wirth 受委託將這些建議整合到一個單一的語言設計中。我對他的設計草案感到高興,它避免了 ALGOL 60 的所有已知缺陷,並包含了幾個新功能,所有這些功能都可以簡單高效地實現,並且安全便捷地使用。
該語言的描述尚未完成。我努力提出了改進建議,我們的許多其他成員也這樣做了。到1965年10月在法國 St. Pierre de Chartreuse 舉行的下一次會議時,我們已經有了一個優秀且現實的語言設計草案,並於1966年6月作為「A Contribution to the Development of ALGOL」發表在 Communications of the ACM 上。它在 IBM 360 上實現,被其許多快樂的使用者稱為 ALGOL W。它不僅是 ALGOL 60 的一個值得的後繼者,甚至是 PASCAL 的一個值得的前身。
在同一次會議上,ALGOL 委員會收到了一份簡短、不完整且相當難懂的文件,描述了一種不同的、更雄心勃勃且對我來說遠不那麼吸引人的語言。當由程式語言領域所有國際知名專家組成的工作組決定擱置我們一直在努力的委託草案,而接受這樣一個沒有吸引力的誘餌時,我感到非常震驚。
這發生在我們對 503 Mark II 軟體項目進行調查僅僅一週之後。我對新設計的模糊性、複雜性和過度雄心發出了絕望的警告,但我的警告沒有被理會。我得出的結論是,構建軟體設計有兩種方法:一種是使其如此簡單,以至於顯然沒有缺陷;另一種是使其如此複雜,以至於沒有明顯的缺陷。
第一種方法困難得多。它需要與發現自然界複雜現象背後的簡單物理定律相同的技能、奉獻、洞察力甚至靈感。它還需要願意接受受物理、邏輯和技術限制的目標,並在衝突目標無法滿足時接受妥協。委員會永遠不會做到這一點,直到為時已晚。
ALGOL 委員會就是如此。顯然,他們偏愛的草案還不完美。因此,承諾在三個月內提交新 ALGOL 語言設計的新的最終草案;它將提交給包括我在內的四人小組進行審查。三個月過去了,沒有任何關於新草案的消息。六個月後,小組在荷蘭開會。我們面前有一份更長更厚的文件,滿是最後一刻才更正的錯誤,描述了一種對我來說同樣沒有吸引力的語言。Niklaus Wirth 和我花了一些時間試圖移除設計和描述中的一些缺陷,但徒勞無功。該語言的完成最終草案承諾在三個月後的下一次 ALGOL 委員會全體會議上提交。
三個月過去了——沒有任何新草案的消息出現。六個月後,在1966年10月,ALGOL 工作組在華沙開會。他們面前有一份更長更厚的文件,滿是最後一刻才更正的錯誤,同樣模糊地描述了另一種不同的、對我來說同樣沒有吸引力的語言。組裡的專家們看不到設計的缺陷,他們堅定地決定採用該草案,認為它將在三個月內完成。我徒勞地告訴他們不會。我徒勞地敦促他們移除語言中的一些技術錯誤,例如引用(references)的過度使用、預設類型轉換。工作組遠非希望簡化語言,反而要求作者包含更複雜的功能,例如運算符重載和並發。
當任何新的語言設計項目接近完成時,總會有一股瘋狂的衝勁,想在標準化之前添加新功能。這股衝勁確實是瘋狂的,因為它導致了一個無法逃脫的陷阱。一個被省略的功能總可以在稍後添加,當其設計和影響得到充分理解時。一個在完全理解之前被包含進來的功能,則永遠無法在以後移除。
最後,在1968年12月,我懷著沮喪的心情,參加了在慕尼黑舉行的會議,我們的這個孕育已久的怪物將在那裡誕生並獲得 ALGOL 68 這個名字。到這時,組裡的一些其他成員也已經感到失望,但為時已晚:委員會當時已被該語言的支持者佔據,並提交給 IFIP 的上級委員會批准。我們能做的最好的事情就是隨同提交一份少數派報告,陳述我們經過深思熟慮的觀點,即「...作為一種可靠地創建複雜程式的工具,該語言是失敗的。」這份報告後來被 IFIP 壓制,這讓我想起了 Hilaire Belloc 的詩句: 但科學家,他們應該知道 / 向我們保證情況必然如此。 / 哦,我們絕不應該,絕不質疑 / 沒人確定是怎麼回事。
我不再參加該工作組的任何會議。我很高興地報告,該工作組很快意識到他們的語言和描述存在問題;他們又辛勤工作了六年,才產生了該語言的修訂描述。這是一個巨大的改進,但我恐怕在我看來,它並未移除設計中的基本技術缺陷,也未開始解決其壓倒性複雜性的問題。
程式設計師總是被複雜性包圍;我們無法避免它。我們的應用程式很複雜,因為我們雄心勃勃地想以越來越複雜的方式使用我們的電腦。程式設計很複雜,因為我們每個程式設計項目都有大量相互衝突的目標。如果我們基本的工具,也就是我們設計和編寫程式的語言也如此複雜,那麼語言本身就成了問題的一部分,而不是解決方案的一部分。
PL/I 與標準化
現在讓我告訴你們另一個雄心勃勃的語言項目。從1965年到1970年,我曾是 European Computer Manufacturers Association 技術委員會 No. 10 的成員,甚至擔任過主席。我們最初負責觀察,後來負責標準化一種旨在終結所有語言的語言,由當時最偉大的電腦製造商設計,旨在滿足所有電腦應用程式的需求,無論是商業的還是科學的。我帶著興趣和驚奇,甚至有一點嘲笑,研究了從1964年3月1日至11月30日之間出現的四份描述一種名為 NPL 的語言的初步文檔。每份文檔都比前一份更雄心勃勃、更荒謬,充滿了美好的願望。然後,該語言開始被實現,一系列新的文檔開始每六個月出現一次,每份都描述了該語言的最終凍結版本及其最終凍結的名稱 PL/I。
但對我來說,文檔的每次修訂僅僅表明初始的 F 級實現取得了多大的進展。尚未實現的語言部分仍然用流暢華麗的散文描述,承諾著純粹的愉悅。已實現的部分,花朵已經枯萎;它們被解釋性註腳的灌木叢所窒息,對每個特性的使用施加了任意且令人不快的限制,並將控制語言所有其他特性的複雜且意外的副作用和互動影響的責任轉嫁給程式設計師。
最後,1968年3月11日,該語言描述被堂而皇之地呈獻給等待中的世界,作為標準化的合格候選人。但事實並非如此。它已經經過了其原始設計者七千多處更正和修改。在1976年最終作為標準發布之前,還需要另外十二個版本。我恐怕這不是因為相關的每個人都對其設計感到滿意,而是因為他們已經徹底厭倦和失望了。
只要我參與這個項目,我就力勸簡化語言,如果需要,則通過子集化,以便專業程式設計師能夠理解它,並能夠對其程式的正確性和成本效益負責。我力勸移除危險的功能,例如預設(defaults)和 ON-conditions。我知道對於如此複雜的語言,不可能編寫一個完全可靠的編譯器,也不可能編寫一個完全可靠的程式,因為程式各部分的正確性取決於檢查程式其他所有部分是否避開了語言中的所有陷阱和陷阱。
起初我希望這樣一個技術上不健全的項目會崩潰,但我很快意識到它註定會成功。只要有足夠的決心,軟體中的幾乎任何東西都可以實現、銷售甚至使用。區區一個科學家說什麼都無法抵擋億萬美元的洪流。但是有一種品質是無法通過這種方式購買的——那就是可靠性。可靠性的代價是追求極致的簡單性。這是非常富有的人最難付出的代價。
對未來的警告:關於 ADA
所有這一切都發生在很久以前。在一場旨在預覽即將到來的計算機時代的會議上,這些是否仍有關聯?我最擔心的就是它們有關聯。過去二十年我們犯下的錯誤,今天正以更大的規模重演。我指的是一個程式語言設計項目,它產生了題為 strawman, woodenman, tinman, ironman, steelman, green,最終是 ADA 的文檔。這個項目是由世界上最強大的組織之一,United States Department of Defense 發起和贊助的。因此,它無論技術優劣,都注定會產生影響和關注,而它的錯誤和缺陷正威脅著我們,帶來更大的危險。因為到目前為止我們所掌握的任何證據都無法讓人相信這門語言避免了過去困擾其他複雜語言項目的任何問題。
自1975年以來,我一直在為這個項目提供我最好的建議。起初我極度樂觀。這門語言的最初目標包括可靠性、程式的可讀性、語言定義的規範性,甚至包括簡單性。漸漸地,這些目標被犧牲,取而代之的是功能強大,據說這是通過過多的功能和符號慣例實現的,其中許多是不必要的,有些,例如異常處理(exception handling),甚至很危險。我們正在重演汽車設計的歷史。小工具和浮華勝過安全和經濟等基本考慮。
現在還來得及!我相信通過仔細裁剪 ADA 語言,仍然可以選出一個非常強大的子集,它在實現上是可靠和高效的,在使用上是安全和經濟的。然而,這門語言的發起者明確聲明不允許有子集。這是整個奇特項目中最奇怪的悖論。如果你想要一門沒有子集的語言,你必須讓它很小。你只包含那些你確定是該語言每個單一應用所必需的,並且你確定適用於實現該語言的每個單一硬體配置的功能。然後,可以在必要時針對特定硬體設備和特定應用程式專門設計擴展。這正是 PASCAL 的巨大優勢,它只有很少不必要的功能,幾乎沒有子集的必要。這就是為什麼這門語言足夠強大,可以支持專門的擴展——用於即時工作的 Concurrent PASCAL,用於離散事件模擬的 PASCAL PLUS,用於微處理器工作站的 UCSD PASCAL。如果我們能夠從過去的成功中吸取正確的教訓,我們就不需要從失敗中學習了。
因此,我給予 ADA 的發起者和設計者的最好建議被忽視了。作為最後的手段,我向你們,美國程式設計專業的代表,以及關心自己國家和人類福祉和安全的公民們發出呼籲:不要讓這門語言在目前狀態下用於可靠性至關重要的應用,例如核電站、巡航導彈、早期預警系統、反彈道導彈防禦系統。 下一個因程式語言錯誤而偏離軌道的火箭可能不是一艘對金星進行無害探測的太空火箭:它可能是一枚在我們一個城市上空爆炸的核彈頭。一個不可靠的程式語言產生不可靠的程式,對我們的環境和社會構成的風險遠大於不安全的汽車、有毒農藥或核電站事故。保持警惕,減少這種風險,而不是增加它。
皇帝的新衣 (故事)
讓我不要以如此陰鬱的調子結束。我們最好的建議被忽視是所有擔任顧問的人的共同命運,自 Cassandra 指出將木馬帶入特洛伊城牆內的危險以來就一直如此。這讓我想起小時候聽過的一個故事。如果我沒記錯的話,它的標題是:
皇帝的新衣
很久以前,有一位皇帝過於喜愛衣服,把所有的錢都花在了穿著上。他不關心士兵,不參加宴會,也不在法庭上做判決。關於其他任何國王或皇帝,人們可能會說:「他正在開會」,但關於他,人們總是說:「皇帝坐在他的衣櫥裡」。他也確實如此。在一個不幸的場合,他曾被騙光著身子出巡,這讓他懊惱不已,而他的臣民則欣喜若狂。他決定再也不離開王座,為了避免赤裸,他命令將他的許多新衣服一件件疊加在舊衣服上。
時光在作為他首都的大城市裡快樂地流逝。大臣和朝臣,織工和裁縫,訪客和臣民,女裁縫和刺繡師,進進出出王座廳,各司其職,他們都驚嘆道:「我們的皇帝的服飾多麼華麗啊!」
有一天,皇帝最年長、最忠誠的大臣聽說有一位最傑出的裁縫在一個古老的高級縫紉學院教書,他發展出了一種抽象刺繡的新技藝,其針法如此精妙,以至於沒有人能看出它們是否真的存在。「這些針法確實很 splendid,」大臣想。「如果我們能請這位裁縫來指導我們,我們將把我們皇帝的裝飾提升到如此浮誇的高度,以至於全世界都會承認他是史上最偉大的皇帝。」
於是,這位誠實的老大臣以巨大的花費聘請了這位大師級裁縫。裁縫被帶到王座廳,向那堆已經完全覆蓋了王座的精美衣服行禮。所有朝臣都熱切地等待著他的建議。想像一下他們的驚訝,當他的建議不是在現有的衣服上增加精巧和更複雜的刺繡,而是移除層層華服,並追求簡潔和優雅,而不是奢華的繁複。「這個裁縫不是他所聲稱的專家,」他們低聲說。「他的腦子被象牙塔裡的長期沉思搞糊塗了,他不再理解現代皇帝的服裝需求。」裁縫大聲而長時間地爭辯他建議的合理性,但他的聲音無法被聽到。最後,他收下費用,回到了他的象牙塔。
直到今天,這個故事的全部真相才被揭示:在一個美好的早晨,當皇帝感到又熱又無聊時,他小心翼翼地從他的衣服山下掙脫出來,現在在另一個故事裡幸福地作為一個豬倌生活著。那位裁縫被封為所有顧問的守護聖人,因為儘管他收取了巨額費用,他卻從未能夠說服他的客戶,他逐漸意識到他們的衣服裡根本沒有皇帝。