error-handling-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseError Handling Patterns — Expert Decisions
错误处理模式——专家决策
Expert decision frameworks for error handling choices. Claude knows Swift error syntax — this skill provides judgment calls for error type design and recovery strategies.
错误处理选择的专家决策框架。Claude 熟悉 Swift 错误语法——本技能为错误类型设计和恢复策略提供判断依据。
Decision Trees
决策树
throws vs Result
throws vs Result
Does the caller need to handle success/failure explicitly?
├─ YES (caller must acknowledge failure)
│ └─ Is the failure common and expected?
│ ├─ YES → Result<T, E> (explicit handling, no try/catch)
│ └─ NO → throws (exceptional case)
│
└─ NO (caller can ignore failure)
└─ Use throws with try? at call siteWhen Result wins: Parsing, validation, operations where failure is common and needs explicit handling. Forces exhaustive switch.
When throws wins: Network calls, file operations where failure is exceptional. Cleaner syntax with async/await.
调用者是否需要显式处理成功/失败?
├─ 是(调用者必须确认失败情况)
│ └─ 失败是否常见且可预期?
│ ├─ 是 → Result<T, E>(显式处理,无需 try/catch)
│ └─ 否 → throws(异常情况)
│
└─ 否(调用者可忽略失败)
└─ 在调用点使用 try? 搭配 throwsResult 的适用场景:解析、验证等失败常见且需要显式处理的操作。强制要求穷举 switch 处理。
throws 的适用场景:网络请求、文件操作等失败属于异常情况的场景。搭配 async/await 语法更简洁。
Error Type Granularity
错误类型粒度
How specific should error types be?
├─ API boundary (framework, library)
│ └─ Coarse-grained domain errors
│ enum NetworkError: Error { case timeout, serverError, ... }
│
├─ Internal module
│ └─ Fine-grained for actionable handling
│ enum PaymentError: Error {
│ case cardDeclined(reason: String)
│ case insufficientFunds(balance: Decimal, required: Decimal)
│ }
│
└─ User-facing
└─ LocalizedError with user-friendly messages
var errorDescription: String? { "Payment failed" }The trap: Too many error types that no one handles differently. If every case has the same handler, collapse them.
错误类型应该有多具体?
├─ API 边界(框架、库)
│ └─ 粗粒度领域错误
│ enum NetworkError: Error { case timeout, serverError, ... }
│
├─ 内部模块
│ └─ 细粒度以支持可操作的处理
│ enum PaymentError: Error {
│ case cardDeclined(reason: String)
│ case insufficientFunds(balance: Decimal, required: Decimal)
│ }
│
└─ 面向用户
└─ 带有友好提示信息的 LocalizedError
var errorDescription: String? { "Payment failed" }陷阱:定义过多无人区分处理的错误类型。如果所有错误分支的处理逻辑相同,应合并它们。
Recovery Strategy Selection
恢复策略选择
Is the error transient (network, timeout)?
├─ YES → Is it idempotent?
│ ├─ YES → Retry with exponential backoff
│ └─ NO → Retry cautiously or fail
│
└─ NO → Is recovery possible?
├─ YES → Can user fix it?
│ ├─ YES → Show actionable error UI
│ └─ NO → Auto-recover or fallback value
│
└─ NO → Log and show generic error错误是否是暂时性的(网络超时等)?
├─ 是 → 操作是否具有幂等性?
│ ├─ 是 → 使用指数退避策略重试
│ └─ 否 → 谨慎重试或直接失败
│
└─ 否 → 是否可恢复?
├─ 是 → 用户能否修复?
│ ├─ 是 → 展示可操作的错误 UI
│ └─ 否 → 自动恢复或使用回退值
│
└─ 否 → 记录日志并展示通用错误Error Presentation Selection
错误展示选择
How critical is the failure?
├─ Critical (can't continue)
│ └─ Full-screen error with retry
│
├─ Important (user needs to know)
│ └─ Alert dialog
│
├─ Minor (informational)
│ └─ Toast or inline message
│
└─ Silent (user doesn't need to know)
└─ Log only, use fallback失败的严重程度如何?
├─ 严重(无法继续操作)
│ └─ 全屏错误提示 + 重试按钮
│
├─ 重要(用户需要知晓)
│ └─ 弹窗提示
│
├─ 轻微(仅信息告知)
│ └─ 轻提示(Toast)或内联消息
│
└─ 静默(无需用户知晓)
└─ 仅记录日志,使用回退方案NEVER Do
绝对不要做的事
Error Type Design
错误类型设计
NEVER use generic catch-all errors:
swift
// ❌ Caller can't handle different cases
enum AppError: Error {
case somethingWentWrong
case error(String) // Just a message — no actionable info
}
// ✅ Specific, actionable cases
enum AuthError: Error {
case invalidCredentials
case sessionExpired
case accountLocked(unlockTime: Date?)
}NEVER throw errors with sensitive information:
swift
// ❌ Leaks internal details
throw DatabaseError.queryFailed(sql: query, password: dbPassword)
// ✅ Sanitize sensitive data
throw DatabaseError.queryFailed(table: tableName)NEVER create error enums with one case:
swift
// ❌ Pointless enum
enum ValidationError: Error {
case invalid
}
// ✅ Use existing error or don't create enum
struct ValidationError: Error {
let field: String
let reason: String
}绝对不要使用通用的万能错误类型:
swift
// ❌ 调用者无法区分处理不同情况
enum AppError: Error {
case somethingWentWrong
case error(String) // 仅包含消息,无可用操作信息
}
// ✅ 具体、可操作的错误分支
enum AuthError: Error {
case invalidCredentials
case sessionExpired
case accountLocked(unlockTime: Date?)
}绝对不要抛出包含敏感信息的错误:
swift
// ❌ 泄露内部细节
throw DatabaseError.queryFailed(sql: query, password: dbPassword)
// ✅ 清理敏感数据
throw DatabaseError.queryFailed(table: tableName)绝对不要创建仅含一个分支的错误枚举:
swift
// ❌ 无意义的枚举
enum ValidationError: Error {
case invalid
}
// ✅ 使用现有错误类型或不创建枚举
struct ValidationError: Error {
let field: String
let reason: String
}Error Propagation
错误传播
NEVER swallow errors silently:
swift
// ❌ Failure is invisible
func loadUser() async {
try? await fetchUser() // Error ignored, user is nil, no feedback
}
// ✅ Handle or propagate
func loadUser() async {
do {
user = try await fetchUser()
} catch {
errorMessage = error.localizedDescription
analytics.trackError(error)
}
}NEVER catch and rethrow without adding context:
swift
// ❌ Pointless catch
do {
try operation()
} catch {
throw error // Why catch at all?
}
// ✅ Add context or handle
do {
try operation()
} catch {
throw ContextualError.operationFailed(underlying: error, context: "during checkout")
}NEVER use force-try () outside of known-safe scenarios:
try!swift
// ❌ Crashes if JSON is malformed
let data = try! JSONEncoder().encode(untrustedInput)
// ✅ Safe scenarios only
let data = try! JSONEncoder().encode(staticKnownValue) // Compile-time known
let url = URL(string: "https://example.com")! // Literal string
// ✅ Handle unknown input
guard let data = try? JSONEncoder().encode(userInput) else {
throw EncodingError.failed
}绝对不要静默忽略错误:
swift
// ❌ 失败完全不可见
func loadUser() async {
try? await fetchUser() // 错误被忽略,user 为 nil,无任何反馈
}
// ✅ 处理或传播错误
func loadUser() async {
do {
user = try await fetchUser()
} catch {
errorMessage = error.localizedDescription
analytics.trackError(error)
}
}绝对不要捕获错误后直接重新抛出而不添加上下文:
swift
// ❌ 无意义的捕获
do {
try operation()
} catch {
throw error // 那为什么要捕获?
}
// ✅ 添加上下文或处理错误
do {
try operation()
} catch {
throw ContextualError.operationFailed(underlying: error, context: "during checkout")
}绝对不要在非已知安全场景下使用强制 try ():
try!swift
// ❌ JSON 格式错误时会崩溃
let data = try! JSONEncoder().encode(untrustedInput)
// ✅ 仅在安全场景使用
let data = try! JSONEncoder().encode(staticKnownValue) // 编译时已知安全
let url = URL(string: "https://example.com")! // 字面量字符串
// ✅ 处理不可信输入
guard let data = try? JSONEncoder().encode(userInput) else {
throw EncodingError.failed
}CancellationError
CancellationError
NEVER show CancellationError to users:
swift
// ❌ User sees "cancelled" error when navigating away
func loadData() async {
do {
data = try await fetchData()
} catch {
errorMessage = error.localizedDescription // Shows "cancelled"
}
}
// ✅ Handle cancellation separately
func loadData() async {
do {
data = try await fetchData()
} catch is CancellationError {
return // User navigated away — not an error
} catch {
errorMessage = error.localizedDescription
}
}绝对不要向用户展示 CancellationError:
swift
// ❌ 用户导航离开时会看到“已取消”错误
func loadData() async {
do {
data = try await fetchData()
} catch {
errorMessage = error.localizedDescription // 显示“已取消”
}
}
// ✅ 单独处理取消操作
func loadData() async {
do {
data = try await fetchData()
} catch is CancellationError {
return // 用户已导航离开,不视为错误
} catch {
errorMessage = error.localizedDescription
}
}Retry Logic
重试逻辑
NEVER retry non-idempotent operations blindly:
swift
// ❌ May charge user multiple times
func processPayment() async throws {
try await retryWithBackoff {
try await chargeCard(amount) // Not idempotent!
}
}
// ✅ Use idempotency key or check state first
func processPayment() async throws {
let idempotencyKey = UUID().uuidString
try await retryWithBackoff {
try await chargeCard(amount, idempotencyKey: idempotencyKey)
}
}NEVER retry without limits:
swift
// ❌ Infinite loop if server is down
func fetch() async throws -> Data {
while true {
do {
return try await request()
} catch {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
}
}
// ✅ Limited retries with backoff
func fetch(maxAttempts: Int = 3) async throws -> Data {
var delay: UInt64 = 1_000_000_000
for attempt in 1...maxAttempts {
do {
return try await request()
} catch where attempt < maxAttempts && isRetryable(error) {
try await Task.sleep(nanoseconds: delay)
delay *= 2
}
}
throw FetchError.maxRetriesExceeded
}绝对不要盲目重试非幂等操作:
swift
// ❌ 可能导致用户被多次扣费
func processPayment() async throws {
try await retryWithBackoff {
try await chargeCard(amount) // 不具备幂等性!
}
}
// ✅ 使用幂等键或先检查状态
func processPayment() async throws {
let idempotencyKey = UUID().uuidString
try await retryWithBackoff {
try await chargeCard(amount, idempotencyKey: idempotencyKey)
}
}绝对不要无限制重试:
swift
// ❌ 服务器宕机时会无限循环
func fetch() async throws -> Data {
while true {
do {
return try await request()
} catch {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
}
}
// ✅ 带退避策略的有限重试
func fetch(maxAttempts: Int = 3) async throws -> Data {
var delay: UInt64 = 1_000_000_000
for attempt in 1...maxAttempts {
do {
return try await request()
} catch where attempt < maxAttempts && isRetryable(error) {
try await Task.sleep(nanoseconds: delay)
delay *= 2
}
}
throw FetchError.maxRetriesExceeded
}Essential Patterns
核心模式
Typed Error with Recovery Info
包含恢复信息的类型化错误
swift
enum NetworkError: Error, LocalizedError {
case noConnection
case timeout
case serverError(statusCode: Int)
case unauthorized
var errorDescription: String? {
switch self {
case .noConnection: return "No internet connection"
case .timeout: return "Request timed out"
case .serverError(let code): return "Server error (\(code))"
case .unauthorized: return "Session expired"
}
}
var recoverySuggestion: String? {
switch self {
case .noConnection: return "Check your network settings"
case .timeout: return "Try again"
case .serverError: return "Please try again later"
case .unauthorized: return "Please log in again"
}
}
var isRetryable: Bool {
switch self {
case .noConnection, .timeout, .serverError: return true
case .unauthorized: return false
}
}
}swift
enum NetworkError: Error, LocalizedError {
case noConnection
case timeout
case serverError(statusCode: Int)
case unauthorized
var errorDescription: String? {
switch self {
case .noConnection: return "无网络连接"
case .timeout: return "请求超时"
case .serverError(let code): return "服务器错误 (\(code))"
case .unauthorized: return "会话已过期"
}
}
var recoverySuggestion: String? {
switch self {
case .noConnection: return "检查你的网络设置"
case .timeout: return "请重试"
case .serverError: return "请稍后再试"
case .unauthorized: return "请重新登录"
}
}
var isRetryable: Bool {
switch self {
case .noConnection, .timeout, .serverError: return true
case .unauthorized: return false
}
}
}Result with Typed Error
搭配类型化错误的 Result
swift
func validate(email: String) -> Result<String, ValidationError> {
guard !email.isEmpty else {
return .failure(.emptyField("email"))
}
guard email.contains("@") else {
return .failure(.invalidFormat("email"))
}
return .success(email)
}
// Forced exhaustive handling
switch validate(email: input) {
case .success(let email):
createAccount(email: email)
case .failure(let error):
showValidationError(error)
}swift
func validate(email: String) -> Result<String, ValidationError> {
guard !email.isEmpty else {
return .failure(.emptyField("email"))
}
guard email.contains("@") else {
return .failure(.invalidFormat("email"))
}
return .success(email)
}
// 强制穷举处理
switch validate(email: input) {
case .success(let email):
createAccount(email: email)
case .failure(let error):
showValidationError(error)
}Retry with Exponential Backoff
指数退避重试
swift
func withRetry<T>(
maxAttempts: Int = 3,
initialDelay: Duration = .seconds(1),
operation: () async throws -> T
) async throws -> T {
var delay = initialDelay
for attempt in 1...maxAttempts {
do {
return try await operation()
} catch let error as NetworkError where error.isRetryable && attempt < maxAttempts {
try await Task.sleep(for: delay)
delay *= 2
} catch {
throw error
}
}
fatalError("Should not reach")
}swift
func withRetry<T>(
maxAttempts: Int = 3,
initialDelay: Duration = .seconds(1),
operation: () async throws -> T
) async throws -> T {
var delay = initialDelay
for attempt in 1...maxAttempts {
do {
return try await operation()
} catch let error as NetworkError where error.isRetryable && attempt < maxAttempts {
try await Task.sleep(for: delay)
delay *= 2
} catch {
throw error
}
}
fatalError("Should not reach")
}Error Presentation in ViewModel
ViewModel 中的错误展示
swift
@MainActor
final class ViewModel: ObservableObject {
@Published private(set) var state: State = .idle
enum State: Equatable {
case idle
case loading
case loaded(Data)
case error(String, isRetryable: Bool)
}
func load() async {
state = .loading
do {
let data = try await fetchData()
state = .loaded(data)
} catch is CancellationError {
state = .idle // Don't show error
} catch let error as NetworkError {
state = .error(error.localizedDescription, isRetryable: error.isRetryable)
} catch {
state = .error("Something went wrong", isRetryable: true)
}
}
}swift
@MainActor
final class ViewModel: ObservableObject {
@Published private(set) var state: State = .idle
enum State: Equatable {
case idle
case loading
case loaded(Data)
case error(String, isRetryable: Bool)
}
func load() async {
state = .loading
do {
let data = try await fetchData()
state = .loaded(data)
} catch is CancellationError {
state = .idle // 不展示错误
} catch let error as NetworkError {
state = .error(error.localizedDescription, isRetryable: error.isRetryable)
} catch {
state = .error("出现未知问题", isRetryable: true)
}
}
}Quick Reference
速查指南
Error Handling Decision Matrix
错误处理决策矩阵
| Scenario | Pattern | Why |
|---|---|---|
| Network call | | Exceptional failure |
| Validation | | Expected failure, needs handling |
| Optional parsing | | Failure acceptable, use default |
| Must succeed | | Only for literals/static |
| Background task | Log only | User doesn't need to know |
| 场景 | 模式 | 原因 |
|---|---|---|
| 网络请求 | | 失败属于异常情况 |
| 验证操作 | | 失败可预期,需要显式处理 |
| 可选解析 | | 失败可接受,使用默认值 |
| 必须成功 | | 仅适用于字面量/静态已知安全场景 |
| 后台任务 | 仅记录日志 | 用户无需知晓 |
Error Type Checklist
错误类型检查清单
- Cases are actionable (handler differs per case)
- No sensitive data in associated values
- Conforms to for user-facing
LocalizedError - Has property if recovery is possible
isRetryable - Has for user guidance
recoverySuggestion
- 错误分支具有可操作性(不同分支处理逻辑不同)
- 关联值中无敏感数据
- 面向用户的错误遵循 协议
LocalizedError - 若支持恢复则包含 属性
isRetryable - 包含 为用户提供指导
recoverySuggestion
Red Flags
危险信号
| Smell | Problem | Fix |
|---|---|---|
| Error silently swallowed | Log or propagate |
| Same handler for all cases | Over-specific error type | Collapse to fewer cases |
| Hidden failures | Use |
| Retry without limit | Infinite loop risk | Add max attempts |
| CancellationError shown to user | Bad UX | Handle separately |
| Technical message | Map to user-friendly |
| 问题症状 | 危害 | 修复方案 |
|---|---|---|
| 错误被静默忽略 | 记录日志或传播错误 |
| 所有错误分支使用相同处理逻辑 | 错误类型过度细分 | 合并为更少的分支 |
关键路径使用 | 失败被隐藏 | 使用 |
| 无限制重试 | 存在无限循环风险 | 添加最大重试次数 |
| 向用户展示 CancellationError | 糟糕的用户体验 | 单独处理取消操作 |
对 NSError 使用 | 展示技术化消息 | 映射为用户友好的提示 |