每个应用程序都生活在现实世界中,而现实世界并不完美。因此,即使是理想的,无错误的应用程序也注定会不时地处理错误。
自从第一个计算机程序诞生以来,问题就一直存在,软件工程师发明了许多错误处理方法!
Java传统上使用以下方法向调用者发出信号,指出存在错误:
· 返回一个特殊值(通常,为此目的使用“ null”值)
· 引发异常
这两种方法都有明显的缺点。
返回一个特殊值将丢弃有关错误实际原因的信息,并通过附加检查使代码膨胀。
与正常的执行流程相比,异常的代价非常高,并且使流程难以遵循且难以验证其正确性。一些库和框架倾向于滥用异常,以使其成为正常执行流程的一部分,这是很疯狂的。
因此,有没有其他方法可以通知呼叫者有关错误而又没有上述缺点?是! 函数式编程提供了一种。
请注意,在下文中,我将尽量避免使用FP专用术语。这不会降低方法的功能性,但是可以为那些还不习惯FP lang语的人简化概念。
该Either集装箱
这个想法是使用容器作为返回值,而不是普通值。容器很特殊:虽然被声明为两种类型,但实际上在第一或第二种类型中,每次仅保存一个值。
的Either是通用的容器,不依赖于误差传播/处理。当将其用于错误传播时,按照约定,第一种(或“左”)类型用于表示错误类型,而第二种(或“右”)类型表示返回值类型。
在代码中,这看起来像:
Either parseUUID(final String input) {
2
...
3
// failure
4
return Either.left(ErrorDetails.of("Unable to parse UUID"));
5
...
6
// success
7
return Either.right(uuid);
8
}
实际上,与通常的“做某事并成功返回结果,如果有错误则抛出异常”并没有什么不同。
更深入的外观具有很多优点:
· 不再需要返回一些“特殊”值。
· 有关该错误的信息仍然可用。
· 执行流程没有中断。
上面的代码演示了“生产”方面。现在,让我们看一下“消费”方面的样子:
...// Service interface
2
Either getUserById(final UUID uuid);
3
4
...//Actual use
5
return parseUUID(parameter).flatMapRight(service::getUserById);
此可疑简单代码包含处理错误所需的一切:
· 如果任何处理步骤返回错误,它将返回正确的错误结果。
· 一旦发生错误,它将立即停止处理。
· 它不会中断执行流程, return语句将始终执行,并且始终将值返回给调用方。
· 它强制执行“处理错误或传播错误”策略,从而产生健壮的代码。
· 这种方法的一致应用会产生清晰易读的代码。
专门研究狭窄的用例
可能会注意到,plain Either用于错误处理时非常冗长。
首先,它要求明确引用错误类型。尽管通常错误的基本类型并不多。例如,Java使用单一Throwable类型作为所有错误和异常的基类。
冗长和不便的第二个来源(出于此特定目的)是Either一般意义上的意义,即它可以用于任何类型,并且其API在双方方面都是对称的。当Either用于错误处理时,这要求某些约定的一致应用,例如上面提到的约定。
因此,对于狭义的错误处理,Either可以将其专门化为Result类型,该类型假定单个通用的错误基本类型,并已针对错误处理调整了API。这使代码不再那么冗长,也更容易发生意外错误。
使用Result,上面的代码可以重写为以下代码:
...
2
3
Result parseUUID(final String input) {
4
...
5
return Result.failure(ErrorDetails.of("Unable to parse UUID"));
6
...
7
return Result.success(uuid);
8
}
9
10
...// Service interface
11
Result getUserById(final UUID uuid);
12
13
...
14
return parseUUID(parameter).flatMap(service::getUserById);
现在,代码不再那么冗长,而上面提到的所有属性仍然存在。
修改现有代码以供使用 Result
使用的Result是在自己的代码方便,但我们生活在Java库和框架,不使用它的世界。他们抛出异常并返回空值。因此,我们需要一种方便的方法来与现有代码进行交互。
为此,Reactive Toolbox Core中的 Result实现提供了一组帮助程序方法,该方法允许将传统方法包装到返回Result的方法中。
下面的示例显示如何使用这些辅助方法:
1
interface PageFormattingService {
2
Result format(final URI location);
3
}
4
5
private PageFormattingService service;
6
7
private Result formatPage(final String requestUri) {
8
return lift(URI::create)
9
.apply(requestUri)
10
.flatMap(service::format);
11
}
最后,开发这么多年我也总结了一套学习Java的资料与面试题,如果你在技术上面想提升自己的话,可以关注我,私信发送领取资料或者在评论区留下自己的联系方式,有时间记得帮我点下转发让跟多的人看到哦。