我应该使用枚举还是类层次结构来处理 swift 中的错误?

Tho*_*net 4 enums swift

在 Swift 2 中,任何符合 ErrorType 协议的类型都可以被抛出和捕获。对我来说,有一个通用的错误层次结构并在多个地方重用它是有意义的。但是,Apple 文档似乎促使开发人员使用枚举来处理错误。

例如,这个层次结构可以让我在不知道它的确切子类型的情况下捕获并处理一个常见的 ValidationError。这也将允许应用程序的不同部分扩展 ValidationError。

MyAppError
    ValidationError
        InvalidPathError
        WrongFileTypeError
Run Code Online (Sandbox Code Playgroud)

混合不同风格的错误定义看起来不是一个好主意。那么,我应该围绕类层次结构或枚举对所有错误处理进行建模吗?

Kev*_*vin 5

tl;博士

枚举更短,编写速度更快,更容易理解潜在的错误,编译器将确保您捕获所有错误。

完整的故事

ErrorType本身只是一个空协议(有隐藏的属性_code : Int_domain : String但 Apple 会处理这些)。

我引用了 The Swift Programming Guide (link)

Swift 枚举特别适合对一组相关的错误条件进行建模,关联值允许传达有关错误性质的附加信息。

为了详细说明这一点,枚举允许您表达究竟可能出错的地方。在进行错误处理时,您通常会遇到可能失败的特定条件(Swift 通过可选项和类型安全将您推向这个方向)。因为错误是不同的情况,所以您不应该真正需要多层继承(如果您确实在答案中添加了详细信息)。错误可以很容易地用枚举来表示。使用大型继承层次结构过于复杂。

假设您希望每个错误都有一条可以显示给用户的消息。您可以只使用协议而不是子类。

protocol MyAppError : ErrorType {
    var message: String { get }
}
Run Code Online (Sandbox Code Playgroud)

以您给定的示例更进一步,您会将您的表示ValidationError为枚举(因为存在许多验证错误)。

enum ValidationError : MyAppError {
    case InvalidPathError (String)
    case WrongFileTypeError (expectedFileType: String)

    var message: String {
        switch self {
        case .InvalidPathError(let invalidPath):
            return "\(invalidPath) is an invalid path"
        case .WrongFileTypeError(let expectedFileType):
            return "Expected type of \(expectedFileType)"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

_

func myFileFunction(path: String) throws {
    guard let url = NSURL(string: path) else {
        throw ValidationError.InvalidPathError(path)
    }
    guard let data = NSDictionary(contentsOfURL: url) else {
        throw ValidationError.WrongFileTypeError(expectedFileType: ".plist")
    }
    print(data)
}


do {
    try myFileFunction("hi.jpg")
} catch ValidationError.InvalidPathError(let path) {
    print("Darn, had a bad path \(path)")
} catch ValidationError.WrongFileTypeError(let expectedType) {
    print("Darn, expected the type \(expectedType)")
}  catch (let error as MyAppError) {
    print("Darn, some old error \(error.message)")
}
Run Code Online (Sandbox Code Playgroud)

编译器实际上知道该函数只会抛出 ValidationErrors,因此如果您尝试 catch ,它会警告您MyAppError。这是另一种/更好的方法。

do {
    try myFileFunction("hi.jpg")
} catch (let error as ValidationError) {
    switch error {
    case .WrongFileTypeError(let expectedType):
        print("Darn, expected the type \(expectedType)")
    case .InvalidPathError(let path):
        print("Darn, had a bad path \(path)")
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们比较一下 OO 类/继承

class MyAppError : CustomStringConvertible {
    let message: String

    init(message: String) {
        self.message = message
    }

    var description: String {
        return message
    }
}

class ValidationError : MyAppError {

}

class InvalidPathError : ValidationError {
    let path: String

    init(message: String, path: String) {
        self.path = path
        super.init(message: message)
    }

    override var description: String {
        return "\(path) is an invalid path"
    }
}

class WrongFileTypeError : ValidationError {
    let expectedFileType: String

    init(message: String, expectedFileType: String) {
        self.expectedFileType = expectedFileType
        super.init(message: message)
    }

    override var description: String {
        return "Expected type of \(expectedFileType)"
    }
}
Run Code Online (Sandbox Code Playgroud)

_

func myFileFunction(path: String) throws {
    guard let url = NSURL(string: path) else {
        throw InvalidPathError(path: path)
    }
    guard let data = NSDictionary(contentsOfURL: url) else {
        throw WrongFileTypeError(expectedFileType: ".plist")
    }
    print(data)
}

do {
    try myFileFunction("hi.jpg")
} catch (let error as InvalidPathError) {
    print("Darn, had a bad path \(error.path)")
} catch (let error as WrongFileTypeError) {
    print("Darn, expected the type \(error.expectedFileType)")
} catch (let error as MyAppError) {
    print("Some other error")
}
Run Code Online (Sandbox Code Playgroud)

如您所见,它可以工作,但创建错误类会增加很多负担。我们也没有从ValidationError.WrongFileTypeError或 中获得自动命名空间let error as ValidationError。读过这门课的人只知道这一点,InvalidPathError并且WrongFileTypeError被特别抓住。与枚举版本相比,您知道ValidationError实例正在被捕获并且编译器会告诉您这一点。