有一个return语句只是为了满足语法不良的做法吗?

时间:2015-11-19 12:45:55

标签: java return try-catch

请考虑以下代码:

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语句只是为了满足语法并避免编译错误(使用注释解释它不会达到),或者是否有更好的方法来编写类似这样的东西,这是不好的做法额外的退货声明是不必要的吗?

13 个答案:

答案 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对于无法到达的分支的终点是不好的做法。最好抛出一个RuntimeExceptionAssertionError也可以接受),以便到达那条线已经出错并且应用程序处于未知状态。 大多数像这样(如上所述),因为开发人员遗漏了一些东西(对象可以是非空的和不可复制的)。

我可能不会使用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条件。