Java 通过抛出异常进行流量控制
Java flow control by throwing exceptions
关于首选的流量控制方法,我有一个问题困扰了我一段时间。
我经常遇到这样一种情况,我必须根据方法的 return 值来决定要做什么,该方法是 null
或 not 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
没有被几十个空检查污染。
请各位高手指点应该采用哪一种方案?或者也许还有第三个?
这取决于:
如果缺少值(或 null)在您的逻辑中是正常的和预期的情况,那么您不应该抛出异常,而只是相应地处理它。考虑使用空对象模式 - 而不是 returning null return 一些表示 "non-value" 对象的对象,然后你可以省略对空的检查,因为你的逻辑应该处理这样的空相应地对象(通常您甚至不需要在逻辑中编写单行代码来为此类对象提供服务)。
如果情况是您确实无法继续进行,您应该抛出异常。异常应该用于指示应用程序中的错误。如果您可以事先避免异常(例如检查用户是否使用 file.exists()
指定了正确的文件名),那么您应该这样做(除非它确实超出了正常的逻辑流程并且您无法继续)。
在您的示例中,您真的不应该抛出异常,因为用户输入错误的登录名是正常的(并且是预期的)情况,这应该由正常的逻辑流程处理。
在您的特定情况下,您为什么不 return 重定向指向带有 "user don't exist" 类消息的页面的 uri?
异常方式可以减少代码各部分之间的依赖,所以第一种方式很差。有了它,你需要从 checkLoginExists()
到 process
.
一直传递一个加载了特殊含义 (null == login taken
) 的 null
值
然而抛出异常不是getRedirectUri()
的工作,应该在checkLoginExists()
中完成。我对让 getRedirectUri()
负责检查登录名是否存在感到怀疑。更不用说使用 null
或 Optional
只会给你一个二进制概念 success/fail。如果登录名存在但被锁定怎么办,因此您必须禁止登录并创建一个具有相同名称的新用户。您可能希望重定向到其他页面以表明登录已锁定。
不过,这是基于意见的,高度依赖于具体情况,并且没有您可以遵循的明确规则。
编辑(现在所有这些喧嚣都结束了):一个广泛接受的观点是 正常 流量控制不应该有例外。然而 normal 的定义并不那么容易定义。你不会写这样的代码
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";
首先,如果你使用可选项,你可以完全避免 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 {}
}
接下来,您的第二个版本只有未经检查的异常看起来不错。所以这归结为已检查异常与未检查异常。这是一种宗教问题。
几年前,我曾使用该软件,以前的 "generation" 开发人员遵循未经检查的异常宗教。比如,"we mostly can't handle exceptions reasonably anyway so let's just throw unchecked exceptions and have some routine on the top level to catch them and show the error info dialog to the user"。那次失败非常壮观,重构它极其困难。代码就像一个雷区,你永远不知道什么会发生什么不会发生。
之后我完全切换到 "checked exceptions religion"。如果您遇到一种情况,最好将其描述为编程错误(例如,传递 null
,其中不期望 null 或访问必须存在的类路径资源),那么运行时错误是合适的。在其他情况下建模并使用已检查的异常。
您将(不幸的是)永远不会得到此类问题的明确答案,因为它取决于很多因素,其中很多因素都是基于意见的。
我认为你的两个选择没有完全错误。在 Java 中使用异常进行流量控制被认为是不好的做法(但在其他一些语言中并非如此 - 比如 Python)但是当你说
时你明白了
The controller method process is not contaminated with dozens null
checks.
我教你一招:
当你在两段代码之间犹豫时。问问自己,哪些代码是第一次看的人更容易理解。
在你的第二个解决方案中,很清楚如果会话已经存在会发生什么。在六个月内,另一个开发人员可以查看您的代码,并通过搜索异常处理很容易地找到您在这种情况下所做的事情。
鉴于我给你的技巧,我发现第二种选择更好(以我的拙见)。
希望对您有所帮助。
使用异常控制流程总是坏主意,这里有广泛的解释:
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,你们现在可以给我投反对票了,我不在乎。
并且...检查第二个 link 以获得 真正的程序员 的列表,他们说你不应该使用异常来进行正常的流程控制。
如果你的自我因此而受到刺激,我真的很高兴。
关于首选的流量控制方法,我有一个问题困扰了我一段时间。
我经常遇到这样一种情况,我必须根据方法的 return 值来决定要做什么,该方法是 null
或 not 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
没有被几十个空检查污染。
请各位高手指点应该采用哪一种方案?或者也许还有第三个?
这取决于:
如果缺少值(或 null)在您的逻辑中是正常的和预期的情况,那么您不应该抛出异常,而只是相应地处理它。考虑使用空对象模式 - 而不是 returning null return 一些表示 "non-value" 对象的对象,然后你可以省略对空的检查,因为你的逻辑应该处理这样的空相应地对象(通常您甚至不需要在逻辑中编写单行代码来为此类对象提供服务)。
如果情况是您确实无法继续进行,您应该抛出异常。异常应该用于指示应用程序中的错误。如果您可以事先避免异常(例如检查用户是否使用
file.exists()
指定了正确的文件名),那么您应该这样做(除非它确实超出了正常的逻辑流程并且您无法继续)。
在您的示例中,您真的不应该抛出异常,因为用户输入错误的登录名是正常的(并且是预期的)情况,这应该由正常的逻辑流程处理。
在您的特定情况下,您为什么不 return 重定向指向带有 "user don't exist" 类消息的页面的 uri?
异常方式可以减少代码各部分之间的依赖,所以第一种方式很差。有了它,你需要从 checkLoginExists()
到 process
.
null == login taken
) 的 null
值
然而抛出异常不是getRedirectUri()
的工作,应该在checkLoginExists()
中完成。我对让 getRedirectUri()
负责检查登录名是否存在感到怀疑。更不用说使用 null
或 Optional
只会给你一个二进制概念 success/fail。如果登录名存在但被锁定怎么办,因此您必须禁止登录并创建一个具有相同名称的新用户。您可能希望重定向到其他页面以表明登录已锁定。
不过,这是基于意见的,高度依赖于具体情况,并且没有您可以遵循的明确规则。
编辑(现在所有这些喧嚣都结束了):一个广泛接受的观点是 正常 流量控制不应该有例外。然而 normal 的定义并不那么容易定义。你不会写这样的代码
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";
首先,如果你使用可选项,你可以完全避免 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 {}
}
接下来,您的第二个版本只有未经检查的异常看起来不错。所以这归结为已检查异常与未检查异常。这是一种宗教问题。
几年前,我曾使用该软件,以前的 "generation" 开发人员遵循未经检查的异常宗教。比如,"we mostly can't handle exceptions reasonably anyway so let's just throw unchecked exceptions and have some routine on the top level to catch them and show the error info dialog to the user"。那次失败非常壮观,重构它极其困难。代码就像一个雷区,你永远不知道什么会发生什么不会发生。
之后我完全切换到 "checked exceptions religion"。如果您遇到一种情况,最好将其描述为编程错误(例如,传递 null
,其中不期望 null 或访问必须存在的类路径资源),那么运行时错误是合适的。在其他情况下建模并使用已检查的异常。
您将(不幸的是)永远不会得到此类问题的明确答案,因为它取决于很多因素,其中很多因素都是基于意见的。
我认为你的两个选择没有完全错误。在 Java 中使用异常进行流量控制被认为是不好的做法(但在其他一些语言中并非如此 - 比如 Python)但是当你说
时你明白了The controller method process is not contaminated with dozens null checks.
我教你一招: 当你在两段代码之间犹豫时。问问自己,哪些代码是第一次看的人更容易理解。
在你的第二个解决方案中,很清楚如果会话已经存在会发生什么。在六个月内,另一个开发人员可以查看您的代码,并通过搜索异常处理很容易地找到您在这种情况下所做的事情。
鉴于我给你的技巧,我发现第二种选择更好(以我的拙见)。
希望对您有所帮助。
使用异常控制流程总是坏主意,这里有广泛的解释: 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,你们现在可以给我投反对票了,我不在乎。 并且...检查第二个 link 以获得 真正的程序员 的列表,他们说你不应该使用异常来进行正常的流程控制。 如果你的自我因此而受到刺激,我真的很高兴。