務實程式設計(The Pragmatic Programmer):高手工程師不是知道最多工具,而是更會做取捨

務實程式設計(The Pragmatic Programmer):Andrew Hunt 與 David Thomas 提出的一套工程思維。它不把「高手」定義成記住最多工具或框架,而是能看懂情境、評估取捨、降低耦合、控制變更風險,並持續讓系統保持可修改、可理解與可回復。

工程能力很容易被誤解成「知道很多技術」。

會幾種語言、熟幾套框架、記得多少設計模式,當然都有幫助;但進入複雜系統之後,難題很快會從「這個語法怎麼寫」變成:

  • 這個決定要不要現在做?
  • 這個抽象會不會綁死未來?
  • 這份邏輯應不應該共用?
  • 這個估算到底能不能信?
  • 這個架構改動如果失敗,退得回來嗎?

《The Pragmatic Programmer》的價值,就在於它把這些日常判斷整理成一套可重複使用的習慣。

目錄 隱藏

第一個核心:DRY 不是「看到重複程式碼就抽掉」

DRY(Don’t Repeat Yourself)最常被簡化成「不要寫重複程式碼」,但書裡更重視的是知識不要重複。

如果同一個商業規則同時寫在前端驗證、後端服務、資料庫 Trigger 與文件裡,未來規則改變時,就要同步修改多個地方。只要漏掉一個,就會產生不一致。

這才是 DRY 想避免的問題。

看起來一樣的程式碼,不一定代表同一份知識

兩段程式長得很像,不代表一定要抽成共用函式。

如果它們分別代表不同商業概念,只是現在剛好計算方式一樣,過早共用反而會把兩個未來可能分開演進的規則綁在一起。

所以 DRY 的判斷重點,是「這兩段是不是在表達同一個知識來源」;文字相似只能當線索。

第二個核心:正交性讓改動留在局部

正交性(Orthogonality)可以理解成:修改 A,不應該莫名其妙讓 B 壞掉。

例如換資料庫,不應該改到商業規則;換 Email 供應商,不應該改到訂單計算;換前端框架,也不應該重寫核心 Domain。

這種獨立性會直接降低 Blast Radius(影響範圍)。

如果一個小改動總要碰很多不相關模組,通常代表耦合太高。

全域狀態很方便,也很容易破壞正交性

Global State 的風險,在於所有人都可以偷偷依賴它;它並非絕對不能使用。

當一個函式的結果受到全域變數影響,你只看函式參數已經無法理解完整行為。

依賴愈隱藏,測試愈困難,修改也愈難預測。

這也是為什麼 Dependency Injection、明確參數與清楚邊界,常常比「到處都能直接拿」更容易維護。

第三個核心:先打一發曳光彈,再把整套系統做完

曳光彈(Tracer Bullets)是一個很實用的開發思維。

當需求、架構或整合方式還不確定時,不要先把每一層都做完整;先做一條很薄、但真的能從頭走到尾的 Vertical Slice(垂直切片)。

例如一個新系統,可以先完成:

畫面 → API → 商業邏輯 → 資料庫 → 回傳結果

功能可能很少,但整條路是真的。

曳光彈和原型(Prototype)不一樣

原型通常是為了驗證某個風險,可以丟掉。

曳光彈則是正式系統的第一條細路,之後會繼續擴建。

方式 目的 之後保留嗎?
原型 探索未知、驗證技術或互動 通常不保留
曳光彈 建立最小但完整的端到端路徑 會繼續演進

這個差異很重要,因為最危險的情況,是本來說要丟掉的 Prototype 最後直接變成 Production Code。

先做一條真路,比先把每一層做滿更早發現問題

如果團隊先把 Database Layer 做三週、API 再做三週、前端最後才接,很可能兩個月後才發現整合方式根本不合理。

曳光彈的價值,是把 Integration Risk 提前。

你會更早知道資料模型夠不夠用、API 契約合理不合理、前端實際需要什麼,以及部署流程能不能工作。

第四個核心:契約式設計讓「假設」變成明確規則

契約式設計(Design by Contract)會把模組之間的責任說清楚。

常見有三種:

  • 前置條件(Precondition):呼叫之前必須成立。
  • 後置條件(Postcondition):執行完成後保證成立。
  • 不變條件(Invariant):任何時候都必須成立。

