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

Web APP Java
スポンサーリンク

6日目のゴール

6日目のテーマは
「例外処理を“アプリ全体の安全ネット”として設計する」 ことです。

ここまでであなたは、

例外の基本(try / catch)
事前チェックとの役割分担
throws で上の層に任せる設計
独自例外で「アプリの言葉」を持たせる

を一通り体に入れてきました。

6日目では、もう一歩だけ視点を引いて、

どこまで細かく try / catch を書くべきか
どこで“最後の砦”として例外を受け止めるか
ログ(記録)とユーザー向けメッセージをどう分けるか

を、小さな「メニュー付き例外処理アプリ」を通して整理します。


今日の題材:メニュー付き「ファイル or 割り算」アプリ

アプリのイメージ

今日は、これまでの二つをまとめたミニアプリを作ります。

「1: 割り算」「2: ファイル読み込み」「0: 終了」というメニューを表示する
1 を選んだら、割り算アプリ(例外あり)
2 を選んだら、ファイル読み込みアプリ(例外あり)
どちらも失敗する可能性がある
それでもアプリ全体は落とさず、メニューに戻る

ここで意識したいのは、

個々の機能の中で try / catch を書く
それでも漏れた例外を「一番外側」で受け止める

という二重の安全ネットです。


まずは「機能ごとの例外処理」を整理する

割り算機能をメソッドに切り出す

これまでの割り算アプリを、
メニューから呼べるメソッドにします。

import java.util.Scanner;

public class DivideFeature {

    public static void run(Scanner scanner) {
        System.out.println("=== 割り算機能 ===");
        System.out.print("1つ目の整数を入力してください(キャンセルは空 Enter): ");
        String aStr = scanner.nextLine();
        if (aStr.isEmpty()) {
            System.out.println("キャンセルしました。");
            return;
        }

        System.out.print("2つ目の整数を入力してください: ");
        String bStr = scanner.nextLine();
        if (bStr.isEmpty()) {
            System.out.println("キャンセルしました。");
            return;
        }

        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 以外を入力してください。");
        }

        System.out.println("=== 割り算機能終了 ===");
    }

    static int divideStrings(String aStr, String bStr) {
        int a = Integer.parseInt(aStr);
        int b = Integer.parseInt(bStr);
        return a / b;
    }
}
Java

ここでのポイントは、割り算機能の中で
「想定できる異常系」をすべて try / catch していることです。

数字でない → NumberFormatException
0 で割る → ArithmeticException

このレベルの例外は、
この機能の中で完結して扱うのが自然です。

ファイル読み込み機能もメソッドにする

5日目の FileLoader と FileLoadException を使って、
「ファイル読み込み機能」をメソッドにします。

import java.util.Scanner;

public class FileFeature {

    public static void run(Scanner scanner) {
        System.out.println("=== ファイル読み込み機能 ===");
        System.out.print("読み込みたいファイル名を入力してください(キャンセルは空 Enter): ");
        String fileName = scanner.nextLine();
        if (fileName.isEmpty()) {
            System.out.println("キャンセルしました。");
            return;
        }

        try {
            FileLoader.printFile(fileName);
            System.out.println("=== 読み込み完了 ===");
        } catch (FileLoadException e) {
            System.out.println("ファイルを読み込めませんでした。ファイル名や権限を確認してください。");
            System.out.println("(開発者向け情報)" + e.getMessage());
        }

        System.out.println("=== ファイル読み込み機能終了 ===");
    }
}
Java

ここでも、
「この機能にとって意味のある例外(FileLoadException)」を
この機能の中で完結して扱っています。


アプリ全体の“司会進行”と「最後の砦」

メニューを持つ Main を書く

次に、メニューを持つ Main を書きます。

import java.util.Scanner;

public class ExceptionApp {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        try {
            runApp(scanner);
        } catch (Exception e) {
            System.out.println("予期しないエラーが発生しました。アプリを終了します。");
            System.out.println("(開発者向け情報)" + e.getClass().getName() + ": " + e.getMessage());
        } finally {
            scanner.close();
        }
    }

    private static void runApp(Scanner scanner) {
        while (true) {
            System.out.println();
            System.out.println("=== 例外処理アプリ メニュー ===");
            System.out.println("1: 割り算機能");
            System.out.println("2: ファイル読み込み機能");
            System.out.println("0: 終了");
            System.out.print("番号を選んでください: ");

            String choice = scanner.nextLine();

            if ("0".equals(choice)) {
                System.out.println("終了します。");
                break;
            } else if ("1".equals(choice)) {
                DivideFeature.run(scanner);
            } else if ("2".equals(choice)) {
                FileFeature.run(scanner);
            } else {
                System.out.println("不正な入力です。0, 1, 2 のいずれかを入力してください。");
            }
        }
    }
}
Java

