用最少的程式碼解決問題。不要任何推測性內容。
問問自己:「資深工程師會說這個太複雜了嗎?」如果是,簡化它。
LLM 在訓練過程中學到了大量「設計模式」和「最佳實務」——策略模式、依賴注入、觀察者模式等等。當它收到「加入折扣計算」的需求時,會自動聯想到「折扣有多種類型」→「用策略模式」→「建立完整的抽象層」。
問題在於:時機。過早引入複雜度會讓程式碼更難理解、更難測試、更容易出錯。好的程式碼是簡潔解決今日問題的程式碼,不是過早解決明日問題的程式碼。
關鍵心態:把「簡單」當作專業的象徵,而不是偷懶。資深工程師的標誌不是寫出複雜的架構,而是用最簡單的方式解決問題。
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, amount: float) -> float: pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percentage: float): ...
class FixedDiscount(DiscountStrategy):
def __init__(self, fixed_amount: float): ...
# ... 策略模式 + 設定物件 + 計算器 = 100+ 行
def calculate_discount(amount: float, percent: float) -> float:
"""Calculate discount amount. percent should be 0-100."""
return amount * (percent / 100)
# 使用
discount = calculate_discount(100.0, 10.0) # 折抵 $10
到互動範例頁看這個原則的 ❌/✅ 對照:
class SubscriptionPlan(ABC):
@abstractmethod
def price(self, base: float) -> float: ...
class MonthlyPlan(SubscriptionPlan): ...
class YearlyPlan(SubscriptionPlan): ...
class PlanFactory:
@staticmethod
def create(kind: str, config: PlanConfig) -> SubscriptionPlan: ...
class BillingService:
def __init__(self, plans: dict, prorater, invoicer, taxer): ...
# 100+ 行:抽象類別、工廠、設定物件、折抵引擎……
# 只為了算出「年付 = 月付 × 12 × 0.8」。
「支援年付」的最簡詮釋:給定方案與期間,算出價格。
def price(plan: str, base: float) -> float:
"""plan: 'monthly' | 'yearly'. Yearly gives 20% off."""
if plan == 'yearly':
return base * 12 * 0.8
return base
# 使用
monthly = price('monthly', 10.0) # 10.0
yearly = price('yearly', 10.0) # 96.0
決策紀錄(說給使用者聽):
- 沒有建立 Plan 類別:因為目前只有兩種字串,if 就夠。
- 沒有抽象層:因為「第二種付款維度」(例如一次買 3 年)還沒出現。
- 折扣數字寫死在函式裡:因為折扣策略是業務規則,不是可配置基礎設施。
- 什麼時候重構:當第 3 種方案出現、或折扣邏輯超過一行時,再提抽象。
模型的訓練資料大量來自含設計模式的程式碼——開源專案、Stack Overflow、教學文章。這些資料的統計模式是:需求越抽象,模型就越傾向用「專業架構」回應。策略模式、依賴注入、工廠——模型不是「思考後選擇」這些模式,而是它們在訓練資料裡與「商業規則需求」高度共現,所以生成時被優先喚出。這叫統計性過度設計:模型在複製「看起來資深」的形狀,而不是「解決問題所需的形狀」。
200 行的預設值:模型還有「擴充偏誤」——RLHF 評分時,完整的、考慮周到的答案得分更高,於是「加更多參數、更多防禦、更多功能」成為默認行為。對模型而言,寫少是一件需要被特別要求的事。
Token 經濟的誘因:append 比 revise 便宜。模型可以一直「加」下去而不會推翻自己;而「重寫得更短」需要先刪除再重建——那是更高階的生成任務。所以簡潔不是模型的舒適區,是使用者的紀律。
「簡潔」最容易被誤解成「短」。一個 Agent 收到「計算平均分」的需求,把 12 行清楚的迴圈壓成一行多層嵌套的 lambda——字元少了,但認知負擔暴增:
avg = (lambda s: (lambda t: t/len(s))(sum(s)))(scores)
這不是簡潔,是混淆。原則 2 定義的成功標準是「資深工程師 30 秒內讀懂」與「最少的不必要認知負擔」,不是「最少的字元數」。簡潔是刪掉不必要的部分,不是刪掉必要的可讀性。
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| 收到「加入折扣」就建了 5 個 class | 設計模式與商業需求的統計共現被觸發 | 要求「先寫最小實作,標為 v1」,並禁止在本輪加入抽象 |
| 函式有 8 個參數、全帶預設值 | 推測性可配置性——為「可能的呼叫者」留後路 | 砍到需求所需參數;其餘等真的出現第二個呼叫者再加 |
| 為「未來擴展」建立 interface/基底類別 | 過早抽象——誤把「可能」當「必然」 | 套用「第二次使用」法則:抽象必須等到第二個真實使用者出現才值得 |
| 200 行程式碼,功能其實只要 50 行 | 防禦性編程+重構衝動疊加 | 重寫為最小版本;同時用原則 3 提醒自己別動無關的既有程式碼 |
| 一行看不懂的 lambda/三元運算式串 | 把「短」誤當成「簡潔」 | 改用可讀性標準:資深工程師能否在 30 秒內講出它的行為?不能就拆開 |
先確認: 1. **範圍**:每個端點獨立限制?還是全站共用? 2. **策略**:固定窗口?滑動窗口?令牌桶? 3. **後端**:單機記憶體夠?還是需要 Redis? 4. **回應**:超限回傳什麼?429 + Retry-After? 5. **白名單**:內部服務要豁免嗎? → 確認:固定窗口、單機記憶體、全站 1000 req/min。
# 30 行,不超過
from time import time
class RateLimiter:
def __init__(self, max_requests=1000, window=60):
self.max_requests = max_requests
self.window = window
self.requests = []
def allow(self):
now = time()
self.requests = [t for t in self.requests if now - t < self.window]
if len(self.requests) < self.max_requests:
self.requests.append(now)
return True
return False
def rate_limit middleware(app, limiter):
@app.before_request
def check():
if not limiter.allow():
abort(429, headers={'Retry-After': '60'})
# 先不做:Redis 後端、滑動窗口、每個端點獨立限制、設定檔。
# 為什麼不做?因為現在的需求是「全站 1000/min」,上面 30 行就夠了。
新增:rate_limiter.py(30 行) 修改:app.py(加一行 import + 掛載 before_request) 刻意沒動的: - app.py 的既有路由格式 — 單引號雙引號不一致?不改。 - config.py 的結構 — 看到可以更優雅?不動。 - tests/ 裡「看起來沒覆蓋到」的舊測試 — 不碰。
計畫與驗證: 1. 實作 RateLimiter 類別 驗證:寫 3 個單位測試(允許、滿額、過期重置)全綠 2. 掛載中介層 驗證:curl 發 1001 個請求 → 第 1001 個回傳 429 3. 驗證 Retry-After header 驗證:429 回應包含 Retry-After: 60 4. 既有測試 驗證:完整套件全綠,無回歸 完成標準:4 步全部通過 + 手動 curl 驗證。
效能:過度工程直接消耗效能。每一層抽象都是一次間接呼叫;每一個不需要的參數都是一次分支預測失敗;每一個不需要的 class 都是一次記憶體配置。例如:策略模式的 5 個 class 在 hot path 上比一個 if-else 慢 3-5 倍——不是因為策略模式差,而是因為在此情境下它是多餘的。簡潔不只省行數,也省 CPU。
品質:複雜度是品質的頭號殺手。更多行數 = 更多 bug 容身之處;更多抽象 = 更多需要理解的介面;更多參數 = 更多組合需要測試。心理學研究顯示:人類短期記憶上限約 7±2 項——一個 10 參數的函式已經超出大多數人的理解能力。品質不是「功能完整」,而是「能在 30 秒內被理解」。
安全:過度工程引入不必要的攻擊面。例如:為了一個簡單的設定檔加上 YAML 解析 + 動態 import + plugin 機制——YAML 解析器是已知的反序列化漏洞來源(CVE 數以百計),動態 import 可被用來執行任意程式碼。每一行不需要的程式碼都是一個不需要的漏洞入口。安全的最佳實踐不是「加更多防禦」,而是「少寫程式碼」。
Agent 收到「支援可擴充的折扣規則」需求,建立了 YAML 設定檔 + 動態 import 的 plugin 系統。攻擊者上傳了一份含 !!python/object/apply:os.system ['rm -rf /'] 的折扣規則 YAML——伺服器被遠端執行任意命令。如果一開始只用 if/elif 判斷折扣類型,這條攻擊路徑根本不存在。
| 面向 | 原則 2 · 簡潔優先 | 相關原則 | 差異說明 |
|---|---|---|---|
| 核心動作 | 砍掉不必要的程式碼,只留「今天需要的」 | 原則 3(精準修改) | 兩者都反對「多做」:原則 2 反對的是「做多的抽象」(過度設計),原則 3 反對的是「改多的範圍」(順帶重構)。一個管「寫什麼」,一個管「動什麼」。 |
| 判斷標準 | 「資深工程師會說太複雜了嗎?」 | 原則 1(編碼前思考) | 原則 1 在動手前問「是不是做對的事」,原則 2 在動手時問「是不是用最簡單的方式做」。前者管方向,後者管效率。 |
| 對抗的偏誤 | 統計性過度設計——模型複製「看起來資深」的形狀 | 原則 4(目標驅動) | 原則 2 反對「多做」,原則 4 反對「做了不知道對不對」。兩者搭配:用最少的程式碼(2),每一步都可驗證(4)。 |
| 重構時機 | 等到第二個真實使用者出現才值得抽象 | 原則 3(精準修改) | 原則 2 定義「什麼時候值得重構」;原則 3 確保「重構時只動必要的」。兩者共同控制重構的「觸發條件」與「執行邊界」。 |