Donald Knuth#
在本書所有受訪者中,Donald Knuth 或許是最不需要介紹的。過去四十多年來,他一直在撰寫多卷本巨著 The Art of Computer Programming——基礎演算法和資料結構的聖經,被 American Scientist 列為二十世紀最重要的 12 本物理科學專著之一,與 Russell、Einstein、Dirac、Feynman、von Neumann 的著作並列。他普及了 Big-O 符號在演算法分析中的使用,發明了 LR parsing,並為 goto 語句辯護以回應 Dijkstra 的批評。
但他不僅是理論家。1976 年完成 The Art of Computer Programming 第三卷後,Knuth 花了本應一年的休假來撰寫排版軟體 TeX 和 METAFONT,結果花了十年。在此過程中,他發明了一種新的程式設計風格——literate programming(文學程式設計),以及一種至今仍是最先進水準的段落斷行演算法。
他的眾多獎項包括首屆 ACM Grace Murray Hopper Award(1971)、Turing Award(1974)和 National Medal of Science(1979)。1990 年他停止使用電子郵件,解釋說他的工作不是「站在事物頂端」而是「深入事物底部」。
學習程式設計#
初識電腦#
- 1956 年秋天進入 Case Institute of Technology 就讀大一
- 該學期學校剛好買了一台 IBM 650——第一台大量生產的電腦
- 在統計實驗室打工為統計學家製表資料,透過窗戶看到閃爍燈光的機器,覺得很迷人
- 一天下午有人在黑板上為新生解說這台機器的功能
第一個程式#
- 找到機器手冊,裡面有十行的範例程式——覺得它們「有點蠢」,看起來可以改進
- Case 是少數允許大學生親手碰機器的學校(其他學校需要專人操作)
- 晚上可以去操作機器,嘗試自己的修改,發現修改真的有效
- IBM 650 是十進位機器,這讓它感覺更「人性化」
真正的程式設計啟蒙#
- 寫了一個約 100 行的程式來計算質因數,花了兩週除錯,找到 100 多個 bug
- 第二個程式是二進位和十進位轉換
- 第三個程式——井字棋(tic-tac-toe)——才真正讓他成為程式設計師
- 寫了三個版本,其中一個是自我學習的:從零開始學習,每輸一局就記住自己可疑的走法和對手的好走法,400 局後就能玩得不錯
Knuth 學習程式設計的方式:自己摸索一個程式,坐在機器前花數週時間,讓它一點一點變得更好。他不認同 Dijkstra 說的「學生在最初幾年不應該碰機器」的觀點——學習需要在所有層次上看到事物。
The Art of Computer Programming#
寫作動機與方法#
- 試圖組織圍繞所討論主題的大量智慧,從各個來源收集,統一成一個整體
- 目前在寫的部分,起始材料是以數學行話寫成的論文——他試圖去除行話,給出關鍵思想
- 每五頁就是某個人一生的職業成果
- 他把答案都寫出來,因為知道十年後自己也不會記得怎麼做
書的受眾與平衡#
- 時常在「太複雜了不該討論」和「太簡單了沒有好東西」之間掙扎
- 關於 Binary Decision Diagrams (BDDs) 的章節竟有超過 260 道練習
- 大多數人會挑選自己需要的部分閱讀,而非從頭讀到尾
- 嘗試以對實際程式設計師最相關的方式來探索領域,而非追求學術聲望
實用主義取向#
- 不使用平衡樹或 AVL 樹,除非知道會是非常大的樹
- 偏好使用普通的二元搜尋樹加上一個隨機化技巧
- 刻意省略那些只在 n 超過百萬時才有理論優勢的資料結構
TeX 與程式設計實踐#
TeX 的開發過程#
- 1977-78 年首次撰寫 TeX,當時還沒有 literate programming
- 在大筆記本上用鉛筆手寫程式碼,六個月後才開始輸入電腦
- 結構化程式設計給了他不變量(invariants)的觀念,讓他有信心在六個月不測試的情況下寫程式碼
- 這是第一代系統,很多架構需要在實際使用後才能確定
寫 TeX 對寫書的影響#
- 使用結構化程式設計解決真實(非玩具)問題的經驗,影響了他描述演算法的方式
- 讓他理解了快取策略、計算趨勢等實務知識
- 發現軟體佔據大腦的程度遠超預期——無法同時全職教書和全職寫軟體
- 這讓他對做大型軟體專案的人產生特別的敬佩
程式設計時間估算#
- 程式設計比寫書更難,也更難估算時間
- 即使是 Knuth 自己,也常常低估程式的豐富度和所需時間
- 管理程式設計師的人不應期望時間估算是可預測的
Literate Programming(文學程式設計)#
核心理念#
- 技術寫作的第一原則:了解你的讀者
- 第二原則:用互補的方式說兩次——正式地和非正式地,讓讀者從兩個方向強化理解
- 技術寫作中通常有冗餘:先給形式定義,再給非正式的直覺解釋
- Literate programming 提供了一個自然框架,在自然語言(英文)和形式語言(C、Lisp 等)之間切換
寫程式的順序#
- 不需要按照編譯器要求的順序呈現程式——而是按讀者最容易理解的順序
- 自己的程式設計方式既不完全是 top-down 也不完全是 bottom-up
- 心理上選擇「接下來做什麼我最有成就感」——解決最令人擔憂但又準備好面對的問題
- 最終程式的順序幾乎總是按照他思考的順序自然排列
Jon Bentley 的觀察(Knuth 轉述):世界上只有 2% 的人天生是超級程式設計師,也只有 2% 的人天生是超級作家。而 Knuth 期望所有人同時是兩者。但隨著部落格的普及,一般人的寫作能力已大幅提升,所以這個論點不再那麼有力。
實際效果#
- 他相信有些程式不用 literate programming 就不可能完成——例如 MMIX 模擬器(170 頁),光分拆成子程式不足以讓它在智力上可管理
- 在 Stanford 與七位大學生嘗試,六位喜愛並沿用至今,一位討厭
- 寫給自己看也很有價值——一年後回頭看能確切知道當時在想什麼
- TeX 是歷史上被最多人在某個時間點理解的程式之一,歸功於 literate programming 的文件能力
對黑箱和可重用軟體的批評#
- 有一種「對可重用軟體的過度強調」——人們製作封閉的黑箱,只允許設定參數
- 如果寫程式只是從函式庫中呼叫函式、找到正確的參數組合,誰還會想以此為業?
- 打開黑箱、理解內部、根據需求調整——幾乎總能做得更好
- 類比數學:數學書充滿定理,但你幾乎不會直接套用某個定理——而是看證明,然後修改假設來適應你的問題。軟體也是如此。
Knuth 並非說黑箱無用——如果必須打開每個黑箱才能使用函式庫,結果會更慢更差。但他希望程式設計師有能力也有自由去打開黑箱,理解並改進內部的東西。
除錯方法#
使用 GDB#
- GNU debugger 的開發者意識到可以讓前處理器寫出的程式與高階原始碼對應
- 因此 GDB 可以直接在 CWEB 原始碼層級進行除錯——不需要看低階 C 程式碼
- 這利用了 C 語言中的
__LINE__指令
Sanity Check(健全性檢查)#
- 會寫大量檢查資料結構的程式碼——冗餘性、參考計數等
- 健全性檢查可能讓程式慢 100 倍,但能在崩潰前數十億步就發現錯誤
- 約 10% 的程式碼專門用於除錯時才需要的健全性檢查
- 健全性檢查程式碼同時也記錄了資料結構
窮舉測試#
- 例如寫了一個新的乘法程式,就用 256 個數字相互乘以窮舉測試
- 乘到 2 x 3 就失敗了——修好後再測,直到 256 x 256 全部正確
攻擊者心態#
- 為 TeX 寫了極端的「酷刑測試」(torture test)
- 訣竅是忘記自己是作者,想像是別人寫的程式然後攻擊它
- 一生都是挑剔者(nitpicker),喜歡從找錯誤中獲得快感
- 比起證明某個東西正確,更容易進入「找反例」的心態
對形式驗證的看法#
- 永遠不可能真正「證明」程式正確——驗證器可能有 bug,規格可能有 bug
- Tony Hoare 的第一篇形式證明論文(“Proof of a Program: FIND”)中就有兩三個 bug
- TeX 是為人類使用而設計的,要定義「正確」都很困難
- 自己寫的程式中,根本不知道如何陳述所有的 assertion
對錯誤日誌的看法#
- 保留了 TeX 的完整錯誤日誌(“The Errors of TeX”)
- 但記錄錯誤並沒有防止他犯同樣的錯——因為他總是在嘗試更難的東西
- 如果回去寫以前那種簡單的程式,就不會犯那些錯了
- 總是在自己能力的極限運作——所以永遠會有 bug
Knuth 對銀彈的看法:「每三年就會出現一個新的流行語,號稱能解決所有問題。Extreme programming 是最近兩三年的。再之前是別的東西。會有很多人跳上那班車,然後發現:『噢,還是很難。』」
對程式語言的看法#
C 語言中的指標#
- 認為 C 語言中指標的使用是程式設計中最重要的革命之一
- 當 x 是指標且
x + 1根據 x 指向的東西不同而跳不同距離——這是表示法上的驚人改進 - 起初以為是大錯誤,後來發現很喜歡
指標在 64 位元時代的困境#
- 在 64 位元機器上,每個指標佔 64 位元,但實際只用 32 位有意義
- 指標讓資料結構大小翻倍,而且快取效率降低
- 不得不改用陣列加上複雜的巨集來模擬指標
完美程式語言的幻想#
- 1974 年曾預言 1984 年會有「Utopia 84」——完美的程式語言取代 COBOL 和 Fortran
- 這沒有發生——每種新語言都清理了前代已理解的東西,然後加入實驗性的新東西
- 沒有人願意在「已理解的部分」停下來,保持乾淨和穩定
- Pascal 曾有這個哲學但沒有成功持續
- Java 不夠好——不夠可擴展
學術界與工業界#
- 1960 年代學術界遠超工業界
- 1980 年代情況逆轉——工業界在嘲笑大學裡禁止 goto 的教條
- 後來大學又在網路和大資料方面超前
- 來回擺盪,但目前演算法和資料結構社群有太多「巴洛克式」的東西——精巧但與實踐脫節
程式設計師的本質#
2% 理論#
- 每 100 人中大約 2 人是天生與機器「共振」的程式設計師
- 這個比例在他長期觀察中幾乎是常數
- 即使是 Wasilla, Alaska 這樣的小鎮(10,000 人)也有約 200 個程式設計師
- 不同的「語法糖」和「語言方言」適合不同的個性類型——但沒有唯一最好的思考方式
對程式設計變得無聊的擔憂#
- 擔心很多程式設計正變成把別人的軟體拼在一起的「魔法咒語」
- 缺乏創意,變得太無聊
- 過去的樂趣來自創造新東西;現在的樂趣可能只是做完無聊的工作後得到漂亮的圖像
- 但他自己做的那種程式設計仍然令人興奮
Knuth 對程式設計的熱情:「天啊,我有一種必須去程式設計的需求。我早上醒來腦中就有 literate program 的句子。早餐前——我相信詩人也有這種感覺——我必須去電腦前寫下這一段,然後才能吃飯,然後我就開心了。這是一種衝動,我必須承認。」
對可重用軟體的批評#
黑箱的問題#
- 矩陣乘法的例子:如果有一個矩陣乘法黑箱,將實數改為複數就需要 4 次乘法;但打開黑箱,利用一個數學恆等式只需要 3 次
- 優先佇列、二元搜尋等——他寧可自己寫,每次根據需求調整
- 寫 The Art of Computer Programming 第一卷時,人們甚至不知道可以在自己的程式中使用鏈結串列和指標
- 他的書「打開了人們的眼界」——原來可以理解並調整資料結構,而不只是呼叫別人的套件
自己寫 vs. 用函式庫#
- 他寫很多程式,自認可以代表典型情況:學幾個基本概念,用文字編輯修改之前的程式碼來重用,比花時間學別人的介面更快
- 但承認這不適用於所有人——如果是 50 人的團隊,情況可能不同
- 希望「個人程式設計師為了學習而寫程式」的想法不會消亡
重要觀點總結#
關於學習#
「科學家們常犯的一個根本錯誤是:他們不理解當你在學某樣東西時,需要在所有層次上看到它。你必須先看到地板,才能建到天花板。所有這些都會進入大腦,被壓到更深處——到了某個程度,年長者會忘記他們曾經需要它。」
關於軟體的複雜性#
「軟體佔據大腦的程度遠超我的預期。它是比寫書困難得多的任務。我無法同時全職教書和全職寫軟體。」
關於永恆的 Bug#
「我認為是因為我總在嘗試更難的東西。我總是在自己能力的極限操作。如果回去寫以前那種簡單的程式,我不會犯那些錯。但那樣就沒那麼有趣了。」
關於程式設計的變遷#
- 結構化程式設計提供了模式,使人們能可靠地將程式組合成更大的系統
- Literate programming 是他最大的轉變——從「組合正確的指令」變為「作為解說者向讀者呈現」
- 不同語言中的語法糖和方言適合不同個性——沒有唯一的最佳方式
- 抽象的概念很重要,讓我們能處理複雜系統同時保持控制感
關於 Change Files#
- Literate programming 的一個意外副產品
- 讓使用者可以在不修改主程式的情況下自訂程式——只需要一個描述差異的 change file
- TeX 曾有數百人用 change file 將其移植到不同平台
- 極端案例:將 TeX 從 8-bit 改為 Unicode 的 Omega 專案,change file 比主程式長 10 倍
- Knuth 自己現在大量使用 change file 來做同一程式的不同實驗版本
- 例如研究 BDD 時,他有一個基本程式,然後用十幾個 change file 做各種變體實驗
對 Burroughs 硬體除錯的故事#
- 早期為 Burroughs Corporation 除錯硬體設計
- 工程師們給他機器規格,他嘗試構造邊界情況
- 在 B-5000 系列機器量產前找到了超過 200 個 bug——即使機器已經通過了模擬器測試
- 例如:浮點數乘法計算錯誤、堆疊在某些暫存器為空時邏輯出錯
- 方法是嘗試證明規格是正確的——如果無法證明,就找反例
對程式設計教育的看法#
學習的所有層次#
- 批評科學家們忘記自己曾經也需要從底層學起的錯誤
- 當年長的科學家說「我的學生不需要浪費時間在低層次的東西上」時,他們忘了那些底層知識是如何被壓入潛意識、形成直覺的
- 你必須先看到地板才能建到天花板
Binary Search 的教訓#
- 1970 年代開始的程式設計課,第一天就讓學生寫 binary search
- 收集後讓助教檢查——正確率不到 10%,而且有四到六種不同的 bug
- Java 標準函式庫中的 binary search 也有一個存在多年的溢位 bug(
(min + max) / 2) - 這是黑箱問題的一個例子——如果每個人自己寫 binary search,錯誤率可能更高;但至少標準函式庫的 bug 被找到並修復了
對程式設計是否需要數學的看法#
- The Art of Computer Programming 包含大量數學
- 但 Knuth 有時自己也懷疑讀者是否能讀懂
- 試圖去除行話、簡化、給出關鍵思想
- 電腦科學不能被歸結為 50 個簡單的東西——它真的很廣很深