Java | 1 日 90 分 × 7 日アプリ学習 初級編:例外処理アプリ

Web APP Java
スポンサーリンク

2日目のゴール

2日目のテーマは
「try / catch を“とりあえず囲む文法”から、“設計の道具”に格上げする」 ことです。

今日はこういうことを狙います。

どの例外を、どこで、どう扱うかを意識する
「全部 catch する」の危険さを知る
異常系を“パターン”として考えられるようにする

1日目の割り算アプリを、もう一段だけ“ちゃんとした例外処理アプリ”に育てていきます。


例外を「種類」として意識する

例外には“意味のある種類”がある

Java の例外は、ざっくり言うと「何がまずかったか」を種類で表しています。

数字にできない文字列 → NumberFormatException
0 で割った → ArithmeticException
存在しないファイルを開いた → FileNotFoundException

1日目では、NumberFormatExceptionArithmeticException を使いましたね。

2日目では、
「例外の種類ごとに、どう振る舞うかを決める」
という視点を強く持ちます。

例外の“親子関係”を軽く知っておく

細かく覚えなくていいですが、
イメージだけ持っておくと役に立つので、少しだけ。

Exception が大きな親
その下に、いろいろな種類の例外がぶら下がっている
NumberFormatExceptionArithmeticException も、その子どもたち

だから、こういう書き方もできます。

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 アプリでも、

そのまま使えます。

次のステップでは、
「自分で例外を投げる」「例外を呼び出し元に伝える」側も含めて、
もう少し深く例外処理を設計していきます。

タイトルとURLをコピーしました