通过抛出异常来控制Java流量

时间:2017-10-12 07:06:18

标签: java exception-handling flow-control

我有一个问题困扰我一段时间关于更好的流量控制方法。

我经常会遇到一种情况,我必须根据nullnot null

方法的返回值来决定做什么

所以我有两个选项,我知道如何处理它:

检查是否为空:

public class BasedOnNullFlowControl {

    public String process(String login) {
        String redirectUri = getRedirectUri(login);
        if (redirectUri != null) {
            return redirectUri;
        } else {
            return "Your login is taken";
        }
    }

    private String getRedirectUri(String login) {
        Optional<Login> loginFromDb = checkLoginExists(login);
        if (loginFromDb.isPresent()) {
            return null;
        } else {
            return "some-redirect-url";
        }
    }

    private Optional<Login> checkLoginExists(String login) {
        return Optional.empty(); //assume this value comes from some API
    }

    private class Login {}
}

或者中断流程,但例外:

public class ExceptionFlowControl {

    public String process(String login) {
        return getRedirectUri(login);
    }

    private String getRedirectUri(String login) {
        Optional<Login> loginFromDb = checkLoginExists(login);

        loginFromDb.ifPresent(l -> {
            throw new LoginExistsException();
        });
        return "some-redirect-url";
    }

    private Optional<Login> checkLoginExists(String login) {
        return Optional.empty();
    }

    private class LoginExistsException extends RuntimeException {
    }

    private class Login {}
}

我知道,异常应该仅在特殊情况下使用,但我的情况并不例外,但第二种解决方案看起来对我来说更好,因为我可以添加一些异常处理程序(如在Spring中)并将其转换为一些不错的Http状态代码到底。控制器方法process没有受到数十次空检查的污染。

请问聪明人请告诉我应该使用哪种解决方案?或者可能还有第三个?

5 个答案:

答案 0 :(得分:1)

异常方式允许代码部分之间的依赖性较小,因此第一种方式很差。有了它,您需要从nullnull == login taken一直传递一个加载了特殊含义(checkLoginExists())的process值。

然而,getRedirectUri()抛出异常并非checkLoginExists()的工作,应该在getRedirectUri()中完成。我怀疑null是否负责检查是否存在登录信息。更不用说使用Optionalint[] array = ... int i = 0; try { while(true) { array[i]++; i++; } } catch(ArrayIndexOutOfBoundsException e) { // Loop finished } 只能为您提供成功/失败的二元概念。如果登录存在但被锁定,那么您必须禁止登录以及创建具有相同名称的新用户。您可能希望重定向到其他页面以指示登录已锁定。

尽管如此,这是基于意见的,高度依赖于具体情况,并且没有明确的规则可供您遵循。

编辑(现在所有这个hubbub都结束了):广泛接受的观点是正常流控制不应该用异常来完成。但是, normal 的定义并不那么容易定义。你不会写像

这样的代码
getRedirectUri()

但是,如果你没有使用上面这个荒谬简单的例子,它会进入灰色区域。

另请注意,异常与应用程序错误不同。通过验证一切来尝试解决异常是不可能的(或者至少是不明智的)。如果我们考虑新用户尝试注册用户名的情况,我们希望防止重复的用户名。您可以检查重复并显示错误消息,但仍有两个并发请求尝试注册相同用户名的可能性。此时,其中一个将获得异常,因为数据库将禁止重复(如果您的系统无论如何都是合理的)。

验证很重要,但对于异常没有任何真正特殊的。你需要优雅地处理它们,所以为什么你最终必须处理异常才能验证。如果我们举一个我在评论中写的东西的例子,你需要防止重复和诅咒单词作为用户名,你可能会想出这样的东西(让我们假设它在if(checkForCurseWords(username)) return "login not allowed"; try { // We can also omit the try-catch and let the exception bubble upwards createLogin(username); } catch(DuplicateException e) { return "login exists"; } catch(Exception e) { return "problem with login"; } return "main page"; 中:

function myFunction() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var source = ss.getSheets()[0];
  var range = source.getRange("E2");

  range.copyValuesToRange(226810801, 6, 6,4,4);

}

答案 1 :(得分:1)

首先,如果您使用选项,您可以完全避免null次检查:

public class BasedOnNullFlowControl {

    public String process(String login) {
        String redirectUri = 
        return getRedirectUri(login).orElse("Your login is taken");
    }

    private Optional<String> getRedirectUri(String login) {
        return checkLoginExists(login).map(ifExists -> "some-redirect-url");
    }

    private Optional<Login> checkLoginExists(String login) {
        return Optional.empty(); //assume this value comes from some API
    }

    private class Login {}
}

接下来,您的第二个版本只有未经检查的异常才能看起来不错。因此,这归结为检查与未检查的异常。这是一种宗教问题。

几年前,我曾使用过上一代&#34;的软件。开发商遵循未经检查的例外宗教。就像,&#34;我们大多数都无法合理地处理异常,所以让我们只是抛出未经检查的异常并在顶层有一些例程来捕获它们并向用户显示错误信息对话框&#34; 。那失败了,重构它非常困难。代码就像一个雷区,你永远不知道会发生什么或不可能发生。

之后我完全切换到&#34;检查异常宗教&#34;。如果您的情况更好地描述为编程错误(例如,传递null,其中不需要null或者必须访问类路径资源),那么运行时错误是合适的。在其他情况下,建模并使用已检查的例外。

答案 2 :(得分:0)

你会(遗憾的是)从来没有得到这个问题的明确答案,因为它取决于很多因素,而且很多因素都是基于意见的。

我发现你的两个选择完全没有错。使用流控制的异常在Java中被认为是不好的实践(但在某些其他语言中却不是这样 - 比如Python),但是当你说

时,你得到了一个观点。
  

控制器方法进程没有被数十个null污染   检查。

我给你一个技巧: 当你在两段代码之间犹豫不决时。问问自己,第一次看到它的人会更容易理解哪些代码。

在您的第二个解决方案中,很明显如果会话已经存在会发生什么。在六个月内,另一个develepper可以查看您的代码,并通过搜索异常处理很容易找到您正在做的事情。

鉴于我给你的伎俩,我发现第二个选择更好(以我的拙见)。

希望这有帮助。

答案 3 :(得分:0)

使用例外的控制流程总是糟糕的主意,这里有广泛的解释: https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why

在这里:

http://wiki.c2.com/?DontUseExceptionsForFlowControl

使用异常与普通逻辑的线路应该是明确的,因此异常应该只用于真正的错误,而不是普通应用程序逻辑可以提供的东西。

@lexicore和@Kayaman,你现在可以投票给我,我不在乎。 并且......检查真正的程序员列表的第二个链接,他们说您不应该使用异常来进行正常的流量控制。 如果你的自负因此而烦躁,我真的很开心。

答案 4 :(得分:-1)

取决于:

  • 如果缺少值(或null)是逻辑中的正常和预期情况,那么您不应该抛出异常,而是相应地提供它。考虑使用空对象模式 - 而是返回null返回一些将表示“非值”对象的对象,然后您可以省略对null的检查,因为您的逻辑应该相应地处理这样的null对象(通常您不需要在你的逻辑中编写甚至单行代码来提供这种对象。)

  • 如果情况是你真的无法继续下去,你应该抛出异常。应该使用例外来表示应用程序中的错误。如果您可以事先避免异常(例如,使用file.exists()检查用户是否指定了正确的文件名),那么您应该这样做(除非它真的超出了正常的逻辑流程而且您无法继续)。

在你的例子中,你真的不应该抛出异常,因为用户输入错误的登录是正常的(和预期的)情况,这应该由正常的逻辑流程来处理。

在您的特定情况下,为什么不直接返回指向某个页面的重定向uri,其中包含“用户不存在”类型的消息?