【初心者向け】例外発生時の対処法

 プログラムを書いていると、度々発生するのが例外です。
 今回は、そういった例外に遭遇したときの対処方法をご紹介します。

スポンサーリンク
rectangle

例外とは

 その名のとおり、開発者が意図しない動作をしようとしたときに発生するのが例外です。

 たとえば、有名なjavaのNullPointerExceptionは、nullを参照したときに発生する例外です。

 これは、javaがnullを参照することを意図していない設計のためです。

 他には、

  • IndexOutOfBoundsExcption
  • NumberFormatException

 などが比較的遭遇する例外なのではないでしょうか。

スタックトレースの読み方

 単純なプログラムでは、スタックトレースの読み方に迷うことは少ないかもしれません。

 しかし、Webアプリではたいていの場合、フレームワークを利用します。

 スタックトレースは、呼び元を順番に羅列しています。フレームワークは、メインとなるコードが多数の処理にラップされていて、スタックトレースが数十行となることも珍しくありません。

 次のコードを実行してみましょう。

public class Main {
    public static void main(String[] args) throws Exception {
        String str = null;
        str.toString();
    }
}

 結果

Exception in thread "main" java.lang.NullPointerException
    at Main.main(Main.java:4)

 2行目は、どの種類のExceptionがthrowされたかを表します。

 3行目以降は、それがどこで発生したかを表します。

 上記の例でいえば、
「NullPointerExceptionがMainクラスのmainメソッド4行目で発生しました」
という意味ですね。

 少し書き換えてみます。

public class Main {
    public static void main(String[] args) throws Exception {
        String str = null;
        Integer.parseInt(str);
    }
}

 結果

Exception in thread "main" java.lang.NumberFormatException: null
    at java.lang.Integer.parseInt(Integer.java:542)
    at java.lang.Integer.parseInt(Integer.java:615)
    at Main.main(Main.java:4)

 今度はNumberFormatExceptionが発生しました。

 しかしmainメソッドで発生しただけのはずの例外が、違うクラスを経由しているように見えます。

 さて、普段我々はInteger.parseInt()というメソッドの実装を知りません。

 API仕様書と使い方を知っていれば、実装を知る必要はないので、意識してソースコードを読むこともないでしょう。しかしながら、今回はparseIntメソッドの内部で例外が発生しています。

 とはいえ、内部実装のソースコードをすべて読むことはありません。NumberFormatExceptionのAPI仕様書にはこう記述されています。

アプリケーションが文字列を数値型に変換しようとしたとき、文字列の形式が正しくない場合にスローされます。

 つまり今回の例でいえば、数値変換出来ないnullを変換しようとしまったことがこの例外発生の根本原因なのです。修正すべきコードは、両方とも結局mainメソッドの4行目です。

 先ほどとの違いは何でしょうか?両方ともメソッドを呼んで、そこで例外が発生していますが…

 string.toString()は、string自体にnullが格納されており、そのnullを参照してしまったために発生した例外になります。イメージ的には、null.toString()のようなものです。

 対して、Integer.parseInt(string)を見てみましょう。
 Integerは実体のあるクラスで、parseIntはstaticメソッドです。また、引数にnullを渡すことに何の問題もありません。中には、nullを渡すことで分岐するようなメソッドもあります。(許可されていないものもありますが、今回は割愛します)

 つまり、Integer.parseIntは問題なく引数を受け取りましたが、内部で問題が発生したということです。しかし前述したように、NumberFormatExceptionにはAPI仕様書があり、明確な説明があるおかげで、そのメソッドの利用者は何が間違っているかを簡単に推測することができます。

 さて、ここまでで、大抵の場合は該当するexceptionのAPI仕様書を読むべきであるということが分かりました。

 しかし、いつも一人で開発している訳ではありませんので、熟知していないフレームワークを使うことも、例外設計が不十分なコードに出会うこともあります。API仕様書すら無い妙なカスタムExceptionが投げられているかもしれません。

 スタックトレースは、呼び元を辿っています。例外が発生した箇所は

java.lang.Integer.parseInt(Integer.java:542)

 ですが、それ以外のどこかに見覚えのあるクラスがあるはずです。上の例でいえば、Main.main(Main.java:4)ですね。特定が出来たら、ステップ実行などでその時点での変数状態を確認してみましょう。問題解決の糸口になるはずです。

 逆に言えば、見覚えのあるクラスが無い場合は、アプリケーション起因の例外ではないことを疑ってください。たとえば、メモリリークだったり、データベースの不整合やロック例外だったりします。

検索を活用する

 exceptionのメッセージとスタックトレースは、情報の宝庫です。たとえば、さっきのNumberFormatExceptionを例に取ると、

java.lang.NumberFormatException: null

 nullであることが明示されています。

 これは検索しなくてもわかる例ですが、試しにそのまま検索インプットに貼り付けてみてください。質問と回答や、その例外について説明しているサイトがあり、それだけで解決できてしまうことが多くあります。どうしても解決できない場合は、質問サイトを利用するのも手です。

最後に

 問題解決は、自力解決だけが正義ではありません。
使えるものはなるべく使い、効率よく問題解決に向かうのがよい方法です。