例外處理哲學:該 catch 什麼?
原則:不要 catch 你不知道怎麼處理的例外
// ❌ 最糟糕的寫法:吞掉例外
try {
DoSomething();
} catch (Exception) {
// 什麼都不做 → 錯誤被隱藏,debug 時找不到原因
}
// ❌ 也很糟:catch 所有例外然後記 log 繼續跑
try {
ProcessOrder(order);
} catch (Exception ex) {
_logger.LogError(ex, "Error");
// 然後呢?訂單沒處理成功但程式繼續跑?
}
// ✅ 好:只 catch 你知道怎麼處理的
try {
var data = await httpClient.GetAsync(url);
} catch (HttpRequestException ex) {
// 網路錯誤 → 回傳快取的資料
return _cache.Get(cacheKey);
}
// 其他例外讓它自然往上拋
全域例外處理
// ASP.NET Core:用 Middleware 統一處理
app.UseExceptionHandler("/Error");
// 或自訂 Middleware
app.Use(async (context, next) => {
try {
await next();
} catch (NotFoundException ex) {
context.Response.StatusCode = 404;
await context.Response.WriteAsJsonAsync(new { error = ex.Message });
} catch (Exception ex) {
_logger.LogError(ex, "Unhandled");
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new { error = "伺服器錯誤" });
}
});
自訂例外 vs 回傳值
// 方式 1:拋例外(適合「不應該發生」的錯誤)
public User GetUser(int id) {
return _db.Users.Find(id) ?? throw new NotFoundException($"User {id} not found");
}
// 方式 2:Result Pattern(適合「可預期的失敗」)
public Result<User> GetUser(int id) {
var user = _db.Users.Find(id);
return user != null ? Result.Ok(user) : Result.Fail("找不到使用者");
}
| 拋例外 | Result Pattern | |
|---|---|---|
| 適合場景 | 程式錯誤、不預期的狀況 | 業務邏輯的「失敗」 |
| 效能 | 例外有效能成本 | 無額外成本 |
| 強制處理 | 不強制(可能忘了 catch) | 強制(必須檢查 Result) |
簡單記法:「找不到使用者」是業務邏輯(用 Result),「資料庫連不上」是基礎設施錯誤(用例外)。