七. Implementation: Working with Errors


如果产品代码格式错误,客户名称过长或地址验证服务超时,该怎么办?任何系统都会有错误,我们如何处理它们很重要。一致且透明的错误处理对于任何类型的生产系统都是至关重要的。

在上一章中,我们故意从 pipeline 的步骤中删除了错误副作用(Result 类型),以便我们可以专注于组合和依赖等问题。 但是副作用很重要! 在本章中,我们将 Result 恢复为类型签名,并学习如何和它们一起工作。

更通俗地说,我们将探索错误处理的函数式方法,使用一种技术,可以优雅地捕获错误,而不会因丑陋的条件判断或 try/catch 语句而污染代码。我们还将看到为什么我们应该将某些类型的错误视为领域错误,并应与其它领域驱动设计一样重视。

Using the Result Type to Make Errors Explicit

函数式编程技术着重于使事情尽可能明确,这也适用于错误处理。我们想要创建明确是否成功的的函数,如果失败,则要说明错误情况是什么。

在我们平时的代码中,错误经常被视为二等公民。但是,为了拥有一个健壮的,有价值的系统,我们应该将他们视为头等公民。而对于属于领域的错误,则更要加倍重视。

在上一章中,我们使用异常来引发错误。这很方便,但是这意味着所有的函数签名都是误导性的。例如,检查地址的函数具有以下签名:

type CheckAddressExists =
    UnvalidatedAddress -> CheckedAddress

这种签名对我们没有帮助, 因为它对我们隐藏了可能会出现的问题。取而代之的是,我们想要的是一个全函数(total function),其中所有可能的结果都由函数签名显式记录。正如之前的章节所说,我们可以使用 Result 类型来明确函数可以成功还是失败,然后签名看起来像这样:

type CheckAddressExists =
    UnvalidatedAddress -> Result<CheckedAddress,AddressValidationError>
and AddressValidationError =
    | InvalidFormat of string
    | AddressNotFound of string

这个签名告诉我们:

This tells us: • The input is an UnvalidatedAddress. • If the validation was successful, the output is a (possibly different) CheckedAddress. • If the validation was not successful, the reason is because the format was invalid or because the address was not found.