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


82

考虑以下代码:

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

只是为了满足语法和避免编译错误(在注释中说明将不会到达)而在末尾添加额外的return语句是不好的做法,还是有更好的方法编写这样的代码以使额外的内容返回语句是不必要的吗?


25
您的方法带有一个Object参数。如果a不是支持该clone方法的类(或是否在Object?中定义),或者该clone方法期间发生错误(或我现在无法想到的任何其他错误),则可能引发Exception,并且您将到达返回语句。
Arc676

26
您认为无法获得最终回报的假设是错误的。对于实现Cloneable抛出的类,这是完全有效的CloneNotSupportedException。来源:javadocs
Cephalopod

20
可以访问您已注释为“无法到达”的代码。如上所述,从CloneNotSupportedException到此行都有一条代码路径。
大卫·沃特沃思

7
这里的核心问题是:如果假设所调用的类支持克隆,那么为什么要捕获所有异常?异常行为引发异常。
deworde

3
@wero我会AssertionError代替InternalError。如果JVM中出现问题,则后者似乎有特殊用途。并且您基本上断言未达到代码。
kap

Answers:


134

没有额外的return语句的更清晰方法如下。我什么也不会接住CloneNotSupportedException,但让它传给呼叫者。

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

几乎总是可以摆弄命令,以得到比您最初拥有的语法更直接的语法。


28
这说明了一个非常好的总体观点。如果发现自己违反了Java的要求,则通常意味着您试图以次优的方式进行操作。通常,如果我感到不舒服,我会退后一步,看一看整个图片,然后尝试找出是否可以用其他方式使它更清晰地编码(通常是这样)。拥抱Java之后,大多数Java的小nitpicks都会非常有用。
Bill K

3
@BillK:就像Maroun Maroun指出的那样,此代码具有不同的语义。
Deduplicator 2015年

5
AssertionError是一个内置类,其意图类似于TotallyFooException(实际上不存在)。
user253751

5
@BillK:“如果发现自己违反了Java的要求,那通常意味着您试图以次优的方式做某事。” 或者Java决定不支持使您的生活更轻松的功能。
user541686

5
@Mehrdad,...但是,以这种方式查看并不能使您真正实用,除非您是为未来的Java版本或其他语言编写RFE的人。学习本地习语具有实际的实际优势,而将语言(任何语言,除非它具有元编程支持a-la-LISP允许自己添加新语法)归因于不支持您最初想做的事情的方式。
查尔斯·达菲

101

绝对可以实现。请注意,您仅在catch子句中打印stacktrace 。

在场景中a != null,有是一个例外,该return null 达成。您可以删除该语句并将其替换为throw new TotallyFooException();

通常,如果*null是方法的有效结果(即用户期望它并且它表示某些意思),则将其作为“未找到数据”或发生异常的信号返回不是一个好主意。否则,我不认为您不应该回来的任何问题null

Scanner#ioException方法为例:

返回IOException此Scanner的基础Readable抛出的最后一个。如果不存在此类异常,则此方法返回 null

在这种情况下,返回值null具有明确的含义,当我使用该方法时,我可以确定得到的null原因仅是因为没有此类异常,而不是因为该方法尝试执行某些操作而失败了。

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

返回值为null 不一定表示该映射不包含该键的映射。映射也可能将键显式映射为 null。该containsKey操作可用于区分这两种情况。

在这种情况下,null可以指示找到并返回了该 null,或者该哈希图不包含请求的键。


4
尽管您的观点是正确的,但我想指出,这只是糟糕的API设计。两种方法都应分别返回Optional<Throwable>Optional<TValue>。注意:这不是对您的答案的批评,而是对这两种方法的API设计的批评。(但是,这是一个非常常见的设计错误,不仅遍及整个Java API,而且遍及整个.NET也是如此。)
JörgW Mittag 2015年

4
如果您不知道是否可以使用Java 8工具,那么“ cr脚”是相当苛刻的。
托尔比约恩Ravn的安徒生

2
但是,当null不是一个有效的返回值,返回null是一个更糟糕的主意。换句话说,返回null意外情况绝不是一个好主意。
Holger 2015年

1
@Holger我的意思是无效的,例如,当用户期望从不能包含的数据结构中获取值时null,则null永远不能是“有效”的返回值,因为它必须表示其他内容而非正常值返回值。在这种情况下,返回它具有特殊含义,并且我认为该设计没有任何问题(当然,用户应该知道如何处理这种情况)。
Maroun

1
关键是:“用户应该知道如何处理这种情况”意味着您必须指定那null是调用者必须处理的可能的返回值。这使它成为有效值,并且在这里我们对“有效”有相同的理解,因为您将“有效”描述为“用户期望它,这意味着某些东西”。但是在这里,情况还是有所不同。OP在每个代码注释中都指出,他断言return null;“无法达到”这意味着返回null错误。应该是一个throw new AssertionError();替代品……
Holger 2015年

26

只是为了满足语法并避免编译错误而在末尾添加额外的return语句是不好的做法(带注释说明不会到达)

return null对于不可达分支的总站,我认为这是不好的做法。最好抛出一个RuntimeExceptionAssertionError也可以接受),以至于该行出现了非常错误的错误并且应用程序处于未知状态。之所以如此(如上所述),是因为开发人员错过了一些东西(对象可以是非空且不可克隆的)。

