互動範例

九個真實案例,❌ 常見錯誤對照 ✅ 正確做法,每個都點開看看差在哪
範例取材自 EXAMPLES.md——反模式的可怕之處是它們看起來都「很專業」
教學解說:如何使用這些範例

每個範例都包含三個部分:❌ 常見錯誤(AI 直覺反應)、✅ 正確做法(運用四原則後的做法)、以及關鍵差異(為什麼差這麼多)。

使用方式:先點「❌ 常見錯誤」,看看這是不是你平常看到的 AI 輸出。然後切換到「✅ 正確做法」,比較兩者的差異。最後看「關鍵差異」,理解為什麼正確做法更好。

反模式的可怕之處: ❌ 的程式碼看起來都很「專業」——有完整的抽象層、完整的錯誤處理、完整的文檔。但問題在於時機和必要性。

原則 1 · 編碼前思考 — Think Before Coding
隱藏假設
使用者需求:「加入匯出使用者資料的功能」
def export_users(format='json'):
    """Export all users to a file."""
    users = User.query.all()

    if format == 'json':
        with open('users.json', 'w') as f:
            json.dump([u.to_dict() for u in users], f)
    elif format == 'csv':
        with open('users.csv', 'w') as f:
            writer = csv.DictWriter(f, fieldnames=['id', 'email', 'name'])
            writer.writeheader()
            writer.writerows([u.to_dict() for u in users])

    return f"Exported {len(users)} users"
問題在哪
  • 假設要匯出所有使用者(分頁呢?隱私呢?)
  • 未經詢問就假設檔案存放位置
  • 假設了要包含哪些欄位
  • 未檢查實際資料結構就假設了 CSV 的欄位名稱
在實作之前,我需要釐清:

1. **範圍**:匯出所有使用者還是篩選後的子集?(隱私考量)
2. **格式**:你說的「匯出」是指:
   - 瀏覽器下載檔案?
   - 背景任務搭配電子郵件通知?
   - 回傳資料的 API 端點?
3. **欄位**:哪些使用者欄位?(有些可能涉及敏感資訊)
4. **資料量**:通常有多少使用者?(會影響做法)

最簡單的做法:加入一個回傳分頁 JSON 的 API 端點。
如果是檔案匯出則需要更多資訊。

您偏好哪一種?
關鍵差異
  • 在實作之前列出所有假設,並請求釐清
  • 主動提出最簡單的作法,把選擇權交給使用者
多重解讀
使用者需求:「讓搜尋更快」
# 在不詢問哪種「更快」重要的情況下,
# 直接加入快取、資料庫索引和非同步處理
@lru_cache(maxsize=1000)
async def search(query: str) -> List[Result]:
    # ... 200 行最佳化程式碼
問題在哪
  • 不問「哪種更快重要」就默默選擇一種解釋
  • 一次做了三種截然不同的最佳化(快取、索引、非同步)
「讓搜尋更快」可能意味著:

1. **更快的回應時間**(從 500ms 降到 100ms 以下)
   - 加入資料庫索引
   - 快取常用查詢
   - 預估工時:2 小時

2. **處理更多並發搜尋**(吞吐量)
   - 加入 async/await
   - 連線池
   - 預估工時:4 小時

3. **更快的感知速度**(使用者體驗)
   - 立即顯示部分結果
   - 漸進式載入
   - 預估工時:3 小時

目前典型查詢約需 500ms。
哪個面向對您的使用案例最重要?
關鍵差異
  • 列出三種可能的「更快」、各自作法與預估工時
  • 把選擇權交給使用者,而不是默默挑一個
原則 2 · 簡潔優先 — Simplicity First
過度抽象
使用者需求:「加入計算折扣的函式」
from abc import ABC, abstractmethod
from enum import Enum
from typing import Protocol, Union
from dataclasses import dataclass

class DiscountStrategy(ABC):
    @abstractmethod
    def calculate(self, amount: float) -> float:
        pass

