Javaに「到達不能ステートメント」コンパイラエラーがあるのはなぜですか?


83

プログラムをデバッグするとき、コードのブロック内にreturnステートメントを挿入すると便利であることがよくあります(おそらく悪い習慣ですが)。私はJavaでこのようなことを試みるかもしれません...。

class Test {
        public static void main(String args[]) {
                System.out.println("hello world");
                return;
                System.out.println("i think this line might cause a problem");
        }
}

もちろん、これによりコンパイラエラーが発生します。

Test.java:7:到達不能ステートメント

未使用のコードを持つことは悪い習慣であるため、警告が正当化される理由を理解できました。しかし、なぜこれがエラーを生成する必要があるのか​​理解できません。

これはJavaが乳母になろうとしているだけですか、それともこれをコンパイラエラーにする正当な理由がありますか?


11
Javaもこれについて完全に一貫しているわけではありません。デッドコードを引き起こすいくつかの制御フローについては、Javaは文句を言いません。他の人にとってはそうです。どのコードが死んでいるかを判断することは、計算不可能な問題です。Javaが終了できないものを開始することにした理由はわかりません。
ポール・ドレイパー

回答:


63

到達不能コードはコンパイラにとって無意味だからです。コードを人々にとって意味のあるものにすることは、コンパイラーにとって意味のあるものにすることよりも重要であり、困難ですが、コンパイラーはコードの本質的な消費者です。Javaの設計者は、コンパイラにとって意味のないコードはエラーであるという見方をしています。彼らのスタンスは、到達不能コードがある場合、修正が必要な間違いを犯したというものです。

ここにも同様の質問があります:到達不能コード:エラーまたは警告?、著者は「個人的にはエラーであるべきだと強く感じています。プログラマーがコードを書く場合、それは常に何らかのシナリオで実際に実行することを意図している必要があります。」明らかに、Javaの言語設計者は同意します。

到達不能コードがコンパイルを妨げるべきかどうかは、コンセンサスが得られない問題です。しかし、これがJava設計者がそれを行った理由です。


コメントの多くの人々は、Javaがコンパイルを妨げない到達不能コードのクラスがたくさんあることを指摘しています。ゲーデルの結果を正しく理解していれば、到達不能コードのすべてのクラスをキャッチできるコンパイラはないでしょう。

ユニットテストでは、すべてのバグを検出できるわけではありません。私たちはこれを彼らの価値に対する議論として使用しません。同様に、コンパイラは問題のあるコードをすべてキャッチすることはできませんが、可能であれば不正なコードのコンパイルを防ぐことは依然として価値があります。

Java言語の設計者は、到達不能コードをエラーと見なします。したがって、可能な場合はコンパイルを防止するのが妥当です。


(反対票を投じる前に:問題は、Javaに到達不能なステートメントコンパイラエラーがあるかどうかではありません。問題は Javaに到達不能なステートメントコンパイラエラーがある理由です。Javaが間違った設計上の決定をしたと思うからといって、私に反対票を投じないでください。)


22
明らかに、Javaの言語設計者は、Javaの言語設計者」は人間であり、間違いを犯しやすいことに同意しています。個人的には、あなたが言及している議論の反対側には、はるかに強い議論があると思います。
Nikita Rybak 2010

6
@Gabe:無害ではないので、ほぼ間違いなく間違いです。コードを間違った場所に配置したか、一部のコードに到達できないような方法でステートメントを記述したと誤解したかのいずれかです。これをエラーにすると、誤ったコードが書き込まれるのを防ぐことができます。コード内の到達不能な場所に他の開発者が読めるようにしたい場合(到達不能コードの唯一の対象者)は、代わりにコメントを使用してください。
SamStephens 2010

10
SamStephens:呼び出されていないメソッドと未使用の変数が本質的にすべての形式の到達不能コードであると考えています。一部のフォームを許可し、他のフォームを許可しないのはなぜですか?特に、なぜそのような便利なデバッグメカニズムを許可しないのですか?
Gabe 2010

5
コメントも届きませんか?私の考えでは、到達不能コードは実際にはコメントの形式です(これは私が以前にやろうとしていたことなどです)。しかし、「実際の」コメントも「到達可能」ではないことを考えると...おそらくこのエラーも発生するはずです;)
Brady Moritz

