Swift 3 errors with additional data

2019-03-01 08:45发布

问题:

In Swift 3, what is the recommended way to put (potentially lots of) additional information in an error/exception that the catcher can use to solve/handle the problem? In all the examples I've seen, they use enums with associated values, and that seems overly cumbersome/verbose for lots of information.

Specifically, I am writing a simple parser and want a place to store the affected line and column numbers (and potentially other information in the future), but without requiring that every handler explicitly declare those as associated values, as that would be a burden on the caller.

At this point I can basically see two ways of doing this, neither of which seems particularly elegant and both of which require defining two different things:

  1. Define an outer enum error that represents the type of error, and for each case accept a parameter that is an object that contains the additional exception details, or
  2. Use the object as the actual Error and pass in a case from an enum to its constructor to represent the actual error condition.

Both of these feel somewhat unclean to me though as they take two separate concepts to represent a simple idea, an error, and I'm just wondering if there's a nicer way to do this.

Are there any conventions or recommended ways to handle errors that need to contain potentially lots of additional information?

回答1:

I don't know if there is a "recommended" way, perhaps someone else can answer that or provide a better solution.

But one possible approach would be to use a struct (with properties) as the error type and use optional properties for values which need not be provided. Example:

struct ParserError: Error {
    enum Reason {
        case invalidCharacter
        case unexpectedEOF
    }
    let reason: Reason
    let line: Int?
    let column: Int?

    init(reason: Reason, line: Int? = nil, column: Int? = nil) {
        self.reason = reason
        self.line = line
        self.column = column
    }
}

One might also want to adopt the LocalizedError protocol to provide sensible error descriptions even if the concrete error type is not known by the catcher (compare How to provide a localized description with an Error type in Swift?):

extension ParserError: LocalizedError {
    public var errorDescription: String? {
        var description: String
        switch reason {
        case .invalidCharacter:
            description = "Invalid Character in input file"
        case .unexpectedEOF:
            description = "Unexpected end of file"
        }
        if let line = line {
            description += ", line \(line)"
        }
        if let column = column {
            description += ", column \(column)"
        }
        return description
    }
}

Usage example:

func parse() throws {
    // Throw error with line number, but without column:
    throw ParserError(reason: .invalidCharacter, line: 13)
}

do {
    try parse()
} catch let error {
    print(error.localizedDescription)
}

Output:

Invalid Character in input file, line 13