InternalError除非我非常确定代码不可访问(例如,在a之后System.exit()),否则我可能不会使用,因为我比VM更有可能犯错。

TotallyFooException如果到达“无法到达的行”与您抛出该异常的其他地方具有相同的含义,则仅使用自定义异常(例如)。


3
如评论中其他地方所述,AssertionError引发“无法发生”事件也是一种合理的选择。
Ilmari Karonen 2015年

2
我个人会扔一个SomeJerkImplementedClonableAndStillThrewACloneNotSupportedExceptionExceptionRuntimeException显然,它将扩展。
w25r 2015年

@ w25r我没有使用上面的代码,而是在回答基本问题。给定Cloneable没有实现公共clone()方法,并且clone()在对象中的是protected上面的代码实际上并未编译。
Michael Lloyd Lee mlk 2015年

我不会使用被动攻击性异常,以防万一它逃脱了(就像我们的一个客户得到一个JerkException,这很有趣,我不认为会感激)。
Michael Lloyd Lee mlk 2015年

14

您抓住了CloneNotSupportedException,这意味着您的代码可以处理它。但是,在捕获到该函数之后,您实际上不知道在到达函数末尾时该怎么办,这意味着您无法处理它。因此,您是对的,在这种情况下,它是一种代码气味,并且在我看来,您应该不会被捕获CloneNotSupportedException


12

我宁愿使用Objects.requireNonNull()来检查Parameter a是否不为null。因此,当您阅读代码时,很明显该参数不应为null。

为了避免检查异常,我将把抛出CloneNotSupportedExceptionRuntimeException

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

public Object getClone(Object a) {

    Objects.requireNonNull(a);

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

}

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(),即使类型aCloneable。至少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) ; } }
}

编译时错误使我想知道为什么Cloneable不将其声明clone为不引发任何检查异常的公共方法。在bugs.java.com/view_bug.do?bug_id=4098033上
Theodore Norvell

2
我想提到的是,绝大多数Java代码都没有使用Lisp风格的花括号,如果您尝试在工作环境中使用它,就会看起来很脏。
基金莫妮卡的诉讼

1
感谢那。其实我不是很一致。我编辑了答案,以始终使用我喜欢的花括号样式。
西奥多·诺维

6

上面的示例是有效的,并且非常Java。但是,这是我将如何处理OP关于如何处理该退货的问题:

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

检查a它是否为null没有任何好处。要去NPE。打印堆栈跟踪也无济于事。不管在哪里处理,堆栈跟踪都是相同的。

使用无用的null测试和无用的异常处理来填充代码没有任何好处。通过去除垃圾,退货问题就没有意义了。

(请注意,OP在异常处理中包含一个错误;这就是为什么需要返回的原因。OP不会误解我建议的方法。)


5

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

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

但是,要回答这个问题,Lint类型的程序肯定还没有弄清楚!我已经看到有两个不同的人在switch语句中对此进行斗争。

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

有人抱怨没有休息就是违反了编码标准。另一个抱怨说拥有它是因为它是无法到达的代码。

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

如果最终遇到这种情况,请选择一个并注释异常,这是您展示给自己的好形式。那是最好的也是最重要的。


5

这不是“仅仅满足语法”。语言的语义要求是每个代码路径都会导致返回或抛出。此代码不符合。如果捕获到异常,则需要以下返回。

没有“坏习惯”,也没有关于使编译器总体满意的“坏习惯”。

无论是语法还是语义,您都别无选择。


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

作为相关说明,您几乎总是可以将代码重构为在最后使用return语句。实际上,这是一个很好的编码标准,因为您不必考虑很多退出点,因此使代码更易于维护和修复错误。同样,出于相同的原因,在方法的开头声明所有变量也是一个好习惯。还要考虑到在结尾处有return可能需要声明更多变量,例如在我的解决方案中,我需要额外的“ Object b”变量。
Pablo Pazos 2015年

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正是由于这个原因块。


2

返回null;这是必需的,因为可能会捕获到异常,但是在这种情况下,因为我们已经检查了它是否为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作为回滚和恢复过程的一部分,执行此操作(无论该块中有什么)。

在这种情况下,仅打印stacktrace然后被迫返回null并不完全是事务/恢复心态。该代码将错误处理责任转移给所有代码调用,getClone以手动检查故障。您可能更喜欢抓住CloneNotSupportedException并将其转换为另一种更有意义的异常形式并抛出该异常,但是在这种情况下,您不想简单地吞下该异常并返回null,因为这不像事务恢复站点。

当抛出异常可以避免这种情况时,您最终将责任泄露给调用者以手动检查和处理失败。

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


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状态。


5
抛出a的超类不是“ vm bug”-抛出aCloneNotSupportedException是完全合法的。它可能在您的代码和/或超类中构成一个错误,在这种情况下,将其包装在或可能是一个(但不是)可能是合适的。或者,您也可以避开例外-如果您的超类不支持克隆,那么您也不支持,因此在语义上是适当的。CloneableRuntimeExceptionAssertionErrorInternalErrorCloneNotSupportedException
Ilmari Karonen 2015年

@IlmariKaronen,您可能希望将此建议发送给Oracle,以便他们可以更正JDK中所有引发内部错误的克隆方法
wero 2015年

@wero毫无疑问,已经有人使用了,但是有一些遗留代码。
deworde
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.