class PercentageDiscount(DiscountStrategy):
    def __init__(self, percentage: float):
        self.percentage = percentage

    def calculate(self, amount: float) -> float:
        return amount * (self.percentage / 100)

class FixedDiscount(DiscountStrategy):
    def __init__(self, fixed_amount: float):
        self.fixed_amount = fixed_amount

    def calculate(self, amount: float) -> float:
        return min(self.fixed_amount, amount)

@dataclass
class DiscountConfig:
    strategy: DiscountStrategy
    min_purchase: float = 0.0
    max_discount: float = float('inf')

class DiscountCalculator:
    def __init__(self, config: DiscountConfig):
        self.config = config

    def apply_discount(self, amount: float) -> float:
        if amount < self.config.min_purchase:
            return 0
        discount = self.config.strategy.calculate(amount)
        return min(discount, self.config.max_discount)

# 使用時需要 30 多行的設定才能做一個簡單的計算
問題在哪
  • 為了單一折扣計算引入策略模式 + 設定物件
  • 使用時需要 30 多行設定才能做一個簡單的計算
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 PreferenceManager:
    def __init__(self, db, cache=None, validator=None):
        self.db = db
        self.cache = cache or InMemoryCache()
        self.validator = validator or DefaultValidator()

    def save(self, user_id: int, prefs: dict,
             merge: bool = True,
             validate: bool = True,
             notify: bool = False) -> bool:
        """Save preferences with optional merging, validation, and notifications."""

        if validate:
            errors = self.validator.validate(prefs)
            if errors:
                raise ValidationError(errors)

        existing = self.get(user_id) if merge else {}

        if merge:
            existing.update(prefs)
            prefs = existing

        self.db.execute(
            "UPDATE users SET preferences = ? WHERE id = ?",
            (json.dumps(prefs), user_id)
        )

        if self.cache:
            self.cache.set(f"prefs:{user_id}", prefs)

        if notify:
            self.notify_preference_change(user_id, prefs)

        return True

    def notify_preference_change(self, user_id: int, prefs: dict):
        # 又是 30 行沒人要求的功能
        pass
問題在哪
  • 加入了快取、驗證、合併、通知等沒人要求的選項
  • notify_preference_change 又是 30 行沒人要求的功能
def save_preferences(db, user_id: int, preferences: dict):
    """Save user preferences to database."""
    db.execute(
        "UPDATE users SET preferences = ? WHERE id = ?",
        (json.dumps(preferences), user_id)
    )
關鍵差異
  • 只做被要求的事:把偏好存進資料庫
  • 快取(效能出問題時)、驗證(錯誤資料時)、合併(需求出現時)留到以後需要再加
原則 3 · 精準修改 — Surgical Changes
順帶重構
使用者需求:「修復空郵件會讓驗證程式崩潰的錯誤」
   def validate_user(user_data):
-      # Check email format
-      if not user_data.get('email'):
+      """Validate user data."""
+      email = user_data.get('email', '').strip()
+
+      # Validate email
+      if not email:
             raise ValueError("Email required")
-
-      # Basic email validation
-      if '@' not in user_data['email']:
+      if '@' not in email or '.' not in email.split('@')[1]:
             raise ValueError("Invalid email")

-      # Check username
-      if not user_data.get('username'):
+      # Validate username
+      username = user_data.get('username', '').strip()
+      if not username:
             raise ValueError("Username required")
+      if len(username) < 3:
+          raise ValueError("Username too short")
+      if not username.isalnum():
+          raise ValueError("Username must be alphanumeric")

        return True
問題在哪
  • 「改進」了錯誤修復之外的郵件驗證邏輯
  • 加入了沒人要求的使用者名稱驗證
  • 修改了註解
  • 加入了 docstring
   def validate_user(user_data):
       # Check email format
-      if not user_data.get('email'):
+      email = user_data.get('email', '')
+      if not email or not email.strip():
             raise ValueError("Email required")

       # Basic email validation
-      if '@' not in user_data['email']:
+      if '@' not in email:
             raise ValueError("Invalid email")

       # Check username
       if not user_data.get('username'):
           raise ValueError("Username required")

       return True
