请考虑以下代码:
public Object getClone(Cloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException();
}
else {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//cant be reached, in for syntax
return null;
}
return null;
是必要的,因为可能会捕获异常,但是在这种情况下,因为我们已经检查过它是否为null(并假设我们知道我们调用的类支持克隆)所以我们知道尝试声明永远不会失败。
在最后添加额外的return语句只是为了满足语法并避免编译错误(使用注释解释它不会达到),或者是否有更好的方法来编写类似这样的东西,这是不好的做法额外的退货声明是不必要的吗?
答案 0 :(得分:132)
没有额外退货声明的更清晰的方法如下。我也不会抓住CloneNotSupportedException
,而是让它转到来电者。
if (a != null) {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
throw new TotallyFooException();
几乎总是可以摆弄顺序,最终得到比你最初的语法更直接的语法。
答案 1 :(得分:98)
绝对可以达成。请注意,您只是在catch
子句中打印堆栈跟踪。
在a != null
且将作为例外的情况下,会到达return null
。您可以删除该语句并将其替换为throw new TotallyFooException();
。
一般来说, * ,如果null
是方法的有效结果(即用户期望它,则意味着然后返回它作为“未找到数据”或发生异常的信号是不一个好主意。否则,我没有看到任何问题,为什么你不应该返回null
。
以Scanner#ioException
方法为例:
返回此Scanner的基础Readable最后抛出的
IOException
。如果不存在此类异常,此方法将返回 null 。。
在这种情况下,返回值null
具有明确的含义,当我使用该方法时,我可以确定我只有null
因为没有这样的异常,而不是因为方法尝试了做某事但失败了。
* 请注意,即使含义不明确,有时您也希望返回null
。例如HashMap#get
:
返回值 null 并不一定表示地图不包含该键的映射;地图也可能明确地将密钥映射到 null 。
containsKey
操作可用于区分这两种情况。
在这种情况下,null
可以指示找到并返回值 null
,或者hashmap不包含请求的密钥。
答案 2 :(得分:26)
在最后放入额外的return语句只是为了满足语法并避免编译错误(不会达到解释它的注释)是不好的做法。
我认为return null
对于无法到达的分支的终点是不好的做法。最好抛出一个RuntimeException
(AssertionError
也可以接受),以便到达那条线已经出错并且应用程序处于未知状态。
大多数像这样(如上所述),因为开发人员遗漏了一些东西(对象可以是非空的和不可复制的)。
我可能不会使用InternalError
,除非我非常确定代码无法访问(例如在System.exit()
之后)因为我犯的错误比VM更可能
我只使用自定义异常(例如TotallyFooException
),如果到达“无法访问的行”意味着与抛出该异常的任何其他地方相同。
答案 3 :(得分:14)
您抓住了CloneNotSupportedException
,这意味着您的代码可以处理它。但是当你抓住它之后,当你到达函数的末尾时,你根本不知道该怎么做,这意味着你无法处理它。所以你是对的,在这种情况下它是代码味道,在我看来意味着你不应该抓住CloneNotSupportedException
。
答案 4 :(得分:12)
我更愿意使用Objects.requireNonNull()
检查参数a是否为空。因此,当您阅读代码时,参数不应为空,这一点很清楚。
为避免检查异常,我会将CloneNotSupportedException
作为RuntimeException
投放。
对于这两种情况,你可以添加漂亮的文字,意图为什么不应该发生这种情况。或者是这种情况。
public Object getClone(Object a) {
Objects.requireNonNull(a);
try {
return a.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalArgumentException(e);
}
}
答案 5 :(得分:7)
在这种情况下我会写
public Object getClone(SomeInterface a) throws TotallyFooException {
// Precondition: "a" should be null or should have a someMethod method that
// does not throw a SomeException.
if (a == null) {
throw new TotallyFooException() ; }
else {
try {
return a.someMethod(); }
catch (SomeException e) {
throw new IllegalArgumentException(e) ; } }
}
有趣的是,你说" try语句永远不会失败",但你仍然不厌其烦地写一个你声称永远不会执行的语句e.printStackTrace();
。为什么呢?
也许你的信念不是那么牢固。这很好(在我看来),因为您的信念不是基于您编写的代码,而是基于您的客户不会违反前提条件的期望。更好地在防守方面编制公共方法。
顺便说一下,你的代码不会为我编译。即使a.clone()
的类型为a
,您也无法致电Cloneable
。至少Eclipse的编译器这么说。表达式a.clone()
会出错
对于Cloneable
类型,方法clone()未定义
我会针对您的具体案例做些什么
public Object getClone(PubliclyCloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException(); }
else {
return a.clone(); }
}
PubliclyCloneable
由
interface PubliclyCloneable {
public Object clone() ;
}
或者,如果您绝对需要参数类型为Cloneable
,则至少要编译以下内容。
public static Object getClone(Cloneable a) throws TotallyFooException {
// Precondition: "a" should be null or point to an object that can be cloned without
// throwing any checked exception.
if (a == null) {
throw new TotallyFooException(); }
else {
try {
return a.getClass().getMethod("clone").invoke(a) ; }
catch( IllegalAccessException e ) {
throw new AssertionError(null, e) ; }
catch( InvocationTargetException e ) {
Throwable t = e.getTargetException() ;
if( t instanceof Error ) {
// Unchecked exceptions are bubbled
throw (Error) t ; }
else if( t instanceof RuntimeException ) {
// Unchecked exceptions are bubbled
throw (RuntimeException) t ; }
else {
// Checked exceptions indicate a precondition violation.
throw new IllegalArgumentException(t) ; } }
catch( NoSuchMethodException e ) {
throw new AssertionError(null, e) ; } }
}
答案 6 :(得分:6)
以上示例有效且非常Java。但是,我将如何处理OP关于如何处理该回报的问题:
public Object getClone(Cloneable a) throws CloneNotSupportedException {
return a.clone();
}
检查a
以查看它是否为空没有任何好处。它将进入NPE。打印堆栈跟踪也没有帮助。无论处理的位置如何,堆栈跟踪都是相同的。
使用无用的空测试和无用的异常处理来阻止代码是没有好处的。通过删除垃圾,返回问题没有实际意义。
(请注意,OP包含了异常处理中的错误;这就是为什么需要返回的原因.POP不会出错我建议的方法。)
答案 7 :(得分:5)
是否只是为了满足语法不良习惯而使用return语句?
正如其他人所说,在你的情况下,这实际上并不适用。
要回答这个问题,Lint类型的程序肯定无法解决它!我已经看到两个不同的人在切换声明中对此进行斗争。
switch (var)
{
case A:
break;
default:
return;
break; // Unreachable code. Coding standard violation?
}
有人抱怨说不中断是违反编码标准的行为。另一个抱怨 它是一个,因为它是无法访问的代码。
我注意到了这一点,因为两个不同的程序员不断重新检查代码,添加了break然后删除然后添加然后删除,这取决于他们当天运行的代码分析器。
如果你最终遇到这种情况,请选择一个并对异常进行评论,这是你自己展示的好形式。这是最好和最重要的外卖。
答案 8 :(得分:5)
这不仅仅是“满足语法”。语言的语义要求是每个代码路径都会导致返回或抛出。此代码不符合。如果发现异常,则需要返回以下内容。
没有关于它的'不良做法',或者关于满足编译器的一般情况。
无论如何,无论是语法还是语义,你都没有任何选择。
答案 9 :(得分:2)
我会重写这个以在最后得到回报。伪代码:
if a == null throw ...
// else not needed, if this is reached, a is not null
Object b
try {
b = a.clone
}
catch ...
return b
答案 10 :(得分:2)
没有人提到这一点,所以这里是:
public static final Object ERROR_OBJECT = ...
//...
public Object getClone(Cloneable a) throws TotallyFooException {
Object ret;
if (a == null)
throw new TotallyFooException();
//no need for else here
try {
ret = a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
//something went wrong! ERROR_OBJECT could also be null
ret = ERROR_OBJECT;
}
return ret;
}
出于这个原因,我不喜欢return
块内的try
。
答案 11 :(得分:2)
返回null;因为可能会遇到异常,所以是必要的 但是在这种情况下,因为我们已经检查过它是否为空(和 假设我们知道我们调用的类支持克隆)所以我们 知道try语句永远不会失败。
如果您知道所涉及的输入的详细信息,您知道try
语句永远不会失败,那么拥有它的重点是什么?如果您确定事情总是会成功的话,请避免使用try
(尽管您可以在代码库的整个生命周期内完全确定这种情况很少见。)
在任何情况下,遗憾的是编译器不是读者。它会看到函数及其输入,并且根据它所拥有的信息,它需要在底部有return
语句。
最后输入额外的退货声明是不好的做法 只是为了满足语法并避免编译错误(带注释) 解释它将无法达到),或者是否有更好的编码方式 这样的东西,以便额外的return语句是不必要的?
恰恰相反,我建议避免任何编译器警告是好的做法,例如,即使这会花费另一行代码。这里不要过分担心行数。通过测试确定功能的可靠性,然后继续。假装你可以省略return
语句,想象一年之后回到那个代码,然后尝试确定底部的return
语句是否会引起比一些详细描述细节的更多混淆为什么它被省略,因为你可以对输入参数做出假设。最有可能return
语句更易于处理。
那就是说,特别是关于这一部分:
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
...
//cant be reached, in for syntax
return null;
我认为这里的异常处理思维方式有些奇怪。您通常希望在您可以做出有意义的事情的网站上吞下例外。
您可以将try/catch
视为交易机制。 try
进行这些更改,如果它们失败并且我们分支到catch
块,请执行此操作(catch
块中的任何内容)作为回滚和恢复过程的一部分。< / p>
在这种情况下,仅打印堆栈跟踪然后被强制返回null并不完全是事务/恢复思维模式。代码将错误处理职责转移到调用getClone
的所有代码,以手动检查失败。您可能更喜欢捕获CloneNotSupportedException
并将其转换为另一种更有意义的异常形式并将其抛出,但您不希望简单地吞下异常并在此情况下返回null,因为这不像是交易恢复站点。
你最终会将责任泄漏给调用者手动检查并以这种方式处理失败,抛出异常会避免这种情况。
就像你加载一个文件一样,这就是高级别的交易。你可能有一个try/catch
。在trying
加载文件的过程中,您可能会克隆对象。如果此高级操作(加载文件)中的任何位置出现故障,您通常希望将异常一直抛回到此顶级事务try/catch
块,以便您可以从加载失败中正常恢复一个文件(无论是由于克隆错误还是其他原因)。因此,我们通常不希望仅仅在这样一些细粒度的地方吞下一个异常,然后返回一个null,例如,因为这会破坏很多异常的价值和目的。相反,我们希望将异常一直传播回我们可以有意义地处理它的网站。
答案 12 :(得分:0)
您的例子并不理想,如上一段所述:
最后输入额外的退货声明是不好的做法 只是为了满足语法并避免编译错误(带注释) 解释它将无法达到),或者是否有更好的编码方式 这样的东西,以便额外的return语句是不必要的?
更好的例子是克隆本身的实现:
public class A implements Cloneable {
public Object clone() {
try {
return super.clone() ;
} catch (CloneNotSupportedException e) {
throw new InternalError(e) ; // vm bug.
}
}
}
这里的catch子句应该从不输入。语法仍然需要抛出一些东西或返回一个值。由于返回某些内容没有意义,因此InternalError
用于指示严重的VM条件。