オブジェクト返却メソッドの異常系実装
概要
今回は、メソッド仕様の話。
オブジェクト値を返却するメソッドにおいて、入力値・プログラム状態によって異常な結果になったときにそれを示したい。
その方法として、以下の3通りが考えられる。
- null 返却
- 例外発生
- NULL オブジェクト返却 (参考:サルでもわかる 逆引きデザインパターン 第4章 逆引きカタログ その他 Nullオブジェクト)
これらの使いどころやそれぞれの利点について記述する。
詳細内容
実装内容
コンソールアプリケーションでユーザが入力した文字列からコマンド(コード内に定義)を実行する。
実行例
ls カレントのファイルリスト出力 ls /usr/local/bin /usr/local/bin ディレクトリのファイルリスト出力 rm /usr/local/bin /usr/local/binの削除 aaa 不正なコマンド aaa
基本実装
※ この後実装するため getCommandメソッドを空実装にしている。
package com.example1; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Main { /** * コマンド定義. * */ public static enum Command { ls { @Override public void execute(String... params) { if(params.length == 1) { System.out.println("カレントのファイルリスト出力"); }else { System.out.println(params[1] + " ディレクトリのファイルリスト出力"); } } }, rm { @Override public void execute(String... params) { if(params.length > 1) { System.out.println(params[1] + "の削除"); } } }; public abstract void execute(String... params); } /** * メインロジック.標準入力からコマンドを入力して,登録された処理を実行する. */ public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { String[] elements = line.split(" "); getCommand(elements[0]).execute(elements); } } /** * 入力文字列から処理するコマンドを選択する. * @param cmd コマンド文字列 * @return コマンド実体 */ public static Command getCommand(String cmd) { return null; // TODO: 空実装 } }
getCommandメソッドの異常系処理、それに伴うロジック部の対応
登録されていないコマンドを指定した場合の処理を3通りの方法を実装する。
null を返却する場合
- getCommandの修正
public static Command getCommand(String cmd) { for(Command c : Command.values()) { if(c.name().equals(cmd)) { return c; } } return null; }
- ロジック部修正
public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { String[] elements = line.split(" "); Command cmd = getCommand(elements[0]); if(cmd != null) { cmd.execute(elements); }else { System.out.println("不正なコマンド " + elements[0]); } } }
例外を発生させる場合
- getCommandの修正
public static Command getCommand(String cmd) { for(Command c : Command.values()) { if(c.name().equals(cmd)) { return c; } } throw new IllegalArgumentException(cmd + " is not defined."); }
- ロジック部修正
public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { String[] elements = line.split(" "); try { getCommand(elements[0]).execute(elements); }catch (IllegalArgumentException e) { System.out.println("不正なコマンド " + elements[0]); } } }
NULL オブジェクトを返却する場合
- NULLオブジェクトの定義
public static enum Command { ls { @Override public void execute(String... params) { if(params.length == 1) { System.out.println("カレントのファイルリスト出力"); }else { System.out.println(params[1] + " ディレクトリのファイルリスト出力"); } } }, rm { @Override public void execute(String... params) { if(params.length > 1) { System.out.println(params[1] + "の削除"); } } }, // NULL オブジェクト追加 UNDEFINED { @Override public void execute(String... params) { System.out.println("不正なコマンド " + params[0]); } }; public abstract void execute(String... params); }
- getCommandメソッド修正
public static Command getCommand(String cmd) { for(Command c : Command.values()) { if(c.name().equals(cmd)) { return c; } } return Command.UNDEFINED; }
- ロジック部修正
変更不要
それぞれの実装利点/欠点
null 返却
- 利点
- 結果が存在しないことを意図的に示せる。
- オブジェクトを作成しないため、メモリ領域の節約になる。
- 欠点
例外発生
- 利点
- 欠点
NULL オブジェクト返却
- 利点
- null チェックが必要ないため、利用者のコードがシンプルになる。
- 関連した処理に格納されるため、コードが見やすくなる
- 利用者のコードが複数あっても、お決まりの処理の記述が其々で不要なため、冗長性がなくなり保守性が向上する。
- 欠点
- 事前に定義する必要がある。
まとめ
null 返却の利点は基本的に大きな利点にならないため、意図的に null を返す場合は少ない。
また、null を返すことは思わぬバグを生む可能性があるため余程の意図がない限り行うべきではない。
基本的には、NULL オブジェクトで実装する方が保守性が良くなる。
しかし、全メソッドに NULL オブジェクトを作成しても、多くの NULL オブジェクト定義ができるだけで、あまり効果はない。
つまり、それほど重要でない処理にまでこのような実装をする必要はない。
例外を返す場合、その処理が「本質的にどういう処理ものであるべきか」に基づいて決定すべき。
その処理が仕様として、起こりえなかったり・明らかに不自然であれば、例外で実装するのが良い。
例外仕様設計 - Status Code 303 - See Other
また、異常時に返す値・その意図をドキュメントに示す方が良い(特に null を返す場合)。
そして、これらを意識することによって複数人開発においては以下の利点が見込まれる。
- バグの少ない品質の良いコード
- デバッグに要する時間が少ない保守面で優れたコード
- 開発者に異常系を意識したコーディングの促進
一通りコードが書けるようになった人は、次はメソッド仕様として
このようなことを考えてコーディングできるようにしてみよう!