签名,请诚实:Kotlin中的领域错误与函数式处理

签名,请诚实:Kotlin中的领域错误与函数式处理

💡 原文英文,约2600词,阅读约需10分钟。
📝

内容提要

该文讨论Kotlin中函数签名应显式声明领域错误,而非隐藏于实现。作者将失败分为客户端错误、意外异常和领域错误三类,仅后者需在签名中体现。通过密封接口和Either类型,编译器可强制处理所有错误情况,避免遗漏。文章强调窄错误联合优于宽联合,并建议在路由边界映射HTTP状态,使契约贯穿全栈,提升代码可维护性与审查效率。

🔎

延伸解读

三类失败,只有一类该进签名

文章将失败分为客户端错误、意外异常和领域错误。客户端错误(如格式错误)应粗粒度映射为4xx;意外异常(如数据库宕机)应作为500处理,不应建模为领域错误;只有领域错误(如签名窗口关闭)才应出现在函数签名中。这种分类能避免将基础设施问题混入业务逻辑,保持领域模型清晰。

窄错误联合优于宽联合

作者指出,为每个方法定义窄的错误联合类型,比共享一个宽联合类型更好。宽联合(如所有方法共用DocumentError)会导致调用方处理不可能出现的错误分支,变成“带类型的catch-all”。窄联合虽然增加类型数量,但让每个when表达式只处理实际可能的错误,编译器能强制穷尽,减少无效代码。

契约贯穿全栈,映射HTTP在边界

文章强调,领域错误应在服务层和仓储层返回,而HTTP映射只发生在路由边界。这样,控制器是唯一知道HTTP的层,下层只表达业务结果。此外,如果发布API客户端,应同时发布错误类型,使客户端与服务端使用同一密封联合,保持一致性。这有助于代码审查和AI生成代码的验证。

Q&A

Kotlin中如何通过函数签名显式声明领域错误?

在Kotlin中,可以使用Either类型和密封接口来显式声明领域错误。例如,将函数返回类型改为Either<DocumentSignError, Unit>,其中DocumentSignError是一个密封接口,列出所有可能的领域错误。这样调用者必须处理所有错误情况,编译器也会强制检查。

Kotlin中领域错误、客户端错误和意外异常有什么区别?

领域错误是客户端行为正确但操作仍无法成功的情况,如签名代码错误或窗口已关闭,需要在签名中体现。客户端错误是调用者使用API不当,如格式错误或未授权,通常映射为粗粒度的HTTP状态码(如400、403、404)。意外异常是基础设施问题,如数据库不可用或超时,不属于业务结果,应作为操作信号处理(如返回500并记录日志)。

为什么在Kotlin中推荐使用密封接口而不是异常来处理领域错误?

密封接口让编译器知道所有可能的错误子类型,因此在使用when表达式处理错误时,编译器可以验证是否覆盖了所有情况。如果新增一个错误类型,所有未处理的when表达式都会编译失败,从而强制开发者处理新错误。而异常不会在签名中体现,调用者可能遗漏处理,导致运行时才发现问题。

Kotlin中Either类型是什么?它和Optional有什么关系?

Either是一个密封类型,有两个子类型:Left(通常表示失败)和Right(表示成功)。它类似于Java的Optional,但Optional的左侧不携带信息(相当于Unit),而Either的左侧可以携带错误信息。Either提供map、flatMap、fold等辅助方法,用于组合和转换。

在Kotlin中,使用Either时常见的反模式有哪些?

常见的反模式有两种:一是使用Either<Throwable, Unit>,因为Throwable是开放的,无法穷举,相当于没有类型安全;二是使用一个宽泛的错误联合类型(如DocumentError)共享给多个方法,导致每个方法都返回所有错误,调用者必须处理不可能发生的错误。正确做法是为每个公共方法定义窄的错误联合类型。

在Kotlin中,如何组合多个可能失败的操作?

可以使用Either的flatMap进行链式调用,但会导致嵌套。更简洁的方式是使用Arrow库的either { }块,其中bind()函数可以解包Right值并在遇到Left时短路返回。或者使用getOrElse配合early return,例如:val document = findDocument(documentId).getOrElse { return it.left() }。

在Kotlin中,如何将领域错误映射为HTTP响应?

在路由边界(controller层)将领域错误映射为HTTP响应。服务层和仓储层返回领域错误(Either),控制器使用mapLeft将错误转换为HTTP响应。例如:service.signDocument(request).mapLeft { error -> error.toHttpResponse() }。这样,领域错误映射为具体的4xx状态码,意外异常保持为异常并映射为500。

在代码审查中,显式错误签名有什么好处?

显式错误签名让审查者可以从契约(函数签名)开始,而不是深入实现细节。审查者可以快速检查错误联合是否合理、是否有客户端错误伪装成领域错误、新错误是否映射到正确的HTTP状态。这提高了审查效率,尤其对于AI生成的代码,通过检查类型而不是阅读大量实现来验证正确性。

🏷️

标签

➡️

继续阅读