在执行函数时避免连续的“if(…)”检查

我有一个如下所示的函数:

public Status execute() {
    
    Status status = doSomething();

    if (status != Status.ABORTED) {
        status = doSomethingElse();
    }

    if (status != Status.ABORTED) {
        status = doAgainSomethingElse(param1, param2);
    }

    if (status != Status.ABORTED) {
        doSomethingWhichDoesntReturn(param3);
    }

    //etc.
    
    return status;
}
public Status execute() {
    
    Status status = doSomething();

    if (status != Status.ABORTED) {
        status = doSomethingElse();
    }

    if (status != Status.ABORTED) {
        status = doAgainSomethingElse(param1, param2);
    }

    if (status != Status.ABORTED) {
        doSomethingWhichDoesntReturn(param3);
    }

    //etc.
    
    return status;
}

所以基本上这个函数需要返回一个Status. 这是由第一个函数计算的,然后在执行这些函数时,由后续函数重新计算status != Status.ABORTED

我想重构这段代码,但我没有任何有效的想法。

如果总是这样status = someFunction(someParam),我会使用一个列表Function<TypeInput, Status>并在循环中执行该列表:

List<Function<TypeInput, Status>> actions = List.of(function1, function2...);
for (Function<TypeInput, Status> f : actions) {
    if (status != Status.ABORTED) {
        status = f.apply(input);
    }
}

但问题是每个动作可能不同(有时它是一个返回的函数Status,有时有参数但不总是相同的大小,有时它只是一个空函数等)

有谁有想法吗?

注意:只要statusget Status.ABORTED,我就可以返回(我不需要执行函数的其余部分,因为只有当statusis not 时才会执行任何操作Status.ABORTED)。

回答

这看起来是 try-catch 方法的一个很好的例子。您可以在任一方法中抛出异常,例如StatusAbortedException并捕获该异常以返回适当的状态。它看起来像这样

try {
 Status status = doSomethingElse();
 status = doAgainSomethingElse(param1, param2);
 status = doSomethingWhichDoesntReturn(param3); // this one probably does smth else
 return status;
} catch (StatusAbortedException e){
  // return Status.Aborted 
}


回答

您可以选择多种选择。一种选择是延续传球风格。这在 Java 中看起来不是那么好,但您可以做类似的事情。

// This is pseudo code, intended to illustrate the concept.
cpsMethod(Arg... args, ClosureOverFunctionSoItIsNullary continuation) {
 // do stuff
 continuation.call();
}

所以基本上,该方法将接下来应该发生的事情传递给它。在 Java 中这种方法有一些缺点,即您没有尾调用优化,因此您可能会出现堆栈溢出,而且也许更重要的是,它看起来与普通 Java 非常不同。

// Illustrative pseudo code
return doSomething(() -> doSomethingElse(() -> doAgainSomethingElse(param1, param2, () -> doSomethingWhichDoesntReturn())));

这将删除 ifs,或者更确切地说,将测试放在每个方法中,现在必须决定它是要继续,还是只返回 Status.ABORTED。您当然可以通过将处理放在外面并仅将方法作为生产者,提供谓词/硬编码测试,并仅提供可变参数来使这件事更漂亮:

private continuationPasser(Supplier<Status> first, Supplier<Status>... rest) {
    Objects.requireNonNull(first);
    Status status = first.get();
    for(Supplier<T> continuation : methods) {
        status = continuation.get();
        if(status == Status.ABORTED) {
            return status;
        }
    }
}

Dirt 简单的代码,完全符合您的期望,现在您的调用将来自:

类似于:

public Status execute() {
    return continuationPasser(
      this::doSomething,
      this::doSomethingElse,
      () -> doAgainSomethingElse(arg1, arg2);
      () -> doSomethingWhichDoesntReturn(arg3));

除了,你知道,最后一个不返回任何东西。如果让它返回一些东西很简单,那么你可以这样做。如果这不是微不足道的,您可以将类型从供应商更改为 Function<Status, T>,并且您可以根据需要传入最后一个状态。

但这是一个选择。采取一个功能性的想法并使其发挥作用。如果您知道什么是延续传递,这样做的好处是非常清楚。如果您愿意,您也可以将这个想法概括为采用谓词。另一种方法是稍微改变 continuationPasser,让它传入之前的结果,让方法自己决定他们想要做什么。

然后 continuationPasser 可以是这样的:

continuationPasser(Function<Status, Status> first, Function<Status, Status>... rest) {
    Objects.requireNonNull(first);
    Status status = first.apply(Status.SOME_REASONABLE_VALUE_LIKE_NOT_STARTED);
    // You could use some reduce function here if you want to.
    // The choice of a loop here is just my personal preference.
    for(Function<Status, Status> fun : rest) {
      status = rest.apply(status);
    }
    return status;
}

这使得继续传递器变得更加简单。您首先应用第一个函数,以获得一个起始值。然后你就对其余的人进行 for-each。他们可以从检查 ABORTED 状态开始并提前退出。您仍然可以使用 if,但是您的主要运行代码现在看起来非常整洁。您始终可以将方法包装在以下内容中:

Function<Status, Status> runIfNotAborted(Supplier<Status> supplier) {
  return (Status s) -> s == ABORTED? ABORTED : supplier.get();
}

Function<Status, Status> returnPreviousStatus(Runnable code) {
  return (s) -> {
    code.run();
    return s;
  }
}

现在您甚至不必改变您的方法。(但是,如果您要使用这种样式,如果可用,那可能是更好的选择。)

public Status execute() {
    return continuationPasser(
      runIfNotAborted(this::doSomething),
      runIfNotAborted(this::doSomethingElse),
      runIfNotAborted(() -> doAgainSomethingElse(arg1, arg2)),
      runIfNotAborted(returnPreviousStatus(() -> doSomethingWhichDoesntReturn(arg3)));

现在很清楚发生了什么。我们在函数之上构建函数,看起来有点像函数式装饰器模式。

这是一个非常笼统的想法,如果您愿意,您可以更专业地进行此操作或对其进行更多的概括。但是要小心,否则您将编写一个框架而不必编写 if/else。Jenkins 将这个想法用于它的管道,但它还有更多的东西来传递环境。


以上是在执行函数时避免连续的“if(…)”检查的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>