當我們在寫程式設計文件時,通常會有兩種做法:
- 一般技術寫作(Technical Writing):描述演算法、流程,搭配一些程式碼。
- Knuth 的 Literate Programming:把程式與解釋交織在一起,像寫一本書一樣,完整呈現設計思路。
這兩者看似相似,但差異其實非常大。以下我從幾個層面來比較。
1. 「說明什麼」 vs. 「說明為什麼」
- 一般技術文件 重點在於「功能怎麼實作」。例如: 先檢查每個單詞,超過寬度就換行,最後輸出結果。
- Knuth 的 literate doc 他不只告訴你「怎麼做」,而是從「問題動機」開始: 如果我們太早或太晚換行,段落的排版就會很醜。 我們需要一個衡量「醜」的數學函數。假設理想長度是 L,實際長度是 A,那麼 badness = 100 × (|A-L|/L)³。 這樣我們就能用動態規劃,找到最美觀的斷行。
在 Knuth 的筆下,每個程式背後都有故事。
2. 「程式碼附帶註解」 vs. 「程式碼是書的一部分」
Knuth的文稿像一本「帶程式的數學教科書」:
- 先講原理
- 再推導公式
- 然後慢慢展開程式碼
讀者讀完不只是理解程式,還會學到背後的理論。
- 一般寫作:程式碼是主角,文件只是附屬註解。
- Knuth:敘述是主角,程式碼只是證據。
3. 數學的角色
Knuth 的 literate programming 常常帶入數學:
- 公式 定義嚴格標準,例如 badness 函數。
- 演算法分析,計算複雜度、證明正確性。
- 抽象模型,把實際問題轉化為數學最佳化問題。
這和一般 technical writing 最大的不同是: 一般寫作只想「程式能動」,Knuth 的寫作追求「程式與數學一致」。
4. 可維護性 vs. 可傳世性
- 一般技術文件: 目的是讓團隊能維護程式。通常寫給「下一個工程師」。
- Knuth 的 literate doc: 目的是讓程式成為「人類知識的一部分」。 他把《TeX: The Program》寫得像經典教材,讀者不只是工程師,也可能是數學家、科學家、學生。
這就是為什麼 Knuth 的程式碼 40 年後仍被研究。
5. 範例比較
普通寫法(斷行演算法簡版)
// 檢查單詞,若超過寬度就換行。
procedure break_lines(words, max_width);
begin
current_width := 0;
for each word in words do
if current_width + width(word) > max_width then
begin
new_line;
current_width := 0;
end;
print(word);
current_width := current_width + width(word);
end;
Knuth 式寫法
// 排版的美觀不能只靠貪心演算法。為了衡量「不好看」的程度,
// 我們定義 badness = 100 × (|A-L| / L)³。
// 問題轉化為:在所有換行方式中,找到最小化 badness 總和的方案。
// 這是一個典型的動態規劃問題。
procedure break_paragraph;
begin
compute_badness_table;
for each feasible breakpoint do
compute_least_demerits;
choose_breaks_with_minimum_demerits;
end;
這個例子清楚展現:Knuth 不是單純寫程式,而是在寫「程式的哲學」。
總結:Knuth 的境界
Donald Knuth 的 literate programming 有幾個核心特點:
- 程式碼是敘事的一部分,不是獨立的塊。
- 數學推導與程式緊密結合,程式變得更精確。
- 重視「為什麼」,讓程式成為知識,而不是黑箱。
- 追求美學:排版美、程式美、解釋美。
他把程式設計從「工程技術」提升到「人文藝術」。
所以有人說:
Literate programming 不是單純的寫程式,而是寫一部可讀的作品。
如果你正在學習 literate programming,可以試著:
- 選一個小問題(例如計算字頻、排序演算法),
- 不要急著寫程式,先用文字解釋「動機、原理、數學」,
- 然後再逐步引入程式碼。
這樣的訓練,就是向 Knuth 靠近的一步。