はじめに
弊社では文系学部出身の新人や未経験中途の方を積極的に採用しています。ゼロからでもプログラミングが学べるように研修を行っているのですが、そこでよく相談されるのが、「プログラムのエラー(異常終了)の原因をどのように調べれば良いか」です。初学者の研修規模のプログラムであれば、慣れてさえしまえばエラー解析はそんなに難しいものではありません。そこで超初学者向けと想定して、簡単ではありますが、エラー解析のコツを見ていきたいと思います。
大切なものをスルーしちゃってませんか
エラー解析に苦労している人たちに共通しているのが、プログラムの異常終了時に出力されている「エラー情報」を把握していないことです。パッと見ると、よく分からないのでスルーしたくなる気持ちはよくわかるのですが、実はこれがとても大切なのです。
エラー情報って?
Go言語を除く、現在主流のほぼ全てのオブジェクト指向言語は例外機構を持っており、プログラムの異常終了時などに例外をthrowしてきます。この時、
- 例外クラス名
- エラーメッセージ
- スタックトレース
といったエラーに関する情報が標準出力に出力されるので、それを読み解くことでエラーの原因と発生箇所を特定します。
Javaを例にサンプルコードを使って実際のエラー情報を見てみましょう。
public class App {
public static void main(String[] args) throws Exception {
Service service = new Service();
String result = service.doService(null);
System.out.println(result);
}
}
public class Service {
public String doService(String id) {
Repository repository = new Repository();
String data = repository.getData(id);
return data;
}
}
public class Repository {
public String getData(String id) {
if (!checkLength(id)) {
return "id is empty";
}
return "This is Id";
}
private boolean checkLength(String id) {
return id.length() > 0 ? true : false;
}
}
このサンプルを実行すると必ず異常終了します。
その時、標準出力には以下のようなエラー情報が出力されます。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "id" is null
at Repository.checkLength(Repository.java:10)
at Repository.getData(Repository.java:3)
at Service.doService(Service.java:4)
at App.main(App.java:4)
では、このエラー情報を読み取ってみましょう。
例外クラス名
最初の一行目にある「java.lang.NullPointerException」が例外クラス名です。
いろんな例外クラスが存在していて、それぞれがエラーの原因を表しています。
例えば、NullPointerExceptionは操作しようとしたオブジェクトがnullで実行できず処理が中断されたことを示す例外クラスです。
例外クラスは色々ありますが、ググればすぐに何を表す例外なのかはわかると思います。
エラーメッセージ
例外クラス名の後ろにあるのはエラーメッセージです。エラー内容の補足説明です。
ここでは、「Stringクラスに定義されているメソッドlength()を実行しようとしたけど、そのオブジェクトを参照している変数idの中身がnullでした」と言っています。
スタックトレース
2行目以降の"at"から始まる記載がスタックトレースです。
簡単に言うとメソッドの呼び出し履歴のようなもので、下から上に呼び出し履歴が積まれていきます。
メソッドの処理が終了すると、ここから削除されます。その名の通りスタック構造です。
ここでは、
- ①Appクラスのmainメソッドが実行された。そのメソッドの中(ソースファイルの4行目)で次のメソッド(②)を呼び出した
- ②ServiceクラスのdoServiceメソッドが実行された。そのメソッドの中で(ソースファイルの4行目)で次のメソッド(③)を呼び出した
- ③RepositoryクラスのgetDataメソッドが実行された。そのメソッドの中で(ソースファイルの3行目)で次のメソッド(④)を呼び出した
- ④RepositoryクラスのcheckLengthメソッドが実行された。そのメソッドの中(ソースファイルの10行目)で次のメソッドを呼び出した
と言うことが読み取れます。
最後に実行されたのはcheckLengthメソッドのソースファイルの10行目です。ここがエラー発生箇所になります。
サンプルコードのエラーを解析してみると...
スタックトレースから読み取れるエラー発生箇所のソースを見ると、エラーメッセージの通りid.length()を実行しようとしています。例外クラス名が示している通り、このidがnullになっていたことが異常終了の原因とわかりました。後は、この変数の値の元を追っていけば根本原因にたどり着けると思います。
根本原因:Appクラスの4行目のserivce.doServiceメソッドの呼び出しでnullを渡していたから
恐れずエラー情報と向き合ってみよう
見慣れないクラス名や謎のメソッド名の羅列などがあり、思わず尻込みしてしまいますが、慣れてしまえばどうと言うことはありません。いつでもこんな綺麗なスタックトレースになるわけではありませんが、じっくりと向き合ってエラー情報を解析してみましょう。