チェックされた例外は良いですか悪いですか?

Javaはチェックされた例外をサポートします。この物議を醸す言語機能は、ほとんどのプログラミング言語がチェックされた例外を回避し、チェックされていない対応物のみをサポートするまで、一部の人に愛され、他の人に嫌われています。

この投稿では、チェックされた例外を取り巻く論争を調べます。最初に例外の概念を紹介し、初心者が論争をよりよく理解できるように、例外に対するJavaの言語サポートについて簡単に説明します。

例外とは何ですか?

理想的な世界では、コンピュータプログラムで問題が発生することはありません。ファイルが存在するはずのときにファイルが存在し、ネットワーク接続が予期せず閉じられることはなく、null参照integer-division-byを介してメソッドを呼び出そうとすることもありません。 -ゼロ試行は発生しません。しかし、私たちの世界は理想からほど遠いです。理想的なプログラム実行に対するこれらおよびその他の例外は広範囲に及んでいます。

例外を認識する初期の試みには、失敗を示す特別な値を返すことが含まれていました。たとえば、C言語のfopen()関数はNULL、ファイルを開くことができない場合に戻ります。また、SQLの失敗が発生すると、PHPのmysql_query()関数が返されますFALSE。実際の障害コードを他の場所で探す必要があります。実装は簡単ですが、例外を認識するためのこの「特別な値を返す」アプローチには2つの問題があります。

  • 特別な値は例外を説明しません。どういう意味ですか、NULLそれともFALSE本当に意味がありますか?それはすべて、特別な値を返す機能の作成者に依存します。さらに、ユーザーに意味のあるメッセージを提示できるように、例外が発生したときに特別な値をプログラムのコンテキストにどのように関連付けますか?
  • 特別な値を無視するのは簡単すぎます。たとえば、int c; FILE *fp = fopen("data.txt", "r"); c = fgetc(fp);このCコードフラグメントはfgetc()、をfopen()返しNULLた場合でもファイルから文字を読み取るために実行されるため、問題があります。この場合、fgetc()成功しません。見つけるのが難しいバグがあります。

最初の問題は、クラスを使用して例外を記述することで解決されます。クラスの名前は例外の種類を識別し、そのフィールドは(メソッド呼び出しを介して)何が悪かったのかを判断するための適切なプログラムコンテキストを集約します。2番目の問題は、コンパイラーにプログラマーに例外に直接応答するか、例外を他の場所で処理するように指示させることで解決されます。

いくつかの例外は非常に深刻です。たとえば、空きメモリがない場合、プログラムがメモリの割り当てを試みる場合があります。スタックを使い果たす無限の再帰は別の例です。このような例外はエラーとして知られています。

例外とJava

Javaは、クラスを使用して例外とエラーを記述します。これらのクラスは、java.lang.Throwableクラスに根ざした階層に編成されています。(Throwableこの特別なクラスに名前を付けるために選択された理由はすぐに明らかになります。)そのすぐ下にThrowableは、例外とエラーをそれぞれ説明するクラスjava.lang.Exceptionjava.lang.Errorクラスがあります。

たとえば、Javaライブラリにはが含まれていますjava.net.URISyntaxException。これはException、文字列をUniform ResourceIdentifier参照として解析できなかったことを拡張および示します。URISyntaxException例外クラス名が単語で終わる命名規則に従うことに注意してくださいException。同様の規則が、などのエラークラス名にも適用されますjava.lang.OutOfMemoryError

Exceptionはによってサブクラス化されますjava.lang.RuntimeException。これは、Java仮想マシン(JVM)の通常の操作中にスローされる可能性のある例外のスーパークラスです。たとえば、java.lang.ArithmeticException整数を整数0で除算しようとするなどの算術エラーjava.lang.NullPointerExceptionについて説明します。また、null参照を介してオブジェクトメンバーにアクセスする試みについても説明します。

別の見方 RuntimeException

