軟體設計的核心目標是降低複雜度。要對付敵人,得先認得它。複雜度不是某一顆巨石,而是**依賴(dependencies)與模糊(obscurity)**日積月累的結果——累積到一定程度,就以三種症狀現身:變更放大、認知負擔、未知的未知,最終讓修改既有程式碼變得困難又高風險。
🧠 複雜度長什麼樣(症狀)
複雜度不必抽象地談,它會用三種具體症狀讓你認出來:
- 變更放大 (change amplification):一個看似簡單的改動,卻要在許多地方同步修改。
- 認知負擔 (cognitive load):要完成一項任務,開發者必須先在腦中裝下多少東西。負擔越重,越慢、也越容易出錯。
- 未知的未知 (unknown unknowns):最陰險的一種——你根本不知道「為了正確完成這次修改,還有哪些地方該動、哪些事該知道」。
NOTE
辨識複雜度是設計者最關鍵的能力之一:能在投入大量心力之前就提早察覺問題,也能在多個替代方案間做出較好的判斷。關鍵洞見是——判斷一個設計是否簡單,比親手寫出簡單的設計來得容易;而這份判斷力反過來會引導你的設計哲學朝簡潔靠攏。
⚖️ 複雜度從哪來(兩個成因)
複雜度源自兩件事的累積:依賴與模糊。
依賴 (dependency) — 一段程式無法被單獨理解或修改
當一段程式碼要正確運作,必須配合另一段程式碼時,兩者之間就存在依賴。依賴讓你無法只盯著局部就改對,也正是變更放大的來源。
// 同一個「稅率」概念散落在三處:彼此隱性依賴
function priceWithTax(p) { return p * 1.05; }
function shippingWithTax(s) { return s * 1.05; }
function refundTax(amount) { return amount - amount / 1.05; }
// 稅率一改 5% → 8%,三個函式都得同步動手,漏一個就出錯改一件事卻要動三個地方——這就是依賴帶來的變更放大。
模糊 (obscurity) — 重要資訊沒有擺在明顯處
當一段程式的重要資訊不明顯時,就產生模糊。讀者看不出該知道什麼,於是留下未知的未知。
// t 的單位是什麼?毫秒?秒?呼叫端無從得知
function retry(fn, t) {
setTimeout(fn, t);
}
// 呼叫端只能猜,猜錯了也不會有人告訴你
retry(sync, 30); // 30 毫秒?還是想寫 30 秒?當「該知道的事」沒有寫在明顯的地方,改動就變成一場賭博。
TIP
兩個成因彼此呼應:依賴大多引發變更放大,模糊大多引發未知的未知,而兩者都會加重認知負擔。想降低複雜度,就是設法減少依賴、消除模糊。深模組是達成這件事的重要手段——見 深模組 (Deep Modules)。
漸進累積:複雜度不是一次造成的
複雜度很少是某個災難性決策一次鑄成的,而是一點一滴累積起來的:每次為了趕工多加一個依賴、每次省略一段本該寫清楚的資訊,單看都微不足道,累積起來卻讓系統逐漸難以理解與修改。
IMPORTANT
正因為複雜度是漸進累積的,就沒有哪一次改動可以「事後一次清乾淨」。每一次小的取捨都算數——這也是為什麼要在每次改動當下就守住簡潔。
🔑 Takeaways
- 複雜度的症狀有三:變更放大、認知負擔、未知的未知;認得症狀,才認得複雜度。
- 複雜度的成因有二:依賴(→ 變更放大)與模糊(→ 未知的未知),兩者都加重認知負擔。
- 判斷簡不簡單,比寫出簡單來得容易;用這份判斷力提早察覺問題、在方案間取捨。
- 複雜度是漸進累積的,沒有事後一次清乾淨的機會——每次小取捨都算數。
No notes yet — jot your takeaways or Q&A here.