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

Is having a return statement just to satisfy syntax bad practice?

考虑以下代码:

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; 是必需的,因为可能会捕获异常,但是在这种情况下,因为我们已经检查过它是否为空(假设我们知道我们正在调用的 class 支持克隆) 所以我们知道 try 语句永远不会失败。

在末尾添加额外的 return 语句只是为了满足语法并避免编译错误(带有注释解释它不会达到)是不好的做法,还是有更好的方法编写这样的代码以便不需要额外的 return 语句?

肯定能达到。请注意,您仅在 catch 子句中打印堆栈跟踪。

a != null的情况下,return null .您可以删除该语句并将其替换为 throw new TotallyFooException();.

一般来说*,如果null是一个有效方法的结果(即用户 期望 它并且它意味着什么)然后 return 将其作为 "data not found" 的信号或发生异常是 不是 一个好主意。否则,我看不出有什么问题你不应该 return null.

Scanner#ioException方法为例:

Returns the IOException last thrown by this Scanner's underlying Readable. This method returns null if no such exception exists.

在这种情况下,returned 值null 具有明确的含义,当我使用该方法时,我可以确定我得到了 null 只是因为没有这样的异常,而不是因为该方法试图做某事但失败了。

*请注意,有时您确实想要 return null,即使含义不明确。例如 HashMap#get:

A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases.

在这种情况下,null 可以指示 value null 已找到并 returned,或者 hashmap 没有包含请求的密钥。

没有额外return语句的更清晰的方法如下。我也不会抓住 CloneNotSupportedException,但让它交给调用者。

if (a != null) {
    try {
        return a.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}
throw new TotallyFooException();

几乎总是有可能 fiddle 使用命令以比您最初拥有的语法更直接的语法结束。

我更愿意使用 Objects.requireNonNull() 来检查参数 a 是否不为空。所以看了代码就很清楚了,参数不应该为null。

并且为了避免检查异常,我会将 CloneNotSupportedException 作为 RuntimeException 重新抛出。

对于两者,您可以添加漂亮的文字,说明为什么不应该发生这种情况。

public Object getClone(Object a) {

    Objects.requireNonNull(a);

    try {
        return a.clone();
    } catch (CloneNotSupportedException e) {
        throw new IllegalArgumentException(e);
    }

}

你的例子不能很好地说明你在最后一段中提到的问题:

Is it bad practice to put in the extra return statement at the end just to satisfy the syntax and avoid compile errors (with a comment explaining it will not be reached), or is there a better way to code something like this so that the extra return statement is unnecessary?

一个更好的例子是克隆本身的实现:

 public class A implements Cloneable {
      public Object clone() {
           try {
               return super.clone() ;
           } catch (CloneNotSupportedException e) {
               throw new InternalError(e) ; // vm bug.
           }
      }
 }

这里的 catch 子句应该永远不会 输入。语法仍然需要抛出一些东西或 return 一个值。由于 returning 某些东西没有意义,因此 InternalError 用于指示严重的 VM 条件。

Is it bad practice to put in the extra return statement at the end just to satisfy the syntax and avoid compile errors (with a comment explaining it will not be reached)

我认为 return null 对于无法到达的分支的终点是不好的做法。最好抛出一个 RuntimeException (AssertionError 也是可以接受的)因为到达那条线时出现了严重错误并且应用程序处于未知状态。 最像这样的(如上)是因为开发人员遗漏了一些东西(对象可以是 none-null 且不可克隆)。

我可能不会使用 InternalError 除非我非常确定代码无法访问(例如在 System.exit() 之后),因为我犯错的可能性比虚拟机。

我只会使用自定义异常(例如 TotallyFooException),前提是 "unreachable line" 与您在其他任何地方抛出该异常的含义相同。

Is having a return statement just to satisfy syntax bad practice?

正如其他人所提到的,在您的情况下这实际上并不适用。

不过,要回答这个问题,Lint 类型的程序肯定还没有弄明白!我已经看到两个不同的人在 switch 语句中为此争吵不休。

    switch (var)
   {
     case A:
       break;
     default:
       return;
       break;    // Unreachable code.  Coding standard violation?
   }

有人抱怨说没有 中断是违反编码标准的。另一个抱怨说 它是一个,因为它是无法访问的代码。

我注意到这一点是因为两个不同的程序员不断地重新检查代码,添加中断然后删除然后添加然后删除,具体取决于他们当天使用的代码分析器运行。

如果你最终遇到这种情况,请选择一个并评论异常,这是你展示自己的良好形式。这是最好和最重要的收获。

遇到这种情况我会写

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 statement will never fail",但你仍然不厌其烦地写了一个你声称永远不会被执行的声明 e.printStackTrace();。为什么?

也许你的信念没有那么坚定。这很好(在我看来),因为您的信念不是基于您编写的代码,而是基于您的客户不会违反前提条件的期望。最好对 public 方法进行防御性编程。

顺便说一句,你的代码不会为我编译。即使 a 的类型是 Cloneable,您也不能调用 a.clone()。至少 Eclipse 的编译器是这么说的。表达式 a.clone() 给出错误

The method clone() is undefined for the type Cloneable

针对你的具体情况我会做的是

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) ; } }
}