Java 8言語仕様のセクション11.1.1は、次のように述べています。RuntimeExceptionは、式の評価中にさまざまな理由でスローされる可能性があるが、回復が可能なすべての例外のスーパークラスです。

例外またはエラーが発生したときに、適切からオブジェクトExceptionまたはErrorサブクラスが作成され、JVMに渡されます。オブジェクトを渡す行為は、例外スローすることとして知られています。Javaは、throwこの目的のためにステートメントを提供します。たとえば、指定されたテキストに初期化されるthrow new IOException("unable to read file");新しいjava.io.IOExceptionオブジェクトを作成します。その後、このオブジェクトはJVMにスローされます。

Javaは、try例外がスローされる可能性のあるコードを区切るためのステートメントを提供します。このステートメントは、キーワードとtryそれに続く中括弧で区切られたブロックで構成されます。次のコードフラグメントはtry、次のことを示していthrowます。

try { method(); } // ... void method() { throw new NullPointerException("some text"); }

このコードフラグメントでは、実行がtryブロックに入り、を呼び出しますmethod()。これにより、のインスタンスがスローされますNullPointerException

JVMはスロー可能オブジェクトを受け取り、メソッド呼び出しスタックを検索して、例外を処理するハンドラーを探します。派生しRuntimeExceptionていない例外はしばしば処理されます。実行時の例外とエラーが処理されることはめったにありません。

エラーがめったに処理されない理由

Javaプログラムがエラーから回復するためにできることはほとんどないため、エラーが処理されることはめったにありません。たとえば、空きメモリがなくなると、プログラムは追加のメモリを割り当てることができません。ただし、割り当ての失敗が、解放する必要のある大量のメモリを保持していることが原因である場合、ハンダはJVMの助けを借りてメモリを解放しようとする可能性があります。このエラーコンテキストではハンドラーが役立つように見えるかもしれませんが、試行は成功しない可能性があります。

ハンドラーは、catchブロックに続くブロックによって記述されtryます。catchブロックは、それがハンドルに用意しています例外のリストの種類そのヘッダーを提供します。スローアブルのタイプがリストに含まれている場合、スローアブルはcatchコードが実行されるブロックに渡されます。コードは、プログラムを続行するか、場合によっては終了させるような方法で、失敗の原因に応答します。

try { method(); } catch (NullPointerException npe) { System.out.println("attempt to access object member via null reference"); } // ... void method() { throw new NullPointerException("some text"); }

このコードフラグメントでは、catchブロックにブロックを追加しましたtry。ときにNullPointerExceptionオブジェクトがからスローされmethod()、JVMが見つけとに実行を渡すcatchメッセージを出力ブロック。

最後にブロックします

tryブロックまたはその最後のcatchブロックが続くことができるfinallyように取得したリソースを解放などのクリーンアップタスクを実行するために使用されますブロック。finally議論とは関係がないので、これ以上言うことはありません。

Exceptionとそのサブクラスを除くとそのサブクラスによって記述された例外は、チェックされた例外とRuntimeException呼ばれますthrowステートメントごとに、コンパイラは例外オブジェクトのタイプを調べます。タイプがチェック済みを示している場合、コンパイラはソースコードをチェックして、例外がスローされたメソッドで処理されるか、メソッド呼び出しスタックのさらに上で処理されるように宣言されていることを確認します。他のすべての例外は、チェックされていない例外と呼ばれます

Java lets you declare that a checked exception is handled further up the method-call stack by appending a throws clause (keyword throws followed by a comma-delimited list of checked exception class names) to a method header:

try { method(); } catch (IOException ioe) { System.out.println("I/O failure"); } // ... void method() throws IOException { throw new IOException("some text"); }

Because IOException is a checked exception type, thrown instances of this exception must be handled in the method where they are thrown or be declared to be handled further up the method-call stack by appending a throws clause to each affected method's header. In this case, a throws IOException clause is appended to method()'s header. The thrown IOException object is passed to the JVM, which locates and transfers execution to the catch handler.

Arguing for and against checked exceptions

