如何降低编程中对错误处理的心智负担
错误处理的心智负担来自:
- 错误检查太频繁
- 错误处理散落在代码各处
- 错误语义不统一
- 错误缺少上下文
- 过度 try/except / if err != nil
- 缺乏集中处理机制
这是最强、最通用、立竿见影的办法。
❌ 反面示例(嵌套多层,高心智负担):
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)
- 减少嵌套 == 降低脑力消耗
- 逻辑更加线性,更易读,更易维护
如下模式:
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(领域/业务错误)
为什么降低心智负担?
- 写代码时不用“思考用什么错误”,直接按模型分类即可
- 团队内每个人都能统一错误语义
- 捕获错误时更简单
将复杂的底层错误转换为统一结构,比如:
❌ 不要在业务代码写:
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)
- 每个底层模块负责清洗自己的错误
- 业务代码超级干净
- 错误处理完全模块化
例如 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"}
用户错误 ≠ 错误日志
- 用户看到的只是干净可控的提示
- 开发看到的是完整堆栈和上下文
- 不需要每个地方都思考“要不要给用户看这个错误?”
心智负担瞬间下降。
❌
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()
每一步必须找不到错误才继续
每一步的错误直接向上抛
- 强制线性思考
- 不会在函数内部来回跳
让“错误处理”变成结构化、模式化、可组合,而不是每一段代码都从零思考。