Skip to main content
Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

如何降低编程中对错误处理的心智负担

🚀 核心原则:减少重复、提前返回、结构化处理、语义明确、可组合

错误处理的心智负担来自:

  • 错误检查太频繁
  • 错误处理散落在代码各处
  • 错误语义不统一
  • 错误缺少上下文
  • 过度 try/except / if err != nil
  • 缺乏集中处理机制

⭐ 使用 Guard Clauses(提前返回原则)

这是最强、最通用、立竿见影的办法。

❌ 反面示例(嵌套多层,高心智负担):

if user:
    if user.active:
        if has_permission(user):
            process(user)
        else:
            raise PermissionError("No permission")
    else:
        raise ValueError("Inactive user")
else:
    raise ValueError("User not found")

✅ 正面示例(简单、轻量):

if not user:
    raise ValueError("User not found")

if not user.active:
    raise ValueError("Inactive user")

if not has_permission(user):
    raise PermissionError("No permission")

process(user)
  • 减少嵌套 == 降低脑力消耗
  • 逻辑更加线性,更易读,更易维护

⭐ 失败优先(Fail Fast)+ 默认成功

如下模式:

if 错误:
  处理
主逻辑

比:

主逻辑
if 错误:
  处理

更容易读。

  • 把错误的分支提前暴露
  • 减少回溯思考
  • 大脑不需要“记住”决定点

⭐ 统一错误包装(统一上下文)

千万不要在代码里出现这种:

return "error"

应当:

✅ 带上下文 + 原始错误信息

Go:

return fmt.Errorf("query user %d failed: %w", uid, err)

Python:

raise DatabaseError(f"query user {uid} failed") from err

JS:

throw new Error(`query user ${uid} failed: ${err.message}`)

-> 当错误格式统一后,你不需要“想怎么写错误”,心智负担立即下降。

⭐ 把错误处理 “外包”,不要在业务代码中处理

最有效思路:

✅ 业务代码只负责“抛错”

✅ 框架层负责统一捕获和记录日志

例如 FastAPI:

@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    log.error(f"{request.url}: {exc}")
    return JSONResponse({"error": str(exc)}, status_code=500)

业务逻辑里就可以非常干净:

if not user:
    raise UserNotFound()

if not user.active:
    raise UserInactive()

return user
  • 大幅减少 “每个函数都要 try/except” 的负担
  • 所有错误最终被统一处理
  • 你只管抛,不管收

⭐ 为不同错误设计清晰的语义-分层错误模型

建议错误至少分 3 层:

  • 领域错误(DomainError)
  • 业务错误(BusinessError)
  • 系统错误(SystemError)

例如:

  • UserNotFound(业务错误)
  • OrderExpired(业务错误)
  • DatabaseTimeout(系统错误)
  • PermissionDenied(领域/业务错误)

为什么降低心智负担?

  • 写代码时不用“思考用什么错误”,直接按模型分类即可
  • 团队内每个人都能统一错误语义
  • 捕获错误时更简单

⭐ 中间层封装错误(如数据库、HTTP 客户端)

将复杂的底层错误转换为统一结构,比如:

❌ 不要在业务代码写:

try:
    r = requests.get(...)
except Timeout as e:
    ...
except JSONDecodeError as e:
    ...

✅ 推荐:写一个 request_wrapper:

def http_get(url):
    try:
        return requests.get(url)
    except Exception as e:
        raise HttpError(f"GET {url} failed") from e

然后业务代码就只有:

r = http_get(url)
  • 每个底层模块负责清洗自己的错误
  • 业务代码超级干净
  • 错误处理完全模块化

⭐ 用工具减少人肉处理(Optional / Result 模式)

例如 Rust、Go 的思路:

  • Option
  • Result
  • Either
  • monad 链式处理

Python 也可以实现:

def get_user(uid) -> Result[User]:
    ...

JS/TS 可以用:

const res = await safeFetch(url)
if (res.err) { ... }
  • 明确区分正常值 vs 错误
  • 逻辑更机械化,无需思考分支
  • 非常强的可组合性

⭐ 错误日志与用户错误分离(极关键)

不要把错误细节直接暴露给用户。

业务层:

if not user:
    raise UserNotFound()

框架层:

log.error(exc.stack)
return {"msg": "User not found"}

用户错误 ≠ 错误日志

  • 用户看到的只是干净可控的提示
  • 开发看到的是完整堆栈和上下文
  • 不需要每个地方都思考“要不要给用户看这个错误?”

心智负担瞬间下降。

⭐ 错误处理逻辑不超过 3 行,否则一定需要抽函数

try:
    r = http.get()
except:
    rollback()
    notify()
    log.error()
    save_state()
    # ...

✅ 直接抽出去:

try:
    r = http.get()
except Exception as e:
    return handle_http_error(e)
  • 错误处理本身也不要太复杂
  • 抽函数让逻辑更线性
  • 函数更易复用

⭐ 永远保持线性流程,不要纠缠于坑位

坏示例:

干活()
检查错误
再干活()
检查错误
再弄点东西()
再检查错误

好示例:

validate()
prepare()
execute()
cleanup()

每一步必须找不到错误才继续

每一步的错误直接向上抛

  • 强制线性思考
  • 不会在函数内部来回跳

🎯 总结一句话

让“错误处理”变成结构化、模式化、可组合,而不是每一段代码都从零思考。