Checked exceptions have proven to be very controversial. Are they a good language feature or are they bad? In this section, I present the cases for and against checked exceptions.

Checked exceptions are good

James Gosling created the Java language. He included checked exceptions to encourage the creation of more robust software. In a 2003 conversation with Bill Venners, Gosling pointed out how easy it is to generate buggy code in the C language by ignoring the special values that are returned from C's file-oriented functions. For example, a program attempts to read from a file that wasn't successfully opened for reading.

The seriousness of not checking return values

Not checking return values might seem like no big deal, but this sloppiness can have life-or-death consequences. For example, think about such buggy software controlling missile guidance systems and driverless cars.

Gosling also pointed out that college programming courses don't adequately discuss error handling (although that may have changed since 2003). When you go through college and you're doing assignments, they just ask you to code up the one true path [of execution where failure isn't a consideration]. I certainly never experienced a college course where error handling was at all discussed. You come out of college and the only stuff you've had to deal with is the one true path.

Focusing only on the one true path, laziness, or another factor has resulted in a lot of buggy code being written. Checked exceptions require the programmer to consider the source code's design and hopefully achieve more robust software.

Checked exceptions are bad

Many programmers hate checked exceptions because they're forced to deal with APIs that overuse them or incorrectly specify checked exceptions instead of unchecked exceptions as part of their contracts. For example, a method that sets a sensor's value is passed an invalid number and throws a checked exception instead of an instance of the unchecked java.lang.IllegalArgumentException class.

Here are a few other reasons for disliking checked exceptions; I've excerpted them from Slashdot's Interviews: Ask James Gosling About Java and Ocean Exploring Robots discussion:

  • Checked exceptions are easy to ignore by rethrowing them as RuntimeException instances, so what's the point of having them? I've lost count of the number of times I've written this block of code:
    try { // do stuff } catch (AnnoyingcheckedException e) { throw new RuntimeException(e); }

    99% of the time I can't do anything about it. Finally blocks do any necessary cleanup (or at least they should).

  • Checked exceptions can be ignored by swallowing them, so what's the point of having them? I've also lost count of the number of times I've seen this:
    try { // do stuff } catch (AnnoyingCheckedException e) { // do nothing }

    Why? Because someone had to deal with it and was lazy. Was it wrong? Sure. Does it happen? Absolutely. What if this were an unchecked exception instead? The app would've just died (which is preferable to swallowing an exception).

  • Checked exceptions result in multiple throws clause declarations. The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app. This means 1) that a new exception type will affect lots of function signatures, and 2) you can miss a specific instance of the exception you actually -want- to catch (say you open a secondary file for a function that writes data to a file. The secondary file is optional, so you can ignore its errors, but because the signature throws IOException, it's easy to overlook this).
  • Checked exceptions are not really exceptions. The thing about checked exceptions is that they are not really exceptions by the usual understanding of the concept. Instead, they are API alternative return values.

    The whole idea of exceptions is that an error thrown somewhere way down the call chain can bubble up and be handled by code somewhere further up, without the intervening code having to worry about it. Checked exceptions, on the other hand, require every level of code between the thrower and the catcher to declare they know about all forms of exception that can go through them. This is really little different in practice to if checked exceptions were simply special return values which the caller had to check for.

さらに、アプリケーションがアクセスする複数のライブラリから生成される多数のチェック済み例外を処理する必要があるという議論に遭遇しました。ただし、この問題は、Javaの連鎖例外機能と例外の再スローを活用して、スローされた元の例外を保持しながら処理する必要のある例外の数を大幅に減らす巧妙に設計されたファサードによって克服できます。

結論

チェックされた例外は良いですか、それとも悪いですか?言い換えれば、プログラマーはチェックされた例外を処理することを強制されるべきですか、それともそれらを無視する機会を与えられるべきですか?より堅牢なソフトウェアを適用するというアイデアが好きです。ただし、Javaの例外処理メカニズムは、プログラマーにとって使いやすいものにするために進化させる必要があるとも思います。このメカニズムを改善する方法はいくつかあります。