《Code Complete 軟體建構之道》Podcast 準備稿:高品質的類別與常式
書名: 軟體建構之道 Code Complete(A Practical Handbook of Software Construction, 2nd Edition) 作者: Steve McConnell 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Code Complete.類別與常式:把複雜度關進盒子 (3/15) 涵蓋範圍: 第6章 工作類別 + 第7章 高品質的子程式
背景速覽
上一集講了設計的最高原則「管理複雜度」,這集就進到落地的第一線:類別和常式(routine)——你管理複雜度的兩大主力工具。McConnell 講了一段軟體史的演進:早期程式以「陳述句」為單位思考,七八十年代以「常式」為單位,二十一世紀則以「類別」為核心。這集把這兩個尺度的「好設計」拆給你看。
一句話重點
類別和常式存在的最重要理由都是同一個——降低複雜度,讓你用的時候不必再想它內部怎麼運作;好的類別呈現一致的抽象、把秘密藏好,好的常式只做一件事(功能內聚)、名字精準到能當文件用。
值得討論的重點
1. 類別的根基是抽象資料類型(ADT),介面要呈現「一致的抽象」
McConnell 說理解 ADT 是理解物件導向的根本——類別本質上就是「ADT 加上繼承和多型」。一個好類別最重要的第一步,是設計一個好介面:它要呈現一致的抽象層級(一個類別只實作一種 ADT,別讓 EmployeeCensus 同時暴露「員工」和「清單容器」兩種操作)、要成對提供互補操作(有 TurnLightOn() 就要有 TurnLightOff())、要把無關的資訊搬到別的類別。他給了一個漂亮的判準:當你覺得類別內聚性很弱時,反問「這個類別有沒有呈現一致的抽象?」——抽象好的介面,內聚性通常自然就強。
2. 繼承是把雙面刃——McConnell 對它出奇地保守
這是這集最值得展開、也最有觀點的一段。McConnell 對繼承的態度是警戒的,因為繼承會增加複雜度,跟「管理複雜度」的首要使命直接衝突。他的核心判斷規則很值得唸出來:共享資料但非行為時用包含(has-a),共享行為但非資料時用繼承(is-a),兩者都有也用繼承;想控制介面用包含,想讓基底類別控制介面用繼承。 還有一串戒律:遵守 Liskov 替換原則、避免過深的繼承樹(二到三層是實務上限)、偏好多型而非一堆 type checking、所有資料成員設為 private 不要用 protected。最後一句最重:繼承傾向於對抗管理複雜度的首要使命——所以除非是真正的 is-a 關係,包含通常優於繼承。
3. 建立常式最重要的理由,竟然是「降低複雜度」而不是「省空間」
第7章講常式。McConnell 戳破一個迷思:很多人以為常式是用來「避免重複、省程式碼」的,但他說最重要的理由是降低複雜度——把細節封進去,你用的時候就不必想它。更反直覺的一條:看似太簡單的操作也值得建立常式。一行 points = DeviceUnitsToPoints(deviceUnits) 遠比展開的計算公式好讀,而且「小操作往往會長大」——今天三行,明天可能要加錯誤處理、邊界檢查,集中在一處改一次,勝過散在十幾個地方各改三行。
4. 內聚性與命名——常式品質的兩把尺
常式層級最核心的設計概念是內聚性 cohesion,目標是「只做好一件事」。最理想的是功能內聚(像 sin()、GetCustomerName())。研究數據很有力:高內聚的常式 50% 完全無缺陷,低內聚的只有 18%。命名則是常式品質的指標——McConnell 有一句話很狠:糟糕的名字若準確,代表常式設計爛;若不準確,代表名字沒告訴你它在幹嘛;兩種都得改。 如果一個常式要叫 ComputeReportTotalsAndOpenOutputFile() 才講得清楚,真正的解法不是縮短名字,而是消除那個副作用。參數方面:按「輸入-修改-輸出」排列、限制在七個左右、用 const 保護輸入參數。
注意事項
⚠️ 關於常式長度,McConnell 的立場可能跟聽眾的直覺相反、也跟 Uncle Bob 不同。他引研究說:100-200 行的常式錯誤率並不比更短的高,超過 500 行才明顯上升。所以他反對用人為的行數上限來切常式,而主張讓內聚性、巢狀深度、變數數量這些複雜度因素自然決定長度。這跟《Clean Code》主張「函式越短越好、最好幾行」是有張力的——這是個很好的對照討論點:McConnell 是數據派(看研究怎麼說),Uncle Bob 是美學派(看讀起來爽不爽)。
⚠️ 第6章後半談 C++ 巨集、inline、多重繼承 mixin 等,帶較重的 2004 年 C++ 色彩。現代語言(Java/Kotlin/Swift)大多有更好的替代品,講的時候點到即可,把重點放在「謹慎使用、語意要清楚」的原則上。Bjarne Stroustrup 那句「幾乎每個巨集都反映出語言、程式或程式設計師的缺陷」值得唸,很有警世味。
專家補充
💡 「包含優於繼承」這條,是物件導向圈子幾十年的共識,後來被《Design Patterns》(GoF)正式寫成「Favor composition over inheritance」(優先組合而非繼承),也是 Effective Java(Joshua Bloch)的著名條目之一。McConnell 在 2004 年就把這條講得很透。可以告訴聽眾:如果你寫 OO 時常常不知道該不該繼承,預設選包含/組合,通常不會錯。
💡 常式長度的「100-200 行也 OK」這個立場,是 Code Complete 最容易引起爭論的點之一。我的建議是別選邊站,而是點出背後的真問題:長度本身不是罪,內聚性才是。 一個 150 行但只做一件事、巢狀很淺的常式,可能比五個互相糾纏的 30 行常式更好讀。重點永遠是複雜度,不是行數——這也正好回扣上一集的最高原則。
💡 「迪米特法則 Law of Demeter」(account.ContactPerson() 可以,但 a.b().c().d() 這種火車對接不行)這章有提到。這是個很實用的 code smell 偵測器——當你看到一長串點點點,通常代表你違反了封裝、知道太多別人的內部結構。可以給聽眾當隨身檢查工具。
討論問題
🎙️ 開場鉤子:「程式設計這幾十年,思考的單位一直在升級——從一句陳述句,到一個常式,到今天的一個類別。這集講的就是:怎麼設計好這兩個你每天都在用的盒子。」
🎙️ 自問自答:「該繼承還是該組合?」——用 McConnell 的規則回答:真的是 is-a 關係(而且子類能完全替換父類)才繼承,否則一律包含/組合。繼承會增加複雜度,跟我們的首要使命作對。
🎙️ 拋給聽眾的一題:「常式到底該多短?」——拋出 McConnell(數據說 200 行也行)和 Uncle Bob(越短越好)的對立,問聽眾:你站哪邊?我的看法是——別管行數,看它是不是只做一件事。
🎙️ 帶走的一題:「這禮拜挑一個你最近寫的常式,唸出它的名字——如果名字裡有 And、有含糊的動詞(Handle、Process、Manage),那不是名字的問題,是這個常式做太多事了。」
更大範圍關聯
- 物件導向經典群:本集的「包含優於繼承」「Liskov 替換」直通 Uncle Bob 的 SOLID(L 就是 Liskov)、GoF《Design Patterns》、Joshua Bloch《Effective Java》。可在 [軟體工程領域切分藍圖] 把這幾本串成一條 OO 設計線。
- 與《Clean Code》的對話:常式長度與命名是兩本書都重視、但態度不同的議題——Code Complete 重數據、Clean Code 重美感,是整季最好的對照組之一。
- 承上:本集是 EP2「管理複雜度」原則在「類別/常式」尺度的落地;接下來 EP4(防禦式設計)會講怎麼讓這些盒子在錯誤輸入下不崩。
- 時代座標:2004 年 OO 正當紅、設計模式熱潮方興未艾,所以這兩章字裡行間有很濃的「物件導向是王道」氣息——今天回看,函數式、組合式的思潮已部分修正了當年對繼承的依賴。
錄製建議
- 建議時長:約 25-30 分鐘。配比:類別介面與一致抽象 7 分;繼承 vs 包含(重頭戲、有觀點)9 分;常式為何存在+小常式 7 分;內聚性與命名 7 分。
- 討論策略:繼承那段是這集最有戲的部分,用「McConnell 對繼承出奇地保守」當切入點,把他的判斷規則唸清楚。命名那段用「如果名字要叫 AndOpenFile 才完整,問題是副作用不是名字」這個犀利論點當高潮。常式長度刻意製造跟 Clean Code 的對立,讓節目有辯論張力。
- 提醒自己:C++ 巨集、inline 那些細節快速帶過,把時間留給原則性的討論。