今天我們暫時放下程式功能開發,聊聊一個每位 C# 工程師都會遇到的問題:try-catch
要怎麼寫才寫得好?
很多人以為「有捕例外就安全」,但實際上,
不完整的例外處理往往讓 問題更難排查,
尤其在系統上線後、使用者回報「某功能無法使用」時,
你打開 Log,發現只有一句:
Exception: Object reference not set to an instance of an object.
這時你應該會想拿鍵盤砸人。
許多初學者寫的程式可能長這樣:
try
{
DoWork();
}
catch (Exception)
{
Console.WriteLine("發生錯誤!");
}
這種寫法的問題是:
結果是:錯誤發生當下你忽略了,隔天你要查問題時一無所獲。
當發生例外時,你需要留下「能讓人重現現場」的資訊。
例如:
try
{
var result = ProcessStock(code, startDate, endDate);
}
catch (Exception ex)
{
_logger.LogError(ex,
"ProcessStock failed. Code={Code}, Start={Start}, End={End}",
code, startDate, endDate);
throw; // 保留堆疊
}
這樣做的好處是:
Stack trace 只告訴你哪一行出錯,
但真正能解決問題的,是當時輸入的參數。
舉例來說,你收到錯誤訊息:
ArgumentOutOfRangeException: startDate is later than endDate.
如果沒有記錄參數,你只知道「某個人」輸入錯誤;
但若 Log 中有這樣的訊息:
ProcessStock failed. Code=2330, Start=2025-10-01, End=2025-09-30
你馬上知道錯誤的實際場景(日期順序反了),
不需要連接 DB 或回朔 Request。
許多程式為了「不讓畫面壞掉」,會這樣寫:
try
{
SaveData();
}
catch { }
這是最糟糕的情況,因為:
正確做法:
例外發生時,除了 Log 下 Exception 本身,也應加上「當下狀態」。
例如在金融系統中:
catch (Exception ex)
{
_logger.LogError(ex,
"PriceService.GetQuote failed. Stock={Stock}, API={Api}, Attempt={Attempt}",
stockCode, apiName, retryCount);
}
這樣當未來出現類似錯誤時,
你可以透過 log 分析「哪個 API 常錯」「哪個代號有問題」「是否出現重試行為」。
原則上:
簡化流程如下:
Repository → 捕例外 + Log
Service → 捕例外 + 決定是否重試
ViewModel → 捕例外 + 顯示錯誤訊息
寫好 try-catch
並不是防止例外,而是保留足夠的調查線索。
一個好的例外處理應該:
throw;
)以維持原始堆疊有時候你會看到團隊導入 全域例外處理(Global Exception Handler),
例如 ASP.NET WebAPI 的 UseExceptionHandler
、WPF 的 AppDomain.CurrentDomain.UnhandledException
。
但即使有全域攔截,也不能取代在關鍵流程中記錄詳細參數的必要性。
全域 Handler 負責「收屍」,
而每個 try-catch
才是「現場記錄員」。