Java中一致的错误传播和处理

每个应用程序都生活在现实世界中,而现实世界并不完美。 因此,即使是理想的,没有错误的应用程序也注定要处理错误。

从第一个计算机程序诞生以来就存在此问题。 软件工程师发明了许多错误处理方法。

Java传统上使用以下方法来向调用者发出错误信号:

  • 返回特殊值(通常将“ null”值用于此目的)抛出异常

这两种方法都有明显的缺点。

返回特殊值将丢弃有关错误实际原因的信息,并通过附加检查使代码膨胀。

与正常的执行流程相比,异常的代价非常高昂,并且使流程难以遵循且难以验证其正确性。 一些库和框架倾向于滥用异常,以使其成为正常执行流程的一部分,这是很疯狂的。

因此,有没有其他方法可以通知呼叫者有关错误而又没有上述缺点? 是! 函数式编程提供了一种。

请注意,在下文中,我将尝试避免使用FP专用术语。 这不会降低方法的功能性,但是可以为那些还不习惯FP-slang的人们简化对这一概念的理解。

The Either<L, R> container

这个想法是使用容器作为返回值,而不是普通值。 容器很特殊:虽然被声明为两种类型,但实际上在第一或第二种类型中,每次仅保存一个值。

的Either<L, R>是通用容器,与错误传播/处理无关。 但是,当将其用于此特定目的时,按照惯例,第一种(或“左”)类型用于表示错误类型,而第二种(或“右”)类型表示返回值类型。

在代码中,如下所示:


   Either<ErrorDetails, UUID> parseUUID(final String input) {
       ...
       // failure
       return Either.left(ErrorDetails.of("Unable to parse UUID"));
       ...
       // success 
       return Either.right(uuid);
   }

实际上,与通常的“成功并返回结果,如果有错误则抛出异常”并没有太大区别。

但是更深的外观具有很多优点:

  • 不再需要返回一些“特殊”值。有关错误的信息仍然可用。执行流没有中断。

上面的代码显示了“生产”方面,现在让我们看一下“消费”方面的样子:

   ...// Service interface
   Either<ErrorDetails, User> getUserById(final UUID uuid);

   ...//Actual use
   return parseUUID(parameter).flatMapRight(service::getUserById); 

此可疑简单代码包含处理错误所需的一切:

  • 如果任何处理步骤返回错误,它将返回正确的错误结果。一旦发生错误,它将立即停止处理。它不会破坏执行流程,返回 statement is always executed and always 返回s value to caller.它强制执行“处理错误或传播错误”策略,从而生成健壮的代码。这种方法的一致应用导致代码清晰易读。

Specializing to Narrow Use Case

可能有人注意到Either<L, R>用于错误处理时,它非常冗长。

首先,它要求显式引用错误类型,尽管通常错误的基本类型并不多。 例如,Java使用单可投掷输入所有错误和异常的基类。

冗长和不便(为此特定目的)的第二个来源是Either<L, R>从某种意义上讲,它是通用的,因为它可以用于任何类型,并且其API在两侧都是对称的。 什么时候Either<L, R>用于错误处理,这需要一致地应用某些约定,如上文所述。

因此,对于较小的错误处理情况,Either<L, R>可以专门Result<T>类型,该类型假定单个通用基本类型的错误,并已针对错误处理调整了API。 这使代码不再那么冗长,也更容易发生意外错误。

用Result<T>上面的代码可以重写为以下代码:


   ...

   Result<UUID> parseUUID(final String input) {
       ...
       return Result.failure(ErrorDetails.of("Unable to parse UUID"));
       ...
       return Result.success(uuid);
   }

   ...// Service interface
   Result<User> getUserById(final UUID uuid);

   ...
   return parseUUID(parameter).flatMap(service::getUserById); 

现在,代码不再那么冗长,而上述所有属性仍然存在。

Adapting Existing Code to use Result<T>

用于Result<T>使用您自己的代码很方便,但是我们生活在不使用Java库和框架的世界中。 他们抛出异常并返回空值。 因此,我们需要一种方便的方法来与现有代码进行交互。

For this purpose Result<T> implementation in Reactive Toolbox Core provides set of helper methods which allow to wrap traditional methods into ones returning Result.

下面的示例显示如何使用这些辅助方法:

   interface PageFormattingService {
       Result<Page> format(final URI location);
   }

   private PageFormattingService service;

   private Result<Page> formatPage(final String requestUri) {
       return lift(URI::create)
               .apply(requestUri)
               .flatMap(service::format);
   } 

Afterword

This article (along with previous one ) is an attempt to describe some main concepts of Reactive Ťoolbox Core library. Of course, none of these concepts are new. I'm just trying to create library which enables convenient and consistent application of these concepts.

我经常看到整篇关于“ Java太老了,应该退休并用现代语言代替”的文章。 上面提到的概念表明,事实并非如此。 在现有的Java功能中,可以编写现代,干净且可靠的代码。 所有必要的是改变习惯和方法,而不是语言。 有趣的是,改变方法比改变语言付出更多的代价,因为方法适用于多种语言。

from: https://dev.to//siy/consistent-error-propagation-and-handling-in-java-158j

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值