Swift。如何使用错误类型的stderr?

问题描述 投票:1回答:1

我想知道是否有一个简单的方法来使用Swift的Error并同时将其写入stderr。例如,在我的CLI应用程序中,我有这个错误的枚举。

enum ErrorList: Error {
    case alreadyInList
}

extension ErrorList: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .alreadyInList:
            return NSLocalizedString(
                "This item is already in the list.",
                comment: "Info message")
        }
    }
}

以前我用的是一个基本的 OutputType 要写入的枚举 stderr:

fputs("\u{001B}[0;31m\(message)\n", stderr)

有没有一种方法可以轻松地将两者结合起来?如果我用上面的打印行加上 ErrorList 枚举,我得到这个错误。

无法将Int32类型的返回表达式转换为String类型?

该如何操作?哪种方法是最好的?

编辑:下面我尝试了@扫地僧的方法,是这样的。

func validate() throws {
    guard bla bla else {
        let error = ErrorList.alreadyInList
        fputs("\u{001B}[0;31m\(error.errorDescription)\n", stderr)
        throw error
    }
}

func run() throws {
    try validate()
    bla bla
}

上面的代码给了我双倍但正确的输出(红色)。

这个项目已经在列表中了。

错误:这个项目已经在列表中了。这个项目已经在列表中了。

对于下面的代码,我只得到

错误。This item is already in the list.

func validate() throws {
    guard bla bla else {
        throw = ErrorList.alreadyInList
    }
}

func run() throws {
    do {
        try validate()
        bla bla
    } catch let error as ErrorList {
        fputs("\u{001B}[0;31m\(error.errorDescription)\n", stderr)
    }
}
swift command-line-interface stderr
1个回答
1
投票

但我不能把它当作 throw fputs("\u{001B}[0;31m\(ErrorList.alreadyInList)\n", stderr)

你只需要把它分成两个语句就可以了。

let error = ErrorList.alreadyInList
fputs("\u{001B}[0;31m\(error.errorDescription)\u{001B}[0m\n", stderr)
throw error

你也应该考虑将记录部分移到 "日志 "中。catch 块的 do...catch 语句来捕获错误。所以在你的 throws 方法中,你只需要做。

throw ErrorList.alreadyInList

然后在调用者的 throws 方法,你会使用一个 do...catch 这样的说法。

do {
    try throwsMethod()
} catch let error as ErrorList {
    fputs("\u{001B}[0;31m\(error.errorDescription)\n", stderr)
} catch {
    // handle errors that are not ErrorList
}

看到你的编辑,我的意思是这样的。

第一个方法:

func validate() throws {
    guard bla bla else {
        let error = ErrorList.alreadyInList
        fputs("\u{001B}[0;31m\(error.errorDescription)\n", stderr)
        throw error
    }
}

func run() throws {
    do { 
        try validate()
    } catch {
        // handle the error some other way
        // don't log it again! You already did it!
    }
}

第二种方法:

func validate() throws {
    guard bla bla else {
        throw ErrorList.alreadyInList
    }
}

func run() throws {
    do {
        try validate()
    } catch let error as ErrorList {
        fputs("\u{001B}[0;31m\(error.errorDescription)\n", stderr)
    } catch {
        // handle other errors...
    }
}

1
投票

错误只是一个值 你可以自由地操作它们而不扔掉它们。throws 只是一种花哨的 return. 它可以用任何你喜欢的方式生成错误。

虽然Sweeper提出了很好的观点,对于一个命令行应用程序来说,这种方法可能是最好的,但我经常发现在错误产生的地方而不是在错误消耗的地方记录更好。我发现这样可以使日志更清晰地显示原始错误发生的位置,从而使日志更有用(我一般会记录文件和行信息)。当然,不是每一种错误都应该被记录下来。但根据我的经验,生成者往往比调用者更清楚。

所以,把日志提升到自己的函数中,接受一个Error并返回一个Error。

func logging(_ error: LocalizedError) -> LocalizedError {
    fputs("\u{001B}[0;31m\(error.errorDescription ?? "")\n", stderr)
    return error
}

而现在,你什么时候记录就很清楚了,你不需要在所有地方重复记录代码。

func validate() throws {
    guard ... else {
        throw logging(ErrorList.alreadyInList)
    }
}

但是如果你想改变日志记录的方式呢?单元测试什么的怎么办?不是问题。在Swift中,函数是第一类的,所以你可以把它们传来传去,它们甚至可以有默认值。

typealias Logger = (LocalizedError) -> LocalizedError

struct Operation {
    let logging: Logger
    init(logging: @escaping Logger = standardLogging) {
        self.logging = logging
    }

    func validate() throws {
        guard false else {
            throw logging(ErrorList.alreadyInList)
        }
    }
    // ...
}

现在你可以创建 Operation 但对于单元测试,你可以这样做。

class LogAccumulator {
    var logs: [Error] = []

    func logging(_ error: LocalizedError) -> LocalizedError {
        logs.append(error)
        return error
    }
}

let logs = LogAccumulator()
try? Operation(logging: logs.logging).validate()
print(logs.logs)

我通过验证日志输出来进行单元测试,取得了巨大的成功。我认为日志输出是接口的一个显式部分,应该和其他行为一起测试)。它可以让你在不公开私有细节的情况下测试各种难以检查的东西。

© www.soinside.com 2019 - 2024. All rights reserved.