流中的异常处理与任一 背景问题问题

问题描述

背景

我对处理异常的 Scott WLaschin's Railway Oriented Programming 模型很着迷:有一个侧通道,所有坏的东西都将在其中处理,并将好的东西保留在主轨道上。下图:

enter image description here

问题

日常代码中出现的常见模式如下:

  • 一个数据列表
  • 验证每一个
  • 验证可以抛出异常

问题

如何以类似于上面讨论的面向铁路的模型的方式做到这一点。

解决方法

[Somjit's][1] 答案显示了正确的方法,但是它使用外部变量 (errors) 来累积错误。一般来说,这是不鼓励的,因为我们应该避免全局/外部状态。

您可以使用 vavr 的 [partition][2] 方法将流分成两种:一种带有错误,一种带有经过验证的整数。它们都被放入一个元组中:

public void composeExceptions() {
   final Tuple2<Stream<Either<IllegalArgumentException,Integer>>,Stream<Either<IllegalArgumentException,Integer>>> both = Stream.range(1,11)
            .map(this::validate)
            .partition(Either::isLeft);

   both._1.map(Either::getLeft).forEach(e -> System.out.println("Got error: " + e.getMessage()));
   both._2.map(Either::get).forEach(i -> System.out.println("Validated correctly: " + i));
}

编辑 实际上还有其他选项,例如:

Stream
   .range(1,11)
   .map(this::validate)
   .toJavaStream()
   .collect(Collectors.teeing(
      Collectors.filtering(Either::isLeft,toList()),Collectors.filtering(Either::isRight,(errors,ints) -> new Tuple2<>(errors.stream().map(Either::getLeft),ints.stream().map(Either::get))));

使用了 teeing,这是一个来自 Java API 的非常有趣的收集器。不幸的是,它混合了 vavr 和 java API,这不是很好,也不是很糟糕。

还有:

Stream
   .range(1,11)
   .map(this::validate)
   .collect(
      () -> new Tuple2<>(List.<RuntimeException>empty().asJavaMutable(),List.<Integer>empty().asJavaMutable()),(tuple,either) -> {
         either.peekLeft(tuple._1::add);
         either.peek(tuple._2::add);
      },(t1,t2) -> {
         t1._1.addAll(t2._1);
         t1._2.addAll(t2._2);
      }
    )
       .map((exceptions,integers) -> new Tuple2<>(List.ofAll(exceptions),List.ofAll(integers)));```

which uses vavr API only but underneath uses java `List` since a mutable structure is required here.

  [1]: https://stackoverflow.com/a/67556075/542270
  [2]: https://www.javadoc.io/doc/io.vavr/vavr/latest/io/vavr/collection/Traversable.html#partition(java.util.function.Predicate)
,

处理“侧轨”异常的一种简单方法是使用 Vavr 提供的 peekleft 方法,该方法消耗 Either.Left() 侧。我们可以在那里插入我们的异常处理逻辑,并将我们的 Either.right() 东西很好地留在主轨道上,没有任何丑陋。

我很确定这可以改进,并且很想改进它。

List<String> errors = new ArrayList<>();

@Test
public void composeExceptions() {

    List<Integer> valids = IntStream.range(0,11).boxed()
            .map(this::validate)             // throws exceptions
            .peek(this::handleExceptions)    // process left/exceptions on the side
            .flatMap(Value::toJavaStream)    // flatmap is right based
            .collect(Collectors.toList());

    System.out.println("========= Good ones =========");
    System.out.println(valids);
    System.out.println("========= Bad Ones =========");
    errors.forEach(System.out::println);

}

public void handleExceptions(Either<IllegalArgumentException,Integer> either) {
    either.peekLeft(e -> errors.add(e.getMessage())); // is this a monadic bind ???
}

public Either<IllegalArgumentException,Integer> validate(Integer i) {
    if (i % 2 == 0) return Either.right(i);
    return Either.left(new IllegalArgumentException("odd one's out : " + i));
}