Xử lý ngoại lệ (exception handling) đúng cách là chìa khóa để xây dựng ứng dụng robust, dễ bảo trì, dễ debug. Dưới đây là các best practices phổ biến trong cộng đồng .NET.
1. Chỉ catch exception bạn có thể xử lý
// TỆ: Nắm bắt tất cả, nuốt exception
try { ... } catch { }
// TỐT: Chỉ catch exception cụ thể
try {
var data = File.ReadAllText(path);
} catch (FileNotFoundException) {
// Xử lý file không tồn tại
} catch (UnauthorizedAccessException) {
// Xử lý thiếu quyền
} catch (IOException ex) {
// Xử lý I/O error khác
logger.LogError(ex, "Lỗi đọc file");
}
2. Không dùng exception cho control flow (luồng điều khiển)
// TỆ: Dùng exception để kiểm tra key tồn tại
try { return dict[key]; } catch { return default; }
// TỐT: Dùng TryGetValue
if (dict.TryGetValue(key, out var value)) return value;
return default;
3. Luôn log exception với ngữ cảnh (context)
try {
await _service.ProcessOrder(orderId);
} catch (Exception ex) {
// Log: exception + context quan trọng
_logger.LogError(ex, "Lỗi xử lý đơn hàng {OrderId}, User: {UserId}", orderId, userId);
throw; // Re-throw để upper layer xử lý hoặc trả về 500
}
4. Dùng throw; thay vì throw ex; để bảo toàn stack trace
try { ... }
catch (Exception ex) {
Log(ex);
throw; // ✓ Giữ nguyên stack trace gốc
// throw ex; // ✗ Reset stack trace → mất thông tin debug
}
5. Implement IDisposable / using cho tài nguyên unmanaged
// Tự động Dispose (gọi Close/Dispose) ngay cả khi có exception
using (var conn = new SqlConnection(connStr)) {
await conn.OpenAsync();
// ...
}
// Pattern cho class riêng
public class ResourceHolder : IDisposable {
private bool _disposed;
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) { ... }
}
6. Tạo custom exception cho domain logic
[Serializable]
public class InsufficientFundsException : Exception {
public decimal CurrentBalance { get; }
public decimal RequestedAmount { get; }
public InsufficientFundsException(decimal balance, decimal requested)
: base($"Số dư không đủ. Hiện có: {balance}, Yêu cầu: {requested}") {
CurrentBalance = balance;
RequestedAmount = requested;
}
}
7. Sử dụng Exception Filter (C# 6+)
try { ... }
catch (SqlException ex) when (ex.Number == 1205) { // Deadlock
// Retry logic
}
catch (SqlException ex) when (ex.Number == 2627) { // Unique constraint
// Handle duplicate
}
8. Centralized Exception Handling (Global)
- ASP.NET Core:
app.UseExceptionHandler(),IExceptionFilter,ProblemDetails(RFC 7807). - Console/Worker:
AppDomain.CurrentDomain.UnhandledException,TaskScheduler.UnobservedTaskException.
9. Monitoring & Alerting
- Tools: Serilog + Seq, ELK, Application Insights, Datadog, Sentry.
- Log structured (JSON), correlation ID (traceId), sampling cho high-throughput.
10. Tránh anti-patterns
- ❌
catch (Exception) { }(swallow) - ❌
throw new Exception("msg")— mất stack trace gốc - ❌ Exception trong finally/finalizer
- ❌ Dùng exception cho validation thông thường (dùng Result pattern / FluentValidation)