10
問題は、彼ら(Java言語設計者)がこれと矛盾していることです。実際のフロー分析がある場合、両方return; System.out.println();if(true){ return; } System.out.println();が同じ動作をすることが保証されていることを理解するのは簡単ですが、一方はコンパイルエラーであり、もう一方はそうではありません。したがって、デバッグ中にこのメソッドを使用してコードを一時的に「コメントアウト」するときは、そのようにします。コードをコメントアウトする際の問題は、コメントを停止する適切な場所を見つける必要があることです。これは、return;実際にすばやくスローするよりも時間がかかる場合があります。
LadyCailin 2012

47

到達不能なステートメントを許可してはならない明確な理由はありません。他の言語では問題なく使用できます。あなたの特定の必要性のために、これは通常のトリックです:

if (true) return;

それは無意味に見えます。コードを読む人は誰でも、それが意図的に行われたに違いないと推測します。残りのステートメントを到達不能のままにするという不注意な間違いではありません。

Javaは「条件付きコンパイル」を少しサポートしています

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

if (false) { x=3; }

コンパイル時エラーは発生しません。最適化コンパイラは、ステートメントx = 3を認識する場合があります。実行されることはなく、生成されたクラスファイルからそのステートメントのコードを省略することを選択できますが、ステートメントx = 3; ここで指定された技術的な意味で「到達不能」とは見なされません。

この異なる扱いの理論的根拠は、プログラマーが次のような「フラグ変数」を定義できるようにすることです。

static final boolean DEBUG = false;

次に、次のようなコードを記述します。

if (DEBUG) { x=3; }

アイデアは、DEBUGの値をfalseからtrueに、またはtrueからfalseに変更してから、プログラムテキストに他の変更を加えることなくコードを正しくコンパイルできるようにすることです。


2
それでも、あなたが示すトリックに悩む理由がわからない。到達不能コードはコンパイラにとって無意味です。したがって、その対象読者は開発者だけです。コメントを使用してください。一時的に返品を追加する場合は推測しますが、回避策を使用すると、返品を入力して次のコードをコメントアウトするよりもわずかに速い場合があります。
SamStephens 2010

8
@SamStephensしかし、切り替えたいときはいつでも、コードをコメントアウトする必要がある場所と、その逆を行う必要がある場所をすべて覚えておく必要があります。ファイル全体を読み通し、ソースコードを変更する必要があります。おそらく、ある場所で「true」を「false」に置き換えるのではなく、時々微妙な間違いを犯します。スイッチングロジックを一度コーディングして、必要な場合以外は触れない方が良いと思います。
ロバート

「コードを読む人は誰でも、それが意図的に行われたに違いないと推測するでしょう」。これはまさに私の意見の問題です。人々は推測するべきではなく、理解する必要があります。コードを覚えることは、コンピューターへの指示だけでなく、人々とのコミュニケーションです。非常に一時的なもの以外の場合は、私の意見では構成を使用する必要があります。上に表示されているものを見るとif (true) return;、ロジックをスキップしている理由を示しているわけではありませんが、if (BEHAVIOURDISABLED) return;意図を伝えています。
SamStephens 2012年

5
このトリックはデバッグに非常に役立ちます。失敗している場所を見つけようとするメソッドの「残りの部分」を排除するために、if(true)リターンを追加することがよくあります。他にも方法がありますが、これはクリーンかつ迅速である-それはあなたが今までにチェックインすべきものではありません。
ビル・K

18

ナニーです。.Netはこれを正しく理解したと思います。到達不能コードに対して警告が表示されますが、エラーは発生しません。警告されるのは良いことですが、コンパイルを妨げる理由はありません(特に、コードをバイパスするためにリターンをスローするのが良いデバッグセッション中)。


1
javaは以前に設計されたもので、当時は各バイトがカウントされていましたが、フロッピーディスクはまだハイテクでした。
評判の悪い2010

1
デバッグアーリーリターンの場合、アーリーリターンに続く行をコメントアウトするのにさらに5秒かかります。到達不能コードをエラーにすることで防止されたバグのためにプレイするための小さな価格。
SamStephens 2010

