我应该如何报告 api 包装器中的错误?

How should I report errors from within an api wrapper?

我正在为外部 API 编写一个 API 包装器。我如何让消费者知道包装器中发生的特定错误?

如果消费者支持多种语言,而包装器不支持,我认为只用英文发送错误消息对我来说是不好的。

起初,为了克服这个问题,我想到了使用整数错误代码。但是,有了这个,我只能指出错误的类型,而不能发回一些有用的数据。

现在,我正在考虑使用异常或自定义错误对象。要么直接 throw 它们,要么将它们包装在 Result 对象中,例如 Result.failure(exception)。但是,我不确定我是否应该这样做。以下是我将尝试做的事情:

class WrapperException(val error: WrapperError): Exception()

open class WrapperException: Exception()

class SessionCreationFailedException: WrapperException()
class InvalidRequestTokenException: WrapperException()
class RequestTokenExpiredException: WrapperException()
class SessionRevokedException(val failedSession: String): WrapperException()
class UnexpectedException: WrapperException()

WrapperException 扩展没有意义,因为代码或其他原因可能会出现异常,例如 NullPointerExceptionSocketTimeoutException

最终,消费者仍然需要做类似

的事情
when (exception) {
    is ExceptionA -> handleExceptionA()
    is ExceptionB -> handleExceptionB()
    else -> somethingWentWrong()
}

我认为我不应该尝试用 WrapperException 对异常进行分类以逃避 else 声明。而且,对于要实现这样的错误处理的消费者,他们必须知道函数可以抛出哪些异常(一些文档在这里可能会有所帮助)。

这也让我觉得包装函数将变得不可预测。在未来的更新中,消费者可能不希望 ExceptionZ 出现,但从技术上讲,它可以。所以,也许更新文档和变更日志是我在这里能做的最多的。

我应该避免发送错误信息吗?我很迷惑。请帮助我了解如何让它对消费者有用。

我认为密封 classes 接口是比异常更好的选择,因为 Kotlin 不支持检查异常。 Sealed classes 解决了受检查异常的一些问题,这些问题促使 Kotlin 设计人员首先选择不支持它们,同时又不牺牲代码安全性(忘记捕获您需要捕获的异常的风险)。

  • 密封 classes 在实例化时不会创建不必要且昂贵的堆栈跟踪。
  • 密封class/interface中的每个child都可以包装它想要的任何类型的数据。
  • 不可能忘记检查或处理错误。堆栈中的每个调用都可以选择打开结果并处理可能的错误,或者只是将密封的结果向上传递到堆栈。

在这种特殊情况下,我可能会将所有错误类型分解为一个枚举,因此有一堆密封的 children 可能会随着时间的推移而改变,并在您时弄乱 when 语句添加新的错误。在大多数情况下,您实际上不需要以独特的方式手动处理每种类型的错误。您的枚举 class 可能有一个 属性,其中包含一个 key/ID 用于检索 user-readable 字符串。在这里,我提供了一个 errorData 属性 作为额外数据的可选负载。

sealed interface MyResult<T> {
    data class Success<T>(val result: T): MyResult<T>
    data class Failure(val error: MyError, val errorData: String = ""): MyResult<Nothing>
}

enum class MyError {
    SessionCreationFailed,
    InvalidRequestToken,
    RequestTokenExpired,
    SessionRevoked,
    UnexpectedException
}

如果它变得比您想附加到错误案例的事物类型更复杂,您可以有一个中间接口,它是所有故障类型的 parent。然后 when 使用结果的语句不必迭代所有不同的故障类型。

sealed interface MyResult<T> {
    data class Success<T>(val result: T): MyResult<T>
    interface Failure: MyResult<Nothing>

    object SessionCreationFailed: Failure
    object InvalidRequestToken: Failure
    object RequestTokenExpired: Failure
    data class SessionRevoked(val failedSession: String): Failure
    object UnexpectedException: Failure
}