不是 'just to satisfy syntax'。每个代码路径都导致 return 或抛出,这是该语言的语义要求。此代码不符合要求。如果捕获到异常,则需要以下 return。

没有 'bad practice' 关于它,或者关于满足编译器的一般要求。

无论如何,无论是句法还是语义,你都别无选择。

您发现了 CloneNotSupportedException,这意味着您的代码可以处理它。但是在你抓住它之后,当你到达函数的末尾时,你完全不知道该做什么,这意味着你无法处理它。所以你是对的,在这种情况下它是一种代码味道,在我看来意味着你不应该抓住 CloneNotSupportedException.

我会重写它以在末尾添加 return。伪代码:

if a == null throw ...
// else not needed, if this is reached, a is not null
Object b
try {
  b = a.clone
}
catch ...

return b

还没有人提到这个所以这里是:

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;

}

出于这个原因,我不喜欢 try 块内的 return

以上示例有效且非常Java。但是,这是我将如何解决 OP 关于如何处理 return:

的问题
public Object getClone(Cloneable a) throws CloneNotSupportedException {
    return a.clone();
}

检查 a 是否为空没有任何好处。它要去NPE。打印堆栈跟踪也没有帮助。无论在何处处理,堆栈跟踪都是相同的。

用无用的空测试和无用的异常处理来破坏代码没有任何好处。通过删除垃圾,return 问题没有实际意义。

(请注意,OP 在异常处理中包含一个错误;这就是需要 return 的原因。按照我建议的方法,OP 不会出错。)

The return null; is necessary since an exception may be caught, however in such a case since we already checked if it was null (and lets assume we know the class we are calling supports cloning) so we know the try statement will never fail.

如果您知道 try 语句永远不会失败的方式所涉及的输入的详细信息,那么拥有它的意义何在?如果您确信事情总是会成功,请避免使用 try(尽管很少有您可以在代码库的整个生命周期内绝对确定)。

无论如何,不​​幸的是编译器不在意reader。它看到函数及其输入,并根据它所拥有的信息,它需要底部的 return 语句。

Is it bad practice to put in the extra return statement at the end just to satisfy the syntax and avoid compile errors (with a comment explaining it will not be reached), or is there a better way to code something like this so that the extra return statement is unnecessary?

恰恰相反,我建议最好避免任何编译器警告,例如,即使这会花费另一行代码。不要太担心这里的行数。通过测试确定功能的可靠性,然后继续。假设您可以省略 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 块中的内容)作为回滚和恢复过程的一部分作为响应。

在这种情况下,仅打印堆栈跟踪然后被迫 return null 并不完全是 transaction/recovery 的心态。该代码将错误处理责任转移到所有调用 getClone 的代码以手动检查故障。您可能更愿意捕获 CloneNotSupportedException 并将其转换为另一种更有意义的异常形式并抛出它,但您不想简单地吞下异常并且 return 在这种情况下为 null 因为这不像交易恢复网站。

你最终会将责任泄露给调用者,以这种方式手动检查和处理失败,而抛出异常会避免这种情况。

就像加载文件一样,这就是高级事务。你可能在那里有一个 try/catch。在 trying 加载文件的过程中,您可能会克隆对象。如果此高级操作(加载文件)中的任何地方出现故障,您通常希望将异常一直抛回到此顶级事务 try/catch 块,以便您可以从加载失败中优雅地恢复一个文件(无论是由于克隆错误还是其他原因)。因此,我们通常不想像这样在某个细粒度的地方吞下一个异常,然后 return 一个 null,例如,因为这会破坏异常的很多价值和目的。相反,我们希望将异常一直传播回我们可以有意义地处理它的站点。