構文の悪い習慣を満たすためだけに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;例外がキャッチされる可能性があるため、これが必要ですが、そのような場合は、nullであるかどうかをすでに確認しているため(呼び出しているクラスがクローン作成をサポートしていることがわかっていると仮定します)、tryステートメントが失敗することはありません。

構文を満たし、コンパイルエラーを回避するために(到達しないことを説明するコメント付きで)最後に追加のreturnステートメントを挿入するのは悪い習慣ですか、それともこのようなコードを追加して追加するより良い方法がありますか? returnステートメントは不要ですか?


25
メソッドはObjectパラメーターを取ります。メソッドaをサポートするクラスではない場合clone(またはこれはObject?で定義されている場合)、またはcloneメソッド中にエラーが発生した場合(または、現時点では考えられないその他の間違い)、例外がスローされ、次のようになります。 returnステートメント。
arc676 2015年

26
最終的なリターンに到達できないというあなたの仮定は間違っています。を実装するクラスがCloneableをスローすることは完全に有効ですCloneNotSupportedException。出典:javadocs
Cephalopod

20
「到達できない」とコメントしたコードに到達できます。上記のように、CloneNotSupportedExceptionからこの行へのコードパスがあります。
デビッドウォーターワース

7
ここでの中心的な質問:呼び出しているクラスがクローン作成をサポートしていると想定している場合、なぜ例外をキャッチしているのですか?例外的な動作には例外をスローする必要があります。
deworde 2015年

3
@weroのAssertionError代わりに行きInternalErrorます。後者は、JVMの問題が発生した場合に特別に使用されるようです。そして、あなたは基本的にコードに到達していないと主張します。
kap

回答:


134

追加のreturnステートメントを使用しないより明確な方法は次のとおりです。私も捕まえませんCloneNotSupportedExceptionが、発信者に渡してください。

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

ほとんどの場合、最初に持っているものよりも単純な構文になってしまう順序をいじることができます。


28
これは本当に良い一般的なポイントを示しています。Javaの要件と戦っていることがわかった場合、それは通常、次善の方法で何かをしようとしていることを意味します。通常、不快に感じる場合は、一歩下がって全体像を見て、それをより明確にする別の方法でコーディングできるかどうかを判断しようとします。通常はあります。Javaの小さなニッチのほとんどは、一度受け入れると驚くほど役に立ちます。
ビルK

3
@BillK:Maroun Marounが指摘しているように、このコードのセマンティクスは異なります。
デュプリケータ

5
AssertionErrorTotallyFooException(実際には存在しない)と同様の意図を持つ組み込みクラスです。
user253751 2015年

5
@BillK:「Javaの要件と戦っていることがわかった場合、それは通常、次善の方法で何かをしようとしていることを意味します。」または、Javaがあなたの生活を楽にする機能をサポートしないことを決定したこと。
user541686 2015年

5
@ Mehrdad、...しかし、それをそのように見ても、将来のJavaバージョンまたは代替言語のRFEに取り組んでいる人でない限り、実用的に役立つ場所はどこにもありません。ローカルイディオムを学ぶことには実際の実用的な利点がありますが、最初に何かをしたかった方法をサポートしていないことを言語(メタプログラミングサポートa-la-LISPがない限り、任意の言語)のせいにします。
チャールズ・ダフィー

101

それは間違いなく到達することができます。catch句内のスタックトレースのみを出力していることに注意してください。

シナリオではa != nullそことなります例外で、return null なり達すること。そのステートメントを削除して、に置き換えることができthrow new TotallyFooException();ます。

一般的には*、あればnullある有効な(つまり、ユーザがメソッドの結果を期待して、それが何かを意味)は、その後、「データが見つかりません」または例外が発生するための信号としてそれを返すことはありません良いアイデア。そうでなければ、なぜあなたが戻ってはいけないのか、私は何の問題も見ていませんnull

たとえば、次のScanner#ioException方法を考えてみましょう。

IOExceptionこのスキャナーの基礎となるReadableによって最後にスローされたものを返します。このような例外が存在しない場合、このメソッド nullを 返します

この場合、戻り値nullには明確な意味があります。メソッドを使用すると、メソッドnullが何かを実行しようとして失敗したためではなく、そのような例外がなかったためにのみ取得したことを確認できます。

null意味があいまいな場合でも、返却したい場合がありますのでご注意ください。たとえばHashMap#get

null の戻り値は、マップにキーのマッピングが含まれていないことを必ずしも示しているわけではありません。マップがキーを nullに明示的にマップする可能性もあります。このcontainsKey操作は、これら2つのケースを区別するために使用できます。

この場合null nullが見つかって返されたこと、またはハッシュマップに要求されたキーが含まれていないことを示している可能性があります。