6
コンパイラが到達不能コードを除外することも簡単です。開発者にそうするように強制する理由はありません。
ブレイディモリッツ

2
@SamStephensは、すでにコメントがある場合はそうではありません。コメントはスタックされないため、コメントでこれを回避するのは不必要な苦痛です。
enrey 2013年

@SamStephensコメントアウトされたコードはリファクタリングがうまくいきません。
カウリネーター2014

14

私はこの質問に気づいただけで、これに$ .02を追加したいと思いました。

Javaの場合、これは実際にはオプションではありません。「到達不能コード」エラーは、JVM開発者が開発者を何かから保護したり、特に警戒したりすることを考えたという事実からではなく、JVM仕様の要件から発生します。

JavaコンパイラとJVMはどちらも、「スタックマップ」と呼ばれるものを使用します。これは、現在のメソッドに割り当てられた、スタック上のすべてのアイテムに関する明確な情報です。JVM命令が、あるタイプのアイテムを別のタイプに誤用しないように、スタックのすべてのスロットのタイプを知る必要があります。これは、数値がポインタとして使用されるのを防ぐために最も重要です。Javaアセンブリを使用して、数値をプッシュ/格納しようとしてから、オブジェクト参照をポップ/ロードすることができます。ただし、JVMは、クラスの検証中、つまりスタックマップが作成され、整合性がテストされているときに、このコードを拒否します。

スタックマップを検証するには、VMはメソッドに存在するすべてのコードパスをウォークスルーし、どのコードパスが実行されるかに関係なく、すべての命令のスタックデータが以前のコードがプッシュしたものと一致することを確認する必要があります/スタックに保存されます。したがって、次の単純な場合:

Object a;
if (something) { a = new Object(); } else { a = new String(); }
System.out.println(a);

3行目で、JVMは「if」の両方のブランチがオブジェクトと互換性のあるもの(ローカルvar#0)にのみ格納されていることを確認します(3行目以降のコードがローカルvar#0を処理する方法であるため) )。

コンパイラが到達不能コードに到達すると、その時点でスタックがどのような状態にあるかが完全にはわからないため、その状態を確認できません。ローカル変数も追跡できないため、その時点でコードを完全にコンパイルすることはできません。そのため、このあいまいさをクラスファイルに残す代わりに、致命的なエラーが発生します。

もちろん、のような単純な条件if (1<2)はそれをだますでしょうが、それは実際にはだまされていません-それはコードにつながる可能性のある分岐を与えており、少なくともコンパイラとVMの両方がそこからスタックアイテムをどのように使用できるかを決定できますオン。

PSこの場合、.NETが何をするのかわかりませんが、コンパイルも失敗すると思います。これは通常、マシンコードコンパイラ(C、C ++、Obj-Cなど)では問題になりません。


Javaのデッドコード警告システムはどれほど積極的ですか?文法の一部として表現できますか(例:{ [flowingstatement;]* }=> flowingstatement{ [flowingstatement;]* stoppingstatement; }=> stoppingstatementreturn=> stoppingstatementif (condition) stoppingstatement; else stoppingstatement=>stoppingstatement;など?
supercat 2013

私は文法が苦手ですが、そうだと思います。ただし、「停止」はローカルにすることができます。たとえば、ループ内の「break」ステートメントなどです。また、メソッドがvoidの場合、メソッドの終わりはメソッドレベルの暗黙的な停止ステートメントです。'throw'も明示的な停止ステートメントになります。
Pawel Veselov 2013

Java標準はvoid blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); }tryブロックが例外を介して実際に終了する方法はないとJavaがしゃがむのか、または任意の種類の未チェックの例外が任意のtryブロック内で発生する可能性があると判断するのでしょうか。
スーパーキャット2013

見つけるための最良の方法は、それを試してコンパイルすることです。ただし、コンパイラーでは、空のtry / catchステートメントも使用できます。ただし、JVM命令は理論的には例外をスローする可能性があるため、tryブロックは例外を終了する可能性があります。しかし、これがこの質問にどのように関連しているかはわかりません。
Pawel Veselov 2013

