七. 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.