4
あなたのポイントは有効ですが、これは単にくだらないAPI設計であることを指摘したいと思います。どちらのメソッドも、それぞれOptional<Throwable>またはを返す必要がOptional<TValue>あります。注:これは、これら2つのメソッドのAPI設計ではなく、あなたの答えに対する批判ではありません。(ただし、これはかなり一般的な設計ミスであり、Java API全体だけでなく、.NETなどにも蔓延しています。)
JörgWMittag 2015年

4
Java 8機能が使用されるかどうかわからない場合、「くだらない」はかなり厳しいです。
するThorbjörnRavnアンデルセン

2
しかし、nullが有効な戻り値でない場合、返すことnullはさらに悪い考えです。言い換えれば、null予期しない状況に戻ることは決して良い考えではありません。
ホルガー

1
@Holger私の平均によって無効ユーザーを期待を含めることはできませんデータ構造から値を取得する場合などのためであるnull、そしてnullそれは何か他のものではなく、通常のを示さなければならないという意味で、「有効」、戻り値になることはありません戻り値。この場合、それを返すことには特別な意味があり、そのデザインに問題はありません(もちろん、ユーザーはこの状況を処理する方法を知っている必要があります)。
マローン

1
それがポイントです。「ユーザーはこの状況を処理する方法を知っている必要があります」とnullは、呼び出し元が処理しなければならない可能性のある戻り値を指定する必要があることを意味します。これは有効な値になります。ここでは、「有効」を「ユーザーが期待し、何かを意味する」と説明するのと同じように「有効」を理解しています。しかし、ここでは、とにかく状況が異なります。OPは、コードごとのコメントで、return null;「到達できない」というステートメントを主張しているnullと述べています。これは、返送が間違っていることを意味します。それはする必要がありますthrow new AssertionError();代わりに...
ホルガー

26

構文を満たし、コンパイルエラーを回避するためだけに、最後に追加のreturnステートメントを挿入するのは悪い習慣ですか(到達しないことを説明するコメント付き)

return null到達不能なブランチの終端には悪い習慣だと思います。その行に到達するには、何かが非常にうまくいかず、アプリケーションが不明な状態にあるため、RuntimeExceptionAssertionErrorも許容されます)をスローすることをお勧めします。開発者が何かを見逃しているため(上​​記のように)、これに最も似ています(オブジェクトはnullなしで、クローン化できない可能性があります)。

VMよりも間違いを犯す可能性が高いInternalErrorため、コードに到達できないことが非常に確実でない限り(たとえば、後などSystem.exit())、使用しない可能性があります。

TotallyFooExceptionその「到達不能な行」に到達することが、その例外をスローする他の場所と同じことを意味する場合にのみ、カスタム例外(など)を使用します。


3
コメントの他の場所で述べられているようAssertionErrorに、「起こり得ない」イベントをスローするための合理的な選択でもあります。
Ilmari Karonen 2015年

2
個人的に私は投げますSomeJerkImplementedClonableAndStillThrewACloneNotSupportedExceptionExceptionRuntimeException明らかに、それは拡張するでしょう。
w25r 2015年

@ w25r上記のコードは使用していませんが、根本的な質問に答えています。GiveCloneableはパブリックclone()メソッドを実装しておらずclone()、オブジェクトではprotected上記のコードは実際にはコンパイルされません。
Michael Lloyd Lee mlk 2015年

エスケープされた場合に備えて、パッシブアグレッシブ例外は使用しません(クライアントの1人がを取得するのJerkExceptionと同じくらい面白いですが、感謝されるとは思いません)。
Michael Lloyd Lee mlk 2015年

14

あなたCloneNotSupportedExceptionはあなたのコードがそれを処理できることを意味するを捕まえました。しかし、それをキャッチした後は、関数の最後に到達したときに文字通り何をすべきかわかりません。これは、それを処理できなかったことを意味します。したがって、この場合はコードの臭いであるというのは正しいことです。私の見解では、捕まえるべきではなかったことを意味しますCloneNotSupportedException


12

Objects.requireNonNull()パラメータaがnullでないかどうかを確認するために使用したいと思います。したがって、コードを読んだときに、パラメーターがnullであってはならないことは明らかです。

また、チェックされた例外を回避するために、をCloneNotSupportedExceptionとして再スローしRuntimeExceptionます。

どちらの場合も、これが発生しない、または発生しない理由を意図して、素敵なテキストを追加できます。

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()はエラーになります

メソッドclone()は、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) ; } }
}

コンパイル時のエラーにより、チェックされた例外をスローしないパブリックメソッドとしてCloneable宣言しないのはなぜか疑問に思いましたclonebugs.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();
}

チェックするメリットはありません anullかどうかする。それはNPEに行きます。スタックトレースの印刷も役に立ちません。スタックトレースは、処理場所に関係なく同じです。