基本的に、問題は、禁止されている「到達不能」ステートメントだけが、実際に式を評価することなく、解析に基づいて到達不能として識別できるステートメントであるかどうか、または標準がのような到達不能ステートメントを禁止できるかどうかif (constantThatEqualsFive < 3) {doSomething;};です。
スーパーキャット2013

5

コンパイラの目標の1つは、エラーのクラスを除外することです。到達不能コードが偶然に存在します。javacがコンパイル時にそのクラスのエラーを除外するのは素晴らしいことです。

誤ったコードをキャッチするすべてのルールについて、誰かがコンパイラーにそれを受け入れてもらいたいと思うでしょう。これはコンパイラチェックのペナルティであり、バランスを正しく取ることが言語設計のトリッキーなポイントの1つです。厳密なチェックを行っても、作成できるプログラムの数は無限にあるため、それほど悪くはありません。


5

このコンパイラエラーは良いことだと思いますが、回避する方法があります。真になることがわかっている条件を使用します。

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

コンパイラはそれについて文句を言うほど賢くはありません。


6
ここでの質問はなぜですか?到達不能コードはコンパイラにとって無意味です。したがって、その対象読者は開発者だけです。コメントを使用してください。
SamStephens 2010

3
だから私はJavaの人が彼らのコンパイルを設計した方法に同意するので私は反対票を得るのですか?うわあ...–
ショーンパトリックフロイド

1
あなたは実際の質問に答えなかったために反対票を受け取ります。SamStephensのコメントを参照してください。
–BoltClock

2
javacは間違いなくそれ1<2が正しいと推測でき、メソッドの残りの部分をバイトコードに含める必要はありません。「到達不能ステートメント」のルールは、明確で、修正され、コンパイラーの賢さから独立している必要があります。
評判の悪い2010

1
@Samはその態度で、このサイトのベストアンサーの50%に反対票を投じる必要があります(私の答えがその中にあると言っているわけではありません)。他のITコンサルティングの状況と同様に、SOに関する質問に回答することの大部分は、特定の質問から実際の質問(または関連する副次的な質問)を特定することです。
ショーンパトリックフロイド

0

必要なことを実行できる限り、コンパイラが厳密であればあるほど良いと文句を言うのは確かに良いことです。通常、支払うべき小さな代償はコードをコメントアウトすることですが、コードをコンパイルすると機能するという利点があります。一般的な例は、テスト/デバッグがメインテストのみで短いものであることに気付くまで人々が悲鳴を上げるHaskellです。私は個人的にJavaでデバッグをほとんど行いませんが、(実際には意図的に)注意を払っていません。


0

許可する理由if (aBooleanVariable) return; someMoreCode;がフラグを許可することである場合、if (true) return; someMoreCode;コンパイラtrueはフラグではない(変数ではない)ことを「知っている」ため、コンパイル時エラーを生成しないという事実は、CodeNotReachable例外を生成するポリシーに矛盾があるように見えます。

興味深いかもしれないが、メソッドのコードの一部をオフにすることには適用されない他の2つの方法とif (true) return

さて、言う代わりに、jvm引数if (true) return;に言っassert falseて追加-ea OR -ea package OR -ea classNameしたいかもしれません。良い点は、これによりある程度の粒度が可能になり、jvm呼び出しに追加のパラメーターを追加する必要があるため、コードにDEBUGフラグを設定する必要はありませんが、実行時に引数を追加することで、ターゲットがターゲットでない場合に役立ちます。開発者マシンとバイトコードの再コンパイルと転送には時間がかかります。

System.exit(0)方法もありますが、これはやり過ぎかもしれません。JSPのJavaに配置すると、サーバーが終了します。

Javaが設計上「乳母」言語であることは別として、制御を強化するためにC / C ++のようなネイティブのものを使用したいと思います。


static final静的初期化ブロックにstatic final設定されている変数から宣言に設定されている変数に何かを変更することは、重大な変更であってはなりません。クラスが最初に作成されたとき、何かを実行できない場合があった(実行時に実行できるかどうかがわからない)場合もありますが、それ以降のバージョンのクラスでは実行できた可能性があります。その行動を常にしなさい。myClass.canFoo定数に変更すると、if (myClass.canFoo) myClass.Foo(); else doSomethingElse();効率が上がるはずです。壊さないでください。
スーパーキャット2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.