關鍵差異
  • 只改了修正空郵件處理的特定行
風格偏差
使用者需求:「在上傳函式中加入日誌記錄」
- def upload_file(file_path, destination):
+ def upload_file(file_path: str, destination: str) -> bool:
+     """Upload file to destination with logging."""
+     logger.info(f"Uploading {file_path} to {destination}")
+
      try:
-          with open(file_path, 'rb') as f:
-              data = f.read()
-
-          response = requests.post(destination, files={'file': data})
-
-          if response.status_code == 200:
-              return True
-          else:
-              return False
+          with open(file_path, "rb") as f:
+              data = f.read()
+          response = requests.post(destination, files={"file": data})
+          success = response.status_code == 200
+
+          if success:
+              logger.info(f"Upload successful: {file_path}")
+          else:
+              logger.error(f"Upload failed: {file_path}, status={response.status_code}")
+
+          return success
       except Exception as e:
-          print(f"Error: {e}")
+          logger.exception(f"Upload error for {file_path}: {e}")
            return False
問題在哪
  • 修改了引號風格(單引號變雙引號)
  • 加入了沒人要求的型別提示
  • 加入了 docstring
  • 重新格式化了空白
  • 改變了布林回傳邏輯
+ import logging
+
+ logger = logging.getLogger(__name__)
+
    def upload_file(file_path, destination):
+      logger.info(f'Starting upload: {file_path}')
       try:
           with open(file_path, 'rb') as f:
               data = f.read()

           response = requests.post(destination, files={'file': data})

           if response.status_code == 200:
+              logger.info(f'Upload successful: {file_path}')
               return True
           else:
+              logger.error(f'Upload failed: {file_path}, status={response.status_code}')
               return False
       except Exception as e:
-          print(f"Error: {e}")
+          logger.exception(f'Upload error: {file_path}')
            return False
關鍵差異
  • 保留了原有風格:單引號、無型別提示、既有的布林模式、空白風格
原則 4 · 目標驅動執行 — Goal-Driven Execution
模糊 vs 可驗證
使用者需求:「修復驗證系統」
我會這樣修復驗證系統:
1. 檢視程式碼
2. 找出問題
3. 進行改進
4. 測試變更

[在沒有明確成功標準的情況下開始修改]
問題在哪
  • 模糊的做法:沒有可驗證的成功標準就開始修改
要「修復驗證」,我需要定義成功標準。
我們要解決的具體問題是什麼?

如果問題是「使用者變更密碼後仍然保持登入狀態」:

計畫:
1. 撰寫測試:變更密碼 → 驗證舊 session 已失效
   驗證:測試失敗(重現了錯誤)

2. 實作:在密碼變更時使 session 失效
   驗證:測試通過

3. 檢查邊界情況:多個活躍 session、同時變更
   驗證:額外測試通過

4. 驗證無回歸:既有的驗證測試仍然通過
   驗證:完整測試套件全綠

目前驗證的測試覆蓋率:[顯示覆蓋率]
您遇到的具體驗證問題是什麼?
關鍵差異
  • 把「修復」轉成可驗證的目標,每一步都有測試與驗證
  • 反覆驗證直到達成,而不是盲目動手
多步驟搭配驗證
使用者需求:「為 API 加入速率限制」
# 在一份 300 行的提交中實作了完整的速率限制,
# 包含 Redis、多種策略、設定系統和監控
# 沒有明確的驗證步驟
問題在哪
  • 一次到位:300 行完整實作,沒有明確的驗證步驟
速率限制計畫:

1. 加入基本的記憶體內速率限制(單一端點)
   驗證:
   - 測試:100 個請求 → 前 10 個成功,其餘回傳 429
   - 手動:curl 端點 11 次,看到速率限制錯誤

2. 提取為中介層(套用至所有端點)
   驗證:
   - 測試:速率限制套用於 /users 和 /posts
   - 既有的端點測試仍然通過

