---
title: Dùng custom Error enum thay vì NSError hay Error generic
impact: MEDIUM
impactDescription: Custom error type cung cấp type-safe error handling, dễ hiển thị thông báo người dùng và không bị mất context khi propagate qua các tầng.
tags: swift, ios, error-handling, custom-error, enum, code-quality
---

## Dùng custom Error enum thay vì NSError hay Error generic

Định nghĩa `enum` conform `Error` (hoặc `LocalizedError`) cho từng domain nghiệp vụ. Điều này đảm bảo các lớp xử lý lỗi tường minh theo từng case và tránh mất thông tin khi lỗi propagate.

**Incorrect (lỗi generic):**

```swift
enum AppError: Error {
    case generic          // quá mơ hồ
    case networkError     // không có detail
    case unknownError     // không hữu ích
}

func fetchProduct(id: String) throws -> Product {
    guard !id.isEmpty else {
        throw AppError.generic  // caller không biết lỗi gì
    }
    // ...
}
```

**Correct (custom error với đầy đủ case và message):**

```swift
// Mỗi domain có error type riêng
enum ProductError: LocalizedError {
    case emptyProductId
    case productNotFound(id: String)
    case outOfStock(productId: String, available: Int)
    case priceMismatch(expected: Double, actual: Double)
    case networkUnavailable

    var errorDescription: String? {
        switch self {
        case .emptyProductId:
            return "ID sản phẩm không được để trống."
        case .productNotFound(let id):
            return "Không tìm thấy sản phẩm với ID: \(id)."
        case .outOfStock(let id, let available):
            return "Sản phẩm \(id) còn \(available) sản phẩm."
        case .priceMismatch(let expected, let actual):
            return "Giá không khớp: dự kiến \(expected), thực tế \(actual)."
        case .networkUnavailable:
            return "Không có kết nối mạng. Vui lòng thử lại."
        }
    }
}

func fetchProduct(id: String) throws -> Product {
    guard !id.isEmpty else {
        throw ProductError.emptyProductId
    }
    guard let product = productRepository.find(by: id) else {
        throw ProductError.productNotFound(id: id)
    }
    return product
}

// Caller xử lý tường minh
do {
    let product = try fetchProduct(id: productId)
} catch ProductError.outOfStock(let id, let available) {
    showOutOfStockAlert(productId: id, remaining: available)
} catch let error as ProductError {
    showErrorAlert(message: error.localizedDescription)
}
```

**Tools:** Swift `Error` / `LocalizedError` protocol, Code Review

