独自の例外を作る
「引数が不正です」では、何が起きたのか伝わらない
注文を受け付ける処理で、在庫が足りなかったとします。標準の IllegalArgumentException を投げても間違いではありません。ただ、受け取る側には「引数がおかしい」としか伝わりません。数量がマイナスなのか、在庫切れなのか、1 回の上限を超えたのか。区別する手がかりが型に残っていないのです。
すると、こういうコードが生まれます。
Java
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("在庫")) {
// 入荷待ちの案内を出す
}
}メッセージの文言を少し直しただけで壊れる分岐です。しかもコンパイラは何も警告してくれません。起きた出来事を、文字列ではなく型で表せば、この問題は消えます。
業務の言葉でクラスを 1 つ作る
RuntimeException を継承したクラスを 1 つ書くだけで、自分専用の例外ができます。
Java
public class OutOfStockException extends RuntimeException {
public OutOfStockException(String message) {
super(message);
}
}大事なのは super(message) です。ここで親にメッセージを渡さないと、あとで e.getMessage() を見たときに null が返ってきて、原因が何ひとつ分からなくなります。
投げる側は、業務のルールに違反した場所でそのまま throw します。
Java
if (requested > stock) {
throw new OutOfStockException("在庫不足 商品=" + itemId + " 残り=" + stock);
}受け取る側は、専用の型で捕まえられます。
Java
try {
acceptOrder(itemId, requested);
} catch (OutOfStockException e) {
System.out.println("入荷までお待ちください");
}型は catch が読むもの、メッセージは人がログで読むものです。役割を分けておくと、どちらも短く保てます。名前は OutOfStockException のように業務で使っている言葉から取ってください。DataException のような一般名を付けると、せっかく型で分けた意味が薄れます。
Exception と RuntimeException、どちらを継承するか
判断の基準は「呼び出し側に対処を強制したいかどうか」の一点です。
Exception を継承すると検査例外になり、この例外を投げるメソッドには throws の宣言が必要になります。その呼び出し元にも、さらにその呼び出し元にも伝染していきます。強制したい場面では有効ですが、たいていは重すぎます。
RuntimeException を継承すれば非検査例外になり、宣言なしで投げられます。Spring などのフレームワークもほぼこちらで統一しています。迷ったら RuntimeException を選んでおけば大きく外しません。
原因になった例外も一緒に運ぶ
外部システムの呼び出しで起きた例外を、自分の業務例外に包み直すことがあります。このとき元の例外を捨てると、本当の原因がログから消えます。
Java
public OutOfStockException(String message, Throwable cause) {
super(message, cause);
}このコンストラクタを足しておくと、new OutOfStockException("在庫照会に失敗", e) のように元の例外を持たせられます。スタックトレースに両方が並ぶので、追いかけるのが一気に楽になります。
要件
InsufficientFundsExceptionという名前のカスタム例外クラスをSolutionの内部 (static class) として定義することInsufficientFundsExceptionはRuntimeExceptionを継承し、String messageを受け取るコンストラクタでsuper(message)を呼ぶことwithdrawメソッドの中で、amount > balanceのときにInsufficientFundsExceptionをthrowし、同じメソッド内のcatchで受けて-1を返すこと
入出力例
withdraw(1000, 300) → 700
withdraw(500, 1000) → -1
withdraw(0, 0) → 0
withdraw(100, 100) → 0
withdraw(999, 1000) → -1
withdraw(1000000, 1) → 999999