3. 加入 Redis 後端(用於多伺服器)
   驗證:
   - 測試:應用程式重啟後速率限制仍然持續
   - 測試:兩個應用程式實例共享速率限制計數器

4. 加入設定(每個端點的速率)
   驗證:
   - 測試:/search 允許 10/min,/users 允許 100/min
   - 設定檔解析正確

每個步驟都可以獨立驗證和部署。
從步驟 1 開始?
關鍵差異
  • 漸進式實作,每一步都可獨立驗證和部署
測試先行驗證
使用者需求:「當有重複分數時排序會出錯」
# 未確認錯誤就直接修改排序邏輯
def sort_scores(scores):
    return sorted(scores, key=lambda x: (-x['score'], x['name']))
問題在哪
  • 未先重現錯誤就直接修改排序邏輯
# 1. 首先,撰寫一個重現問題的測試
def test_sort_with_duplicate_scores():
    """Test sorting when multiple items have same score."""
    scores = [
        {'name': 'Alice', 'score': 100},
        {'name': 'Bob', 'score': 100},
        {'name': 'Charlie', 'score': 90},
    ]

    result = sort_scores(scores)

    # 錯誤:重複分數時的順序不確定
    # 執行此測試多次,結果應該一致
    assert result[0]['score'] == 100
    assert result[1]['score'] == 100
    assert result[2]['score'] == 90

# 驗證:執行測試 10 次 → 因順序不一致而失敗

# 2. 現在使用穩定排序來修復
def sort_scores(scores):
    """Sort by score descending, then name ascending for ties."""
    return sorted(scores, key=lambda x: (-x['score'], x['name']))

# 驗證:測試持續通過
關鍵差異
  • 先寫重現問題的測試(執行多次會失敗),再修復
  • 驗證:測試持續通過
進階單元 · Round 1 Deepening
進階 Worked Example:把四原則串成一個完整的大型重構
把上面的九個範例收斂成一個真實工作流——「把通知系統從單體拆成獨立佇列消費服務」。四原則不是四個分開的檢查點,而是一條決策鏈。
階段 0 → 原則 1(編碼前思考):先問,不拆
需求:「把通知拆成佇列消費服務。」

在動任何一個資料夾之前,先輸出:
- 邊界假設:通知的範疇=email + push?webhook 算嗎?
- 一致性假設:通知可以延遲嗎?一定要「發出去」才算成功嗎?
- 成功標準:「拆完」的衡量指標是什麼?
- 我最保守的路徑:先只拆「消費端」,生產端(API)後置。
→ 列完停在這裡,等使用者確認。
階段 1 → 原則 4(目標驅動):鎖定基準線
1. 為現有通知發送寫端點測試
   驗證:測試全綠(這是行為基準線)
2. 定義新服務的驗收標準
   驗證:同一個 email,monolith 與新服務送出內容逐字相同
階段 2 → 原則 2(簡潔優先):最小可運作切片
先不做:重試佇列、死信佇列、儀表板、多 worker 擴展。
先做:一個消費佇列的迴圈 + 呼叫既有的 send_email()。

def consume(queue):
    for job in queue:
        send_email(job['to'], job['body'])   # 複用既有函式

→ 這 4 行把「通知服務」跑起來了,其餘全是猜測性功能。
階段 3 → 原則 3(精準修改):搬家而不重寫
搬移既有 send_email() 時:
- 原樣複製貼上,不順手加型別提示/docstring/改名
- diff 只包含「新增消費迴圈」與「搬移函式」兩件事
- 想整理的地方,寫在 PR 建議裡,不在這個 PR 動手
收尾 → 原則 4(驗證無回歸)
完整套件全綠(monolith 端點測試 + 新服務驗收測試)
+ 手動:送一封測試信,確認內容逐字相同
→ 此時才宣告「拆完」。每一步都可回滾、可驗證、可追溯。
為什麼四原則是一條鏈而不是四個按鈕
  • 原則 1 省下「拆錯邊界」的整週返工——問得越早,越便宜。
  • 原則 4 把「完成」變成可檢查的數字——基準線先鎖定,後面每一步都有參照物。
  • 原則 2 讓最小切片先跑起來——不被「重試、死信、儀表板」這些下一個月的需求拖住。
  • 原則 3 保證這個 PR 可以被審計——拆服務是高風險動作,審計速度就是安全係數。