ここでの重要ポイントは二つです。

runApp(scanner) 全体を try { ... } catch (Exception e) で囲んでいる
これは「アプリ全体の最後の砦」としての catch である

つまり、

各機能の中で想定済みの例外は、その機能内で処理する
それでも漏れてきた“予期しない例外”は、ここでまとめて受け止める

という二段構えになっています。


「機能内で処理する例外」と「最後の砦」の違い

機能内の try / catch は“想定済みの失敗”用

割り算機能やファイル機能の中の try / catch は、

ユーザーがよくやりそうなミス
現実的に起こりうる失敗
それに対して、どう案内するか決めている

という、
「想定済みの異常系」 を扱っています。

ここでは、
ユーザーにとって分かりやすいメッセージを出し、
アプリ自体は続ける、という設計です。

最後の砦の catch は“想定外の失敗”用

一方、main の外側にある

try {
    runApp(scanner);
} catch (Exception e) {
    // 予期しないエラー
}
Java

は、
「本来ここまで来てほしくなかった例外」 を扱う場所です。

ここに来るのは、例えばこんなケースです。

自分のバグで NullPointerException が出た
思ってもみなかったライブラリの例外が飛んできた
機能内で catch し忘れた例外が漏れてきた

こういうときに、

アプリ全体が真っ赤なスタックトレースを出して落ちるのではなく
「予期しないエラーが発生しました」と一言伝えて終了する
開発者向けには、例外のクラス名とメッセージを表示する

という“最低限の品位”を保つための場所です。


ログとメッセージの分離を意識する

今はコンソール出力、現場ではログファイル

今回のコードでは、
開発者向け情報も System.out.println で出しています。

現場のアプリでは、ここが「ログ出力」に変わります。

ユーザー向け
→ 画面やコンソールに、短く分かりやすいメッセージ

開発者向け
→ ログファイルに、例外クラス名・メッセージ・スタックトレース

6日目で持っておいてほしい感覚は、

ユーザーに全部を見せる必要はない
でも、開発者が原因を追えるだけの情報は残すべき

というバランスです。

例外オブジェクトから取れる情報

Exception e からは、いろいろな情報が取れます。

e.getClass().getName() で例外のクラス名
e.getMessage() でメッセージ
e.printStackTrace() でスタックトレース(どこで起きたかの履歴)

本番アプリでは、
これらをログに残しておくことで、
「ユーザーから“エラーが出た”と言われたときに、何が起きたか追える」
という状態を作ります。


どこまで try / catch を書くか、という設計の線引き

悪いパターン:全部の行を try / catch で囲む

初心者がやりがちなのが、

とにかくエラーが出た行を try / catch で囲む
何が起きても「エラーです」とだけ表示する

というパターンです。

これは、

コードが try / catch だらけで読みにくくなる
どこで何が失敗しているのか分からなくなる
異常系のストーリーが見えなくなる

という意味で、あまり良くありません。

今日の構造を“理想形のひとつ”として覚えておく

6日目でやった構造は、かなり現場寄りの形です。

機能ごとに「想定済みの例外」を try / catch する
想定外の例外は、アプリ全体の一番外側で catch する
ユーザー向けメッセージと開発者向け情報を分ける

この三つを意識しておけば、
例外処理が「場当たり的な消火活動」ではなく、
「アプリ全体の安全ネット」 として機能し始めます。


6日目で絶対に押さえてほしい本質

今日いちばん大事なのは、
「例外処理には“レベル”がある。機能レベルとアプリ全体レベルを意識して設計する」
という感覚です。

機能の中では、ユーザーがよくやるミスや現実的な失敗を想定して、丁寧に案内する
それでも漏れてくる予期しない例外は、一番外側でまとめて受け止め、アプリをきれいに終わらせる
ユーザーには短く分かりやすく、開発者には詳しく、という二重構造を意識する

ここまで来たあなたは、
もう「try / catch を知っている人」ではなく、
「例外処理をアプリの設計の一部として考えられる人」 です。

7日目では、この感覚を言葉にして整理し直して、
「自分はこういう方針で例外処理を書く」という軸を固めていきましょう。

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