役に立たないnullテストと役に立たない例外処理でコードをジャンクアップするメリットはありません。がらくたを取り除くことによって、リターンの問題は議論の余地があります。

(OPには例外処理にバグが含まれていることに注意してください。これが、リターンが必要な理由です。OPは、私が提案する方法を間違えなかったでしょう。)


5

構文の悪い習慣を満たすためだけにreturnステートメントがありますか?

他の人が述べたように、あなたの場合、これは実際には当てはまりません。

しかし、質問に答えるために、Lintタイプのプログラムは確かにそれを理解していません!私は2つの異なるものがswitchステートメントでこれをめぐってそれを戦うのを見ました。

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

休憩がないことはコーディング標準違反であると不満を言う人もいました。他はそれが不満を持ちますことが到達不能コードがあったので、それは一つでした。

2人の異なるプログラマーが、その日に実行したコードアナライザーに応じて、ブレークを追加してから削除し、追加してから削除してコードを再チェックインし続けたため、これに気付きました。

このような状況に陥った場合は、1つを選択して異常についてコメントしてください。これは、自分で示した良い形式です。それが最良かつ最も重要なポイントです。


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があると、より多くの変数を宣言する必要があるかもしれないことを考慮してください。追加の「Objectb」変数が必要でした。
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ステートメントを省略できるふりをして、1年後にそのコードに戻ることを想像してからreturn、下部にあるそのステートメントが、可能な仮定のために省略された理由の詳細を詳述するコメントよりも混乱を引き起こすかどうかを判断してみてください。入力パラメータについて作成します。ほとんどの場合、returnステートメントは扱いやすくなります。

そうは言っても、特にこの部分について:

try {
    return a.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
...
//cant be reached, in for syntax
return null;

ここでの例外処理の考え方には少し奇妙なことがあると思います。あなたは一般的に、あなたがそれに応じてできる意味のある何かを持っているサイトで例外を飲み込みたいです。

try/catchトランザクションメカニズムと考えることができます。tryこれらの変更を行うと、失敗してcatchブロックに分岐した場合catch、ロールバックおよびリカバリプロセスの一部として、これに応じて(ブロック内にあるものは何でも)実行します。

この場合、単にスタックトレースを出力してから、nullを返すように強制されることは、トランザクション/リカバリの考え方とは異なります。コードは、エラー処理の責任をすべてのコード呼び出しgetCloneに移し、手動で障害をチェックします。をキャッチCloneNotSupportedExceptionして別のより意味のある形式の例外に変換してスローすることをお勧めしますが、これはトランザクション回復サイトとは異なるため、この場合は単に例外を飲み込んでnullを返すことは望ましくありません。

例外をスローすることでこれを回避できる場合は、手動で失敗をチェックして対処する責任を呼び出し元に漏らすことになります。

これは、ファイルをロードする場合のようなもので、高レベルのトランザクションです。あなたはtry/catchそこにいるかもしれません。tryingファイルをロードするプロセス中に、オブジェクトのクローンを作成する場合があります。この高レベルの操作(ファイルのロード)のどこかで障害が発生した場合は、通常、この最上位のトランザクションtry/catchブロックまで例外をスローして、ファイルのロードの失敗から正常に回復できるようにします(ファイルのロードに関係なく)。クローン作成などのエラーが原因です)。したがって、通常、このような細かい場所で例外を飲み込んでからnullを返すことは望ましくありません。たとえば、例外の価値と目的の多くが無効になるためです。代わりに、例外を意味のある方法で処理できるサイトにまで伝播させたいと考えています。


0

あなたの例は、最後の段落で述べたようにあなたの質問を説明するのに理想的ではありません:

構文を満たし、コンパイルエラーを回避するために(到達しないことを説明するコメント付きで)最後に追加のreturnステートメントを挿入するのは悪い習慣ですか、それともこのようなコードを追加して追加するより良い方法がありますか? 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
をスローするスーパークラスは「vmバグ」でCloneNotSupportedExceptionはありません。aCloneableをスローすることは完全に合法です。それは可能性でラッピングしている場合、あなたのコード内および/またはスーパークラスのバグ、構成RuntimeExceptionまたは可能性AssertionError(ではないInternalError)が適切かもしれないが。または、例外をダックすることもできます。スーパークラスがクローン作成をサポートしていない場合は、どちらもサポートしていないため、CloneNotSupportedException意味的に適切です。
Ilmari Karonen 2015年

@IlmariKaronenこのアドバイスをOracleに送信して、InternalErrorをスローするJDK内のすべてのクローンメソッドを修正できるようにすることができます
wero

@wero誰かがすでに持っていることは間違いありませんが、これはレガシーコードのスマックです。
deworde 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.