深入原理:四個原則共享的底層機制

四個原則看似不同,其實都在對抗同一批模型的底層偏誤:

  • Completion bias(完成偏誤):next-token prediction 天生偏向「繼續寫下去」。原則 1(停下來問)與原則 4(先定義完成)都是打斷這個自動接續——一個在前,一個在後。
  • Token 經濟(Token economy):對模型而言,append(追加)比 revise(重寫)便宜。這同時驅動了原則 2 的「過度工程」(不斷加功能)與原則 3 的「順帶重構」(整段重寫比較省事)。你要主動為「寫得更少」定價。
  • 上下文崩潰(Context collapse):長對話中模型對中間內容記憶最差。四個原則其實都是「把關鍵狀態外置」:需求寫在檔頭(原則 1)、標準寫在計畫(原則 4)、把假設重複聲明(原則 1)——這些都是在對抗模型的內存不可靠。
  • RLHF 的「禮貌性」訓練:人類評分者偏好完整、周到、順暢的回應,這餵養了 sycophancy(討好)與 thoroughness(過度周到)。原則 2 與 3 就是在要求模型「違反這個偏好」。
案例:所有原則同時被違反的「完美輸出」

Agent 回應「我來加入通知功能」,輸出:一個含抽象類別、重試邏輯、儀表板 hook 的 300 行模組,順便把相鄰的 config 檔格式化了,最後說「完成了,測試都通過了」。逐項檢視:沒問需求範圍(違反 1)→ 300 行猜測性功能(違反 2)→ 格式化了無關檔案(違反 3)→ 「測試都通過」沒有指明是哪個測試、驗證了什麼(違反 4)。可怕的是:這份輸出看起來比正確答案專業一百倍——而這正是反模式能騙過人眼的原因。

診斷式疑難排解:拿到一段 AI 輸出,先判斷違反哪個原則
症狀可能原因(違反的原則)解決方案
直接給出完整實作,零提問原則 1 編碼前思考——默默假設退回:先列假設、呈現權衡、等確認
大量抽象類別、參數、防禦性邏輯原則 2 簡潔優先——過度工程要求最小實作,砍掉猜測性功能
diff 裡有引號、縮排、docstring、改名原則 3 精準修改——範圍蔓延退回 diff,要求只保留可追溯需求的變更
「我會檢視並改進」「完成」原則 4 目標驅動——模糊目標要求可執行的成功標準與每步「驗證:」
輸出很完整但沒測試、沒驗證原則 4 + sycophancy——討好式完成「沒有測試的完成等於沒完成」——要求可檢查的證據
進階挑戰題
  1. 多原則同時違反:下面這段輸出,逐項指出它違反了哪個原則、證據在哪一行:
    「我來為你加上 CSV 匯出:已用 pandas 實作,
    支援大檔分塊、自動偵測 BOM、進度條,
    並順手把 config.py 的格式整理成 black 風格。
    寫了 2 個測試,全部通過。」
    提示:至少找 4 個違反點。注意「順手整理 config.py」與「匯出需求」的關係。
  2. 原則優先權:你在一個時間壓力極大的情境(凌晨 2 點、線上事故、老闆在旁)。四個原則中,你只能嚴格遵守兩個。選哪兩個?為什麼?
    提示:想想哪個原則的違反在危機中「傷害最小、可事後補救」——通常「問清楚」與「驗證」的價值最高。
  3. 轉譯成檢核表:把你對四原則的理解濃縮成一張 10 秒快速檢查卡(每原則一句話、一個問句),用在每次收到 AI 輸出時。寫出來,並用本頁九個範例中任一個測試它。
    提示:像 cheatsheet 那樣——但這次要求「可當場執行」的問句,例如「它能被測試嗎?」「這一行能追溯到哪個需求?」