2日目のゴール
2日目のテーマは
「try / catch を“とりあえず囲む文法”から、“設計の道具”に格上げする」 ことです。
今日はこういうことを狙います。
どの例外を、どこで、どう扱うかを意識する
「全部 catch する」の危険さを知る
異常系を“パターン”として考えられるようにする
1日目の割り算アプリを、もう一段だけ“ちゃんとした例外処理アプリ”に育てていきます。
例外を「種類」として意識する
例外には“意味のある種類”がある
Java の例外は、ざっくり言うと「何がまずかったか」を種類で表しています。
数字にできない文字列 → NumberFormatException
0 で割った → ArithmeticException
存在しないファイルを開いた → FileNotFoundException
1日目では、NumberFormatException と ArithmeticException を使いましたね。
2日目では、
「例外の種類ごとに、どう振る舞うかを決める」
という視点を強く持ちます。
例外の“親子関係”を軽く知っておく
細かく覚えなくていいですが、
イメージだけ持っておくと役に立つので、少しだけ。
Exception が大きな親
その下に、いろいろな種類の例外がぶら下がっているNumberFormatException や ArithmeticException も、その子どもたち
だから、こういう書き方もできます。
try {
// 何かしら失敗するかもしれない処理
} catch (Exception e) {
System.out.println("何かしらの例外が発生しました");
}
Javaでも、これには落とし穴があります。
「とりあえず Exception で全部 catch」は危険
何が起きたか分からなくなる
catch (Exception e) は、
「どんな例外でも、とりあえずここで受け止める」という意味です。
一見便利ですが、
「何が起きたか」がコードから見えなくなる という問題があります。
数字がおかしいのか
0 で割ったのか
ファイルがないのか
全部「何かしらの例外」として扱われてしまう。
例:悪いパターンのコード
割り算アプリを、わざと悪い書き方にするとこうなります。
try {
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
int result = a / b;
System.out.println("結果: " + result);
} catch (Exception e) {
System.out.println("エラーが発生しました");
}
Javaユーザーからすると、
「何が悪かったのか分からない」メッセージになります。
数字を間違えたのか
0 を入れたのか
どこか別のバグなのか
全部「エラーが発生しました」で終わってしまう。
2日目の大事な意識
だから今日は、
「例外の種類ごとに catch する」
というスタイルを徹底します。
1日目の割り算アプリを“設計目線”で書き直す
入力処理と計算処理を分ける
まず、1日目のコードを少し整理します。
「入力を読む部分」と
「計算する部分」を
メソッドで分けてみます。
import java.util.Scanner;
public class DivideApp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("1つ目の整数を入力してください: ");
String aStr = scanner.nextLine();
System.out.print("2つ目の整数を入力してください: ");
String bStr = scanner.nextLine();
try {
int result = divideStrings(aStr, bStr);
System.out.println("結果: " + result);
} catch (NumberFormatException e) {
System.out.println("整数として解釈できない入力があります。数字だけを入力してください。");
} catch (ArithmeticException e) {
System.out.println("0 で割ることはできません。2つ目の数には 0 以外を入力してください。");
}
scanner.close();
}
static int divideStrings(String aStr, String bStr) {
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
return a / b;
}
}
Javaここでの重要ポイントは二つです。
divideStrings は「例外を投げる側」になっているmain は「例外を受け止めて、ユーザーにメッセージを返す側」になっている
この分け方が、
「例外処理を設計する」 ということの第一歩です。
「例外を投げる側」と「例外を受け止める側」
divideStrings は“正しい入力前提”で書く
divideStrings の中では、
「入力が正しい」という前提で、素直に書いています。
static int divideStrings(String aStr, String bStr) {
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
return a / b;
}
Javaここで例外が起きたら、
このメソッドの外に「そのまま飛んでいく」イメージです。
つまり、
数字じゃない → NumberFormatException が飛ぶ
0 で割る → ArithmeticException が飛ぶ
「飛んでいく先」が、main の try / catch です。
main は“ユーザーとの窓口”として例外を扱う
main の役割は、
「例外をユーザー向けのメッセージに変換する」ことです。
try {
int result = divideStrings(aStr, bStr);
System.out.println("結果: " + result);
} catch (NumberFormatException e) {
System.out.println("整数として解釈できない入力があります。数字だけを入力してください。");
} catch (ArithmeticException e) {
System.out.println("0 で割ることはできません。2つ目の数には 0 以外を入力してください。");
}
Javaここでのポイントは、
例外の種類ごとに、ユーザーへの説明を変えている
例外の“技術的な名前”ではなく、“意味”を伝えている
ということです。
異常系対策を“パターン”として考える
正常系と異常系を、頭の中で分けてみる
割り算アプリの流れを、
「正常系」と「異常系」に分けてみます。
正常系
入力がどちらも整数
2つ目が 0 ではない
→ 計算して結果を表示して終わり
異常系
どちらかが整数に変換できない
→ NumberFormatException → 「数字だけ入れて」メッセージ
2つ目が 0
→ ArithmeticException → 「0 で割れない」メッセージ
この「分けて考える」こと自体が、
異常系対策の本質です。
例外処理の基本パターン
2日目で押さえてほしいパターンは、これです。
失敗しそうな処理を、ひとつのメソッドにまとめる
そのメソッドは「正しい入力前提」で素直に書く
呼び出し側で try / catch を書いて、例外の種類ごとに対応を決める
今回の例でいうと、
divideStrings が「失敗しそうな処理をまとめたメソッド」main が「例外を受け止めて、ユーザーに説明する場所」
という役割分担になっています。
「どこで catch するか」を意識する
例外を“上の層に任せる”という考え方
今のコードでは、divideStrings は例外を catch していません。
「自分では処理せず、呼び出し元に任せる」
という設計になっています。
これはとても大事な考え方です。
例えば、
ファイルを読むメソッドを作るときも、
中で例外を全部握りつぶすのではなく
「読めなかった」という例外を呼び出し元に投げて
呼び出し元で「どうするか」を決めてもらう
という設計がよく使われます。
悪い例:全部その場で握りつぶす
逆に、こういう書き方はあまり良くありません。
static int divideStrings(String aStr, String bStr) {
try {
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
return a / b;
} catch (Exception e) {
System.out.println("エラーです");
return 0;
}
}
Javaこれだと、
何が悪かったのか分からない
呼び出し元は「0」という結果しか受け取れない
「本当は失敗しているのに、成功したように見える」
という危険な状態になります。
2日目では、
「例外は、必要なら上に伝える」
という感覚を持っておいてください。
2日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「try / catch は“とりあえず囲む文法”ではなく、“どこで何をどう扱うかを決める設計の道具”だ」
ということです。
例外には意味のある種類があるcatch (Exception) で全部まとめると、その意味が消える
「例外を投げる側」と「例外を受け止める側」を分けて設計できる
異常系対策とは、「失敗しそうなところを先に想像して、種類ごとに対応を決めること」
割り算アプリの中で、
divideStrings は「失敗しそうな処理をまとめたメソッド」main は「例外をユーザー向けのメッセージに変換する場所」
という役割分担ができました。
このパターンは、
ファイル読み込みアプリでも
ネットワーク通信アプリでも
ToDo アプリでも、
そのまま使えます。
次のステップでは、
「自分で例外を投げる」「例外を呼び出し元に伝える」側も含めて、
もう少し深く例外処理を設計していきます。