例如年齡不能是負數、付款完成後訂單一定有交易編號、帳戶餘額不能落到系統不允許的狀態。

如果這些條件只存在工程師腦中,系統就會依賴默契。

該失敗時要早點失敗

當系統進入不可能狀態時,繼續帶著錯誤資料往下跑,通常比立刻停止更危險。

所以斷言(Assertion)適合處理「理論上絕不該發生」的情況;一般錯誤處理則用來處理真的可能發生的外部事件。

這個界線可以讓錯誤更靠近原因被發現,而不是幾個步驟之後才以奇怪症狀爆出來。

第五個核心:不要放著破窗不管

破窗效應(Broken Windows)放到軟體裡,就是「反正這裡本來就很亂,再多一點也沒差」。

第一個臨時 Hack 留下來之後,下一個人會更容易再加第二個。

久了之後,TODO 越來越多、命名開始隨便、測試被略過、規則開始重複,大家也逐漸接受「這裡本來就不能動」。

品質下降往往來自很多小妥協累積,未必需要一次大事故才發生。

現在修不了,也要把破窗「封起來」

有些問題確實不能立刻解。

這時至少要建立 Ticket、寫清楚風險、隔離問題,並避免新功能繼續複製同一做法。

如果你正在處理這種累積很久的設計問題,可以延伸看技術債怎麼還?不要大重寫,而是讓舊系統一點一點重新變得安全。

第六個核心:把不可逆決定和可逆決定分開

不是每個架構決策都值得花一樣多時間。

按鈕文字、Feature Flag、內部工具通常比較容易改;核心資料模型、Vendor Lock-in、長期公開 API、跨系統身分架構則可能很難回頭。

務實的做法,是把更多思考留給難以回頭的決定。

可逆性(Reversibility):避免把自己鎖死

可逆性並不表示所有東西都要抽象成 Framework。

如果沒有證據顯示某個供應商會替換,先建立十層 Adapter 也可能只是 Over-engineering。

比較務實的做法是:在容易產生高成本鎖定的邊界保留替換空間。

例如商業邏輯依賴自己的 PaymentGateway 介面,而不是把某家付款 SDK 的型別一路傳進 Domain。

把可逆性留給高成本決策

要不要保留替換空間,可以先問三個問題:

  • 這個供應商或技術未來有沒有合理的替換可能?
  • 一旦要換,會影響多少核心程式?
  • 現在多做一層邊界的成本高不高?

替換可能高、未來代價大,而且現在隔離成本低,通常很值得先保留介面。

反過來,如果只是為一個幾乎不會改的小工具建立複雜抽象,就可能是把「彈性」做成新的負擔。

這和軟體設計哲學裡的「把複雜藏起來」是同一方向

如果一個 Vendor、Framework 或 Database 的細節散到整個系統裡,未來替換成本自然會很高。

這和軟體設計哲學:真正好的模組,應該把複雜藏在裡面提到的資訊隱藏很接近:變動細節應該被限制在少數邊界。

第七個核心:估算不要假裝自己能預言

工程估算最常見的問題,是給出一個過度精準的單點答案。

「兩週完成」聽起來很確定,但如果需求還沒完全拆開、第三方 API 還沒驗證,這個數字只是把不確定性藏起來。

比較健康的做法是先問這個估算要拿來做什麼,把工作拆小,用範圍表達,再把主要風險一起寫出來。

估算的能力,來自持續校正

如果每次估完就忘記,估算能力很難變好。

可以保留簡單紀錄:

項目 原估算 實際 差異原因
付款整合 3~5 天 7 天 第三方文件不足
會員功能 2~3 天 2 天 已有可重用模組

幾次之後,你會開始知道自己在哪一類工作上特別容易低估,也會知道哪些未知因素最常造成偏差。

範圍估算比「假精準」更能建立信任

「大約 3~5 天,如果第三方 API 沒有額外限制」通常比「四天一定做完」更誠實。

用範圍表達,是把不確定性顯性化,不等於逃避承諾。

當風險被驗證後,範圍可以再縮小;這比一開始給出看似精準、實際沒有根據的數字更容易管理期待。

領域語言(Domain Languages):讓程式更接近問題本身

工程師和業務如果對同一件事使用完全不同的詞,誤解很容易進到程式裡。

例如業務說「續約」、程式裡卻把它叫成一個含糊的 status_7;產品說「取消資格」,程式裡卻分散成三種布林值。每次溝通都需要重新翻譯。

