工程能力很容易被誤解成「知道很多技術」。
會幾種語言、熟幾套框架、記得多少設計模式,當然都有幫助;但進入複雜系統之後,難題很快會從「這個語法怎麼寫」變成:
- 這個決定要不要現在做?
- 這個抽象會不會綁死未來?
- 這份邏輯應不應該共用?
- 這個估算到底能不能信?
- 這個架構改動如果失敗,退得回來嗎?
《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。
