第 9 / 11 部 待錄

當您寫程式時下-測試安全與命名

《The Pragmatic Programmer 務實的程式設計師》Podcast 準備稿:當您寫程式時(下)測試、安全與命名

書名: 務實的程式設計師 The Pragmatic Programmer(Your Journey to Mastery, 20th Anniversary Edition) 作者: David Thomas & Andrew Hunt 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|The Pragmatic Programmer.測試是設計回饋、屬性測試、安全偏執與命名 (9/11) 涵蓋範圍: 第7章 當您寫程式時(下)Topic 41-44:測試對程式碼的意義、以屬性為基礎的測試、待在安全的地方、命名

背景速覽

接續上一集(直覺、刻意、Big-O、重構),這集收掉第七章後半,把焦點從「內在功夫」轉到「對外把關」:測試的真正意義、用屬性測試讓電腦幫你抓盲點、把安全當成日常的健康偏執、以及命名這門揭示意圖的功夫。其中最反直覺的是測試觀——作者明說「測試不是為了找 bug」。這四個 Topic 共同回答一個問題:當你寫完一段「能跑」的 code,你還欠它什麼?

一句話重點

測試最大的價值發生在你思考與撰寫測試的當下(它是你 code 的第一個使用者,會逼出更好的設計),而非運行的時候;用屬性測試讓不帶成見的電腦幫你驗證假設、抓出你預期不到的 bug;安全要當成每天的健康偏執(最小攻擊面、最小權限、安全預設值、永遠別自己做加密);而好命名揭示你的意圖與信念——這些都是「能跑」之後真正的專業功夫。

值得討論的重點

1. 測試不是為了找 bug,而是程式碼的第一個使用者

Tip 66「Testing Is Not About Finding Bugs」——全章最反直覺的一句。問開發者為什麼寫測試,多半答「確保 code 能用」,作者說錯了。測試的主要好處發生在你思考和撰寫測試的時候,不是運行的時候。 書裡的例子很有說服力:你要寫一個查資料庫、回傳「每週看超過 10 支影片的使用者」的函式——還沒寫一行實作,光是想「我怎麼測它」,你就發現:得把資料庫實例當參數傳進去(不能用全域的),得把判斷欄位也當參數傳進去。兩個發現、API 設計就改了,而 code 還沒動。 所以 Tip 67「A Test Is the First User of Your Code」——測試逼你從外部、用客戶的眼光看自己的方法:難測的函式通常耦合太緊(讓 code 可測就降低了耦合)。配套還有 Tip 68「端到端建構,而非由上而下或由下而上」、Tip 69「為測試而設計」、Tip 70「測試你的軟體,否則你的使用者會幫你測」(你只有三個選項:Test First、Test During、Test Never——而「Test Later」幾乎總是等於「Test Never」)。重要的文化提醒:所有測試永遠保持綠燈,容忍一個「永遠失敗」的測試,會讓忽略全部測試變容易(這正是破窗理論的測試版)。

2. 屬性測試:讓不帶成見的電腦戳破你的假設

Tip 71「Use Property-Based Tests to Validate Your Assumptions」。單元測試有個盲點:如果你又寫 code 又寫測試,你可能在兩邊都表達了同一個錯誤的假設——code 通過測試,因為它做了你以為對的事,但你的理解可能就是錯的。解法不是找別人寫(那會失去「思考測試影響設計」的好處),而是讓電腦幫你測——電腦不分享你的成見。怎麼做?把合約(輸入滿足、輸出保證)與不變量(通過函式後某些狀態不變)合稱屬性,讓框架用上百組隨機輸入自動驗證。排序的例子:屬性一「排序後長度不變」(不變量)、屬性二「每個元素不大於其後者」(後置條件)——Python 的 Hypothesis 自動跑 100 組隨機列表,等於不費力產生 200 個測試。最精彩的是倉庫例子:基本單元測試全綠,加一條屬性「取出量+剩餘量=初始量」後,竟揪出一個沒人預期的 bug——in_stock 只檢查「至少有一個」沒檢查「是否足夠」。屬性測試找到的 bug 往往不是你預期的那種——這既是它的威力也是它的挫折。失敗時,把那組隨機值固定成一個常規單元測試(當回歸測試)。

3. 待在安全的地方:健康的偏執,與「另外的 90%」

Tip 72「Keep It Simple and Minimize Attack Surfaces」、Tip 73「Apply Security Patches Quickly」。作者誠實承認:第一版裡他們天真地說「不需要像間諜那樣偏執」——他們錯了,你確實需要,每一天。而且絕大多數資料洩露不是因為攻擊者多聰明,而是開發者太大意。當 code「終於能用」,你只完成了 90%,還有另外的 90%:分析它會怎麼出錯、外部攻擊者會怎麼故意弄壞它。五條基本原則要刻進骨子裡:最小化攻擊面(簡單的 code = 更少漏洞、永遠不信任外部輸入要消毒、別在輸出洩漏資訊如「這密碼已被別人用過」)、最小權限原則(用最少權限、最短時間,別動不動拿 root,引 Saltzer 1974)、安全預設值(預設就是最安全而非最方便,讓使用者自己決定取捨)、加密敏感資料(PII/密碼/金鑰絕不明文,金鑰絕不簽進版控)、快速套用安全修補(落後更新就是暴露在已知漏洞下,史上最大的洩露事件都是沒更新造成的)。還有一條鐵律:永遠不要自己做加密、不要自己實作登入系統——用第三方認證提供商,他們整天在維護安全,比你強。

4. 命名:揭示意圖與信念

Tip 74「Name Well; Rename When Needed」。引孔子「名不正則言不順」——程式設計中名字代表「一切」,因為它揭示你的意圖和信念。作者主張按角色命名:每當你創造一個東西,停下來問「我創建它的動機是什麼?」——這把你從眼前的解題思維拉出來看大圖景。範例很犀利:user = authenticate(...) 改成 customer/buyer,編碼時就不斷被提醒這個人在做什麼;deductPercent(double amount) 改成 applyDiscount(Percentage discount)(方法名講意圖、參數用自訂型別記錄預期);Fib.fib(n) 在模組脈絡下很冗餘,Fib.of(0) 更自然。關於單字母變數,作者立場務實——看文化:C 裡 i/j/k 是迴圈慣例、在該環境是被期待的,換個環境就錯了;尊重 camelCase/snake_case 的當地慣例。還有一致性:每個專案有自己的行話(jargon),「Order」在電商團隊和宗譜團隊意義完全不同,團隊要統一並一致使用。需要時就重新命名——別讓過時的名字一直誤導讀者。

注意事項

⚠️ 「測試不是為了找 bug」這句很容易被斷章取義成「不用認真測」。完全相反——作者要說的是測試的最大紅利在設計階段(逼出低耦合、可測的好設計),運行時抓 bug 只是附帶好處。所以更該 Test First/During,因為你要的是那個「設計回饋」,而那只有在寫 code 之前或同時思考測試才拿得到。

⚠️ 安全那節別只當清單念。串起它的精神是第四章「務實的偏執」的延伸(你寫不出完美的軟體 → 你也防不住所有攻擊 → 所以縮小攻擊面、降低權限、預設安全)。特別強調「永遠別自己做加密/登入」——這是聽眾最容易犯、後果最嚴重的英雄主義。

專家補充

💡 「測試是 code 的第一個使用者」其實是 TDD(測試驅動開發)最深刻的論據,比「TDD 能抓 bug」更根本。Kent Beck 推 TDD 的核心也是「測試先行會逼出可測、低耦合的設計」。但本書誠實地補了一刀警告:別被綠燈引誘,寫一堆通過的測試卻沒更接近解答——TDD 需要你知道方向,否則會原地打轉。這個平衡的態度(既推 TDD 又提醒它的陷阱)正是「務實」的精髓。

💡 屬性測試(property-based testing)源自 Haskell 的 QuickCheck(Claessen & Hughes, 2000),現在各語言都有移植(Python 的 Hypothesis、JS 的 fast-check、JVM 的 jqwik)。它與第四章合約式設計(EP5)是同一條線的兩端——合約定義「屬性」,屬性測試自動驗證這些屬性。本書把這個原本小眾的學術技術,放進「每個務實程式設計師都該會」的工具箱,是很前瞻的選擇。

💡 命名是三經典共同的執念。《Clean Code》整章談命名(名稱要回答「為何存在、做什麼、怎麼用」),本書則更強調「按角色命名 + 尊重團隊文化 + 需要就改名」。有句業界老笑話:「電腦科學只有兩件難事——快取失效、命名、和 off-by-one error。」命名難,是因為它逼你想清楚「這東西到底是什麼」——命名困難往往是「設計還沒想清楚」的徵兆。這個洞見比任何命名規則都深,也呼應本集「測試逼出設計」「屬性逼你想清不變量」的共同主題:好的外部約束,會反過來改善你的內在設計。

討論問題

🎙️ 開場鉤子:「為什麼要寫測試?大部分人會說『確保 code 能跑』。這集作者直接說:錯。測試最值錢的地方,是你還沒寫 code、光是在想怎麼測它的那一刻——因為那一刻,它會逼你把設計想清楚。」

🎙️ 自問自答:「自己寫 code 又自己寫測試,會不會兩邊一起錯?」——會,而且這是單元測試的死角。解法是讓不帶你成見的電腦來測——屬性測試用上百組隨機輸入去戳你的假設,常常揪出你連想都沒想到的 bug。

🎙️ 帶走的一題:「找一個你『自己手刻過』的安全相關東西——密碼雜湊、token 驗證、登入流程。問自己:我有把握這比一個專門做認證的第三方更安全嗎?如果沒有,這週把它換掉。」

更大範圍關聯

  • 與三經典:命名與《Clean Code》整章命名互文;測試觀與《Refactoring》(重構必須有測試護網)、Kent Beck《Test-Driven Development》一脈。本書的獨特貢獻是把「測試=設計回饋」這個論點講得最透。
  • 與合約式設計:屬性測試是第四章 DBC(EP5)的自動化驗證手段——合約定義屬性,屬性測試驗證屬性,兩集配著聽最完整。屬性測試源頭是 Haskell QuickCheck。
  • 與務實的偏執:安全章是第四章「務實的偏執」(EP5)在資安維度的延伸——同一個「你防不住所有壞事,所以主動防禦」的世界觀。接 OWASP Top 10、《Threat Modeling》、最小權限原則(Saltzer & Schroeder 1975 經典論文)。
  • 首尾呼應:「測試永遠綠燈、別容忍永遠失敗的測試」直接回扣第一章破窗理論(EP1)——測試套件也是會破的窗。

錄製建議

  • 建議時長:約 25-27 分鐘(四個 Topic、安全一節資訊量大)。配比:測試的意義約 7 分(「測試不是找 bug/第一個使用者」是反直覺主菜)、屬性測試約 6 分(倉庫 bug 故事是亮點)、安全約 8 分(五原則 +「別自己做加密」,可條列但帶精神)、命名約 5 分(範例對照好講、孔子引言收尾)。
  • 討論策略:用一條「好的外部約束會改善內在設計」的主線串起來——測試逼出低耦合、屬性逼你想清不變量、安全逼你縮小介面、命名逼你想清角色。四者都在說:當你被迫從外面看自己的 code,設計就會變好。安全那節務必把「別當英雄自己做加密」這條最高 ROI 的提醒講重一點。
  • 這集收掉最厚的第七章,可在結尾預告:接下來離開「個人編碼」,進入「專案」層級——EP10「專案啟動前」談需求、不可能的謎題與敏捷的本質。