更好的做法,是讓重要的 Domain 概念直接出現在:

  • 類別名稱。
  • 方法名稱。
  • 資料欄位。
  • 事件名稱。
  • 測試案例。

當程式語言和業務語言逐漸對齊,工程師就能用更少轉譯理解需求,非工程角色也更容易參與規則討論。

工具箱要擴充,但不要讓工具替你做判斷

務實工程師會持續學新的 Editor、CLI、Debugger、語言與自動化工具,因為工具會放大產能。

但工具不會替你回答「這個抽象值得嗎」「這個決定能不能回頭」「這個需求是不是應該做」。

同一個工具,在不同情境可能是最佳解,也可能只是增加複雜度。

所以工具熟練度很重要,但更長期的能力仍然是判斷:知道什麼時候用、什麼時候不用,以及使用後付出了什麼代價。

知識組合(Knowledge Portfolio):工程師也要管理自己的學習資產

工具會過時,原則相對耐久。

所以學習不能只追目前公司正在用的 Stack。

可以像投資組合一樣:

  • 持續投資,不要一年只學一次。
  • 不要所有能力都押在同一個框架。
  • 同時理解程式、架構、資料、產品與 Domain。
  • 偶爾碰一點新技術,但不要全部押在熱門題材。

這會讓工程師在技術環境變化時更有選擇。

「關心你的工藝」最後會落在日常小習慣

《The Pragmatic Programmer》不是要求工程師追求完美。

它比較像是在問:

  • 今天看到重複知識,有沒有處理?
  • 這個決定有沒有留下回頭路?
  • 這個模組是不是和不相關的東西綁在一起?
  • 這個估算有沒有誠實表達不確定性?
  • 這個小 Hack 會不會變成下一扇破窗?

這些決定每一個都很小,但幾年後會直接決定系統好不好改。

一張表快速檢查你的工程習慣

檢查問題 如果答案是否定
同一個商業規則只有一個權威來源嗎? 檢查 DRY
換資料庫時,商業邏輯可以不動嗎? 檢查正交性
新系統已經有一條端到端路徑嗎? 建立曳光彈
關鍵假設有被明確驗證嗎? 補契約或斷言
技術債有被追蹤,而不是放著嗎? 處理破窗
高成本決定有留下可逆空間嗎? 檢查耦合
估算有範圍與風險說明嗎? 停止假精準

常見問題

不要重複知識(DRY)是不是代表所有重複程式碼都要抽掉?

不是。DRY 主要針對重複知識。兩段程式看起來一樣,但代表不同商業概念時,強行共用反而會增加耦合。

正交性是不是代表模組完全不能互相依賴?

不是。大型系統一定有依賴。重點是依賴要清楚、方向合理,而且不相關的變更不要互相影響。

曳光彈和 MVP 有什麼不同?

曳光彈偏向技術與端到端整合路徑;MVP 偏向產品價值驗證。兩者都追求快速回饋,但焦點不同。

原型(Prototype)可以直接拿去正式環境嗎?

如果一開始明確把它當拋棄式原型,就不應該直接升級成正式產品。需要重新用正式品質標準實作。

可逆性是不是代表所有外部服務都要加一層 Wrapper?

不一定。先看替換成本和實際風險。高鎖定、高影響的依賴更值得建立清楚邊界。

工程師應該花多少時間學新東西?

沒有固定比例。重點是持續、有節奏,而且不要只在工作逼到才學。小額、長期、分散的學習通常比偶爾一次大量吸收更穩定。

務實工程師的優勢,是知道怎麼判斷

技術世界永遠會有新語言、新框架、新工具。

如果能力只建立在「我熟這套工具」,工具變了就很容易失去優勢。

DRY、正交性、曳光彈、契約式設計、破窗效應、可逆性與估算,提供的是更底層的判斷方法。

它們幫助你在每一次小決定裡回答:這個設計會不會更容易改?風險有沒有被提早看見?這個選擇還能不能回頭?這份複雜度到底值不值得?

當這些問題變成習慣,工程能力才不會只跟著工具版本更新,而是能隨著經驗持續累積。

參考資料

Andrew Hunt、David Thomas:The Pragmatic Programmer。

元氣站長
元氣站長

經營網路事業,賺取持續收入!
網路賺錢修煉,成就理想人生!