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

问题描述:

我有一个问题困扰了我一段时间,关于优选的流量控制方法。Java通过抛出异常来控制流量

我经常会遇到的情况,在这里我不得不决定根据返回值是什么做的是一个方法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 {} 
} 

我知道,那例外只能在特殊情况下使用,但我的情况也不例外,仍然第二种方案看起来对我好,因为我可以添加一些异常处理程序(如在春季),并将其转化为一些不错Http状态码到底。控制器方法process没有被几十个空检查污染。

你能否聪明的人请告知应该使用哪一种解决方案?或者也许还有第三个?

+0

请注意,如果您使用'if(Optional.isPresent())',那么您只需用'Optional'替换'null',但不能以任何明智的方式。 – Kayaman

+0

现在看看你的问题引起的混乱。 – Kayaman

+1

谢谢大家的意见,现在我相信,我的问题没有单一的答案,可能一切取决于谁在做代码审查:)我只是想知道是否通过异常来控制应用程序流总是很糟糕练习,即使有时它看起来更简单,更清洁的方式 – nibsa

例外的方式允许代码各部分之间较小的依赖性,所以第一种方式是相当差。有了它,你需要通过装载有特殊含义(null == login taken)均来自checkLoginExists()process的方式null值。

但是这不是getRedirectUri()作业抛出异常,它应该在checkLoginExists()完成。我怀疑getRedirectUri()是否负责检查登录是否存在。更不用说使用nullOptional只能给你一个成功/失败的二元概念。如果登录存在但被锁定,该怎么办?因此,您必须禁止登录以及创建具有相同名称的新用户。您可能需要重定向到其他页面以表明登录已被锁定。

不过,这是基于意见的,高度依赖于具体的情况,并没有一个明确的规则可以遵循。

编辑(现在,这一切喧哗是在用):这是一个被广泛接受的意见是正常流量控制不应有例外来完成。然而,正常的定义并不那么容易定义。你不会写代码像

int[] array = ... 
int i = 0; 
try { 
    while(true) { 
     array[i]++; 
     i++; 
    } 
} catch(ArrayIndexOutOfBoundsException e) { 
    // Loop finished 
} 

但是,如果你没有使用像上面这样一个荒谬的简单的例子,它进入灰色地带。

还要注意,异常与应用程序错误不一样。尝试通过验证一切来解决异常是不可能的(或者至少是不明智的)。如果我们考虑一个新用户正在尝试注册用户名的情况,并且我们希望防止重复的用户名。您可以检查重复并显示错误消息,但仍有可能发生两个并发请求尝试注册相同的用户名。在这一点上,其中一个会得到一个异常,因为数据库将不允许重复(如果你的系统是合理的话)。

验证是重要的,但没有什么真的例外关于例外。你需要优雅地处理它们,所以为什么麻烦证实,如果你最终不得不处理异常。如果我们拿的东西,我的意见,你需要避免重复和骂人的话作为用户名写了一个例子情况下,你可能会想出这样的事情(我们假设这是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"; 
+0

我不同意:异常应该仅用于表示需要修复的应用程序错误,您应该在执行逻辑之前通过执行必要的验证来避免异常。 –

+0

我完全不同意那种不灵活的方法。在这种情况下,验证已完成,但使用异常来指示验证失败。 – Kayaman

+0

验证失败是...正常的逻辑流程,显然不应该通过抛出异常来处理,当你写这几千个小小的kities刚刚死亡。 –

这取决于:

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

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

在你的榜样,你真的不应该抛出异常,因为这是正常的(和预期)的情况是用户输入不正确的登录,这应该通过正常的逻辑流程进行处理。

在你的特殊情况,为什么你不只是返回重定向URI它指向了一些网页“用户不存在”之类的消息的?

+0

因此,根据你的建议,每当我们提交IO文件时,我们需要在我们实际执行IO之前检查文件是否存在(如果有读取),是否有正确的权限以及其他任何情况,而不是通过异常来处理它。 – Kayaman

+0

你也误读了有问题的代码。这不是关于登录,而是关于创建新用户并防止重复登录。 – Kayaman

+1

即使您在实际执行文件IO之前检查了所有内容,它仍然可能会失败(例如,在检查和IO之间删除文件)。 – lexicore

第一所有,如果您使用自选,你可以完全避免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 {} 
} 

接下来,你的第二个版本只用看unchecked异常漂亮所以这归结为检查与unchecked异常这是一种。宗教问题。

几年前,我与软件合作过,前一代“开发者”遵循不受限制的异常宗教信仰。就像,“我们大多无法合理地处理异常,所以我们只是抛出未经检查的异常,并在顶层有一些例程来捕获它们并向用户显示错误信息对话框。”那个壮观失败,重构它非常困难。代码就像是一个雷场,你永远不知道会发生什么或不会发生什么。

之后,我完全切换到“检查异常宗教”。如果你有一种情况会更好地描述为编程错误(例如,通过null,其中没有预期的空或者必须访问必须存在的类路径资源),那么运行时错误是合适的。在其他情况下,模型和使用检查的异常。

+0

我不同意:异常应该仅用于表示需要修复的应用程序错误,您应该在执行逻辑之前通过执行必要的验证来避免异常。 –

+0

@KrzysztofCichocki如果我写一个公共方法,通过契约不接受'null's,我通常会检查与'Objects.requireNonNull'。如果有人用'null'调用方法,他们会得到一个NPE。在我的方法中,我无法验证“在执行逻辑之前”,这是无稽之谈,因为逻辑已经被执行。 而且,特殊情况并不一定意味着应该修复应用程序中的错误。这意味着你无法或不能决定不处理这种情况。 – lexicore

+0

在这种情况下,这应该在方法doc中描述,因此调用方法的方法需要正确使用它(在调用方法之前检查它的值)。或者如果检查逻辑更加复杂,你应该给他一种检查方法,例如在File类中检查,例如'file.exists()' –

你会(不幸)永远不会得到明确的答案,因为这取决于很多因素,这些因素很多都是基于意见的。

我看不出你的两个选项完全错误。使用流量控制的异常被认为是不好practrice在Java中(但不是在其他一些语言的情况下 - 如Python),但你说的有道理,当你说

控制器方法过程不受污染几十空 检查。

我给你一个窍门: 当两片的代码之间犹豫。问问自己,第一次看到它的代码会更容易理解。

在第二种解决方案中,清楚会话已存在会发生什么。在六个月内,另一个develepper可以查看你的代码,并通过搜索异常处理很容易找到你在做什么。

鉴于我给你的技巧,我发现第二个选项更好(在我的愚见)。

希望这会有所帮助。通过使用异常

+0

我已经给他一个明确的答案。我不同意你,因为抛出异常是最糟糕的解决方案。 –

控制流量总是坏主意,它被广泛地解释这里: https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why

这里:

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

beewten使用异常与正常逻辑线应该是清楚,所以异常只能用于真正的错误,而不能用于普通应用逻辑可以提供的东西。

@lexicore和@Kayaman,你现在可以让我冷静下来,我不在乎。 然后...检查第二个链接的真正的程序员名单谁说你不应该使用正常流量控制异常。 如果你的自我因此被激怒,我真的很高兴。