5.2 返回值設計
5.2 返回值設計
Hook 系統採用 (bool, str) 返回值模式,這是一種替代異常處理的設計策略。本章深入探討這個模式的設計理念和最佳實踐。
(bool, str) 模式
基本形式
1def validate_something() -> tuple[bool, str]:
2 """
3 Returns:
4 tuple[bool, str]:
5 - (True, "成功訊息") - 操作成功
6 - (False, "錯誤訊息") - 操作失敗
7 """
8 if some_condition:
9 return True, "驗證通過"
10 return False, "驗證失敗:原因說明"使用方式
1success, message = validate_something()
2if success:
3 print(f"成功: {message}")
4else:
5 print(f"失敗: {message}")
6 # 決定如何處理錯誤實際範例:Git 命令
來自 .claude/lib/git_utils.py:
1def run_git_command(
2 args: list[str],
3 cwd: Optional[str] = None,
4 timeout: int = 10
5) -> tuple[bool, str]:
6 """
7 執行 git 命令並返回結果
8
9 Args:
10 args: git 命令參數列表
11 cwd: 執行目錄
12 timeout: 超時時間(秒)
13
14 Returns:
15 tuple[bool, str]: (是否成功, 輸出或錯誤訊息)
16 """
17 try:
18 result = subprocess.run(
19 ["git"] + args,
20 cwd=cwd,
21 capture_output=True,
22 text=True,
23 timeout=timeout
24 )
25 if result.returncode == 0:
26 return True, result.stdout.strip()
27 else:
28 return False, result.stderr.strip()
29 except subprocess.TimeoutExpired:
30 return False, f"Command timed out after {timeout}s"
31 except FileNotFoundError:
32 return False, "git command not found"使用這個函式
1def get_current_branch() -> Optional[str]:
2 """獲取當前分支名稱"""
3 success, output = run_git_command(["branch", "--show-current"])
4 return output if success and output else None
5
6def get_project_root() -> str:
7 """獲取專案根目錄"""
8 success, output = run_git_command(["rev-parse", "--show-toplevel"])
9 return output if success else os.getcwd()為什麼選擇這個模式?
優點
明確的錯誤處理
1# 呼叫者必須處理兩種情況
2success, message = validate()
3if not success:
4 # 必須處理錯誤
5 pass錯誤訊息保留
1# 錯誤訊息可以傳遞給使用者
2success, error = run_command()
3if not success:
4 logger.error(error)
5 return create_error_response(error)不中斷執行流程
1# 可以收集所有錯誤
2errors = []
3for file in files:
4 success, message = process(file)
5 if not success:
6 errors.append(message)與異常的比較
| 特性 | (bool, str) | 異常 |
|---|---|---|
| 強制處理 | 是(需要解包) | 否(可能被忽略) |
| 控制流程 | 不中斷 | 中斷 |
| 效能 | 較好 | 較差(stack trace) |
| 適合場景 | 預期的錯誤 | 非預期的錯誤 |
設計變體
(bool, T) - 通用型別
1from typing import TypeVar, Tuple
2
3T = TypeVar("T")
4
5def parse_json(text: str) -> Tuple[bool, dict]:
6 try:
7 return True, json.loads(text)
8 except json.JSONDecodeError as e:
9 return False, {"error": str(e)}(T | None) - 成功返回值,失敗返回 None
1def find_config(name: str) -> Optional[dict]:
2 """找不到時返回 None"""
3 path = get_config_path(name)
4 if not path.exists():
5 return None
6 return load_config(path)Result 類別(進階)
1from dataclasses import dataclass
2from typing import Generic, TypeVar
3
4T = TypeVar("T")
5
6@dataclass
7class Result(Generic[T]):
8 success: bool
9 value: Optional[T] = None
10 error: Optional[str] = None
11
12 @classmethod
13 def ok(cls, value: T) -> "Result[T]":
14 return cls(success=True, value=value)
15
16 @classmethod
17 def fail(cls, error: str) -> "Result[T]":
18 return cls(success=False, error=error)
19
20# 使用
21def divide(a: int, b: int) -> Result[float]:
22 if b == 0:
23 return Result.fail("Division by zero")
24 return Result.ok(a / b)
25
26result = divide(10, 2)
27if result.success:
28 print(result.value) # 5.0
29else:
30 print(result.error)實際應用模式
鏈式操作
1def process_pipeline(data: str) -> Tuple[bool, str]:
2 # 第一步
3 success, result = step_one(data)
4 if not success:
5 return False, f"Step 1 failed: {result}"
6
7 # 第二步
8 success, result = step_two(result)
9 if not success:
10 return False, f"Step 2 failed: {result}"
11
12 # 第三步
13 success, result = step_three(result)
14 if not success:
15 return False, f"Step 3 failed: {result}"
16
17 return True, result收集多個錯誤
1def validate_all(items: list[str]) -> Tuple[bool, list[str]]:
2 errors = []
3 for item in items:
4 success, message = validate_item(item)
5 if not success:
6 errors.append(message)
7
8 if errors:
9 return False, errors
10 return True, []帶上下文的錯誤
1def run_with_context(
2 command: str
3) -> Tuple[bool, str]:
4 """執行命令並提供上下文"""
5 try:
6 result = execute(command)
7 return True, result
8 except Exception as e:
9 context = f"Command: {command}\nError: {e}"
10 return False, contextHook 系統的應用
Hook 輸入讀取
1def read_hook_input() -> dict:
2 """
3 從 stdin 讀取輸入
4 失敗時返回空字典(而非拋出異常)
5 """
6 try:
7 return json.load(sys.stdin)
8 except json.JSONDecodeError:
9 return {}分支驗證
1def validate_branch(branch: str) -> Tuple[bool, str]:
2 """驗證分支是否可以編輯"""
3 if is_protected_branch(branch):
4 return False, f"Cannot edit protected branch: {branch}"
5 if not is_allowed_branch(branch):
6 return False, f"Branch not in allowed list: {branch}"
7 return True, f"Branch {branch} is valid"最佳實踐
1. 訊息要有意義
1# 好:提供具體資訊
2return False, f"Config file not found: {path}"
3
4# 不好:模糊的訊息
5return False, "Error"2. 保持一致性
1# 好:整個模組使用相同模式
2def func_a() -> Tuple[bool, str]: ...
3def func_b() -> Tuple[bool, str]: ...
4def func_c() -> Tuple[bool, str]: ...
5
6# 不好:混合使用
7def func_a() -> Tuple[bool, str]: ...
8def func_b() -> Optional[str]: ... # 不一致
9def func_c(): ... # 可能拋出異常3. 文檔說明返回值
1def validate(data: str) -> Tuple[bool, str]:
2 """
3 驗證資料格式
4
5 Returns:
6 tuple[bool, str]:
7 - (True, "Validation passed") - 驗證成功
8 - (False, error_message) - 驗證失敗,包含原因
9 """
10 ...思考題
- 什麼情況下應該使用
(bool, str)而不是異常? - 如何處理需要返回多種錯誤類型的情況?
(bool, str)模式如何與型別檢查工具配合?
實作練習
- 重構一個使用異常的函式,改為
(bool, str)模式 - 實作一個
Result類別,支援map和flat_map操作 - 寫一個函式,執行多個驗證並收集所有錯誤
上一章:異常處理策略 下一章:unittest 基礎