每個範例都包含三個部分:❌ 常見錯誤(AI 直覺反應)、✅ 正確做法(運用四原則後的做法)、以及關鍵差異(為什麼差這麼多)。
使用方式:先點「❌ 常見錯誤」,看看這是不是你平常看到的 AI 輸出。然後切換到「✅ 正確做法」,比較兩者的差異。最後看「關鍵差異」,理解為什麼正確做法更好。
反模式的可怕之處: ❌ 的程式碼看起來都很「專業」——有完整的抽象層、完整的錯誤處理、完整的文檔。但問題在於時機和必要性。
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"
在實作之前,我需要釐清:
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。
哪個面向對您的使用案例最重要?
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 多行的設定才能做一個簡單的計算
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)
)
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
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
+ 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
我會這樣修復驗證系統:
1. 檢視程式碼
2. 找出問題
3. 進行改進
4. 測試變更
[在沒有明確成功標準的情況下開始修改]
要「修復驗證」,我需要定義成功標準。
我們要解決的具體問題是什麼?
如果問題是「使用者變更密碼後仍然保持登入狀態」:
計畫:
1. 撰寫測試:變更密碼 → 驗證舊 session 已失效
驗證:測試失敗(重現了錯誤)
2. 實作:在密碼變更時使 session 失效
驗證:測試通過
3. 檢查邊界情況:多個活躍 session、同時變更
驗證:額外測試通過
4. 驗證無回歸:既有的驗證測試仍然通過
驗證:完整測試套件全綠
目前驗證的測試覆蓋率:[顯示覆蓋率]
您遇到的具體驗證問題是什麼?
# 在一份 300 行的提交中實作了完整的速率限制,
# 包含 Redis、多種策略、設定系統和監控
# 沒有明確的驗證步驟
速率限制計畫:
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']))
# 驗證:測試持續通過
需求:「把通知拆成佇列消費服務。」 在動任何一個資料夾之前,先輸出: - 邊界假設:通知的範疇=email + push?webhook 算嗎? - 一致性假設:通知可以延遲嗎?一定要「發出去」才算成功嗎? - 成功標準:「拆完」的衡量指標是什麼? - 我最保守的路徑:先只拆「消費端」,生產端(API)後置。 → 列完停在這裡,等使用者確認。
1. 為現有通知發送寫端點測試 驗證:測試全綠(這是行為基準線) 2. 定義新服務的驗收標準 驗證:同一個 email,monolith 與新服務送出內容逐字相同
先不做:重試佇列、死信佇列、儀表板、多 worker 擴展。
先做:一個消費佇列的迴圈 + 呼叫既有的 send_email()。
def consume(queue):
for job in queue:
send_email(job['to'], job['body']) # 複用既有函式
→ 這 4 行把「通知服務」跑起來了,其餘全是猜測性功能。
搬移既有 send_email() 時: - 原樣複製貼上,不順手加型別提示/docstring/改名 - diff 只包含「新增消費迴圈」與「搬移函式」兩件事 - 想整理的地方,寫在 PR 建議裡,不在這個 PR 動手
完整套件全綠(monolith 端點測試 + 新服務驗收測試) + 手動:送一封測試信,確認內容逐字相同 → 此時才宣告「拆完」。每一步都可回滾、可驗證、可追溯。
四個原則看似不同,其實都在對抗同一批模型的底層偏誤:
Agent 回應「我來加入通知功能」,輸出:一個含抽象類別、重試邏輯、儀表板 hook 的 300 行模組,順便把相鄰的 config 檔格式化了,最後說「完成了,測試都通過了」。逐項檢視:沒問需求範圍(違反 1)→ 300 行猜測性功能(違反 2)→ 格式化了無關檔案(違反 3)→ 「測試都通過」沒有指明是哪個測試、驗證了什麼(違反 4)。可怕的是:這份輸出看起來比正確答案專業一百倍——而這正是反模式能騙過人眼的原因。
| 症狀 | 可能原因(違反的原則) | 解決方案 |
|---|---|---|
| 直接給出完整實作,零提問 | 原則 1 編碼前思考——默默假設 | 退回:先列假設、呈現權衡、等確認 |
| 大量抽象類別、參數、防禦性邏輯 | 原則 2 簡潔優先——過度工程 | 要求最小實作,砍掉猜測性功能 |
| diff 裡有引號、縮排、docstring、改名 | 原則 3 精準修改——範圍蔓延 | 退回 diff,要求只保留可追溯需求的變更 |
| 「我會檢視並改進」「完成」 | 原則 4 目標驅動——模糊目標 | 要求可執行的成功標準與每步「驗證:」 |
| 輸出很完整但沒測試、沒驗證 | 原則 4 + sycophancy——討好式完成 | 「沒有測試的完成等於沒完成」——要求可檢查的證據 |
「我來為你加上 CSV 匯出:已用 pandas 實作, 支援大檔分塊、自動偵測 BOM、進度條, 並順手把 config.py 的格式整理成 black 風格。 寫了 2 個測試,全部通過。」