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

Web APP Java
スポンサーリンク

5日目のゴール

5日目のテーマは
「例外を“ただ受け止める”から、“意味をつけて投げ直す”レベルに進めること」 です。

ここまでであなたは、

例外の基本(try / catch)
例外の種類ごとの扱い
事前チェックとの役割分担
どの層で例外を握るか(throws と catch)

を体でつかんできました。

5日目では一歩進んで、

技術的な例外を「アプリの言葉」に変換する
例外を“包んで投げ直す(ラップする)”
「ユーザーに見せたい情報」と「内部の詳細」を分ける

という、現場でめちゃくちゃよく使うパターンを身につけます。


今日の題材:ファイル読み込みを“アプリの例外”に変換する

4日目までの形をおさらい

4日目のファイル読み込みは、こんな感じでした。

public class FileLoader {

    public static void printFile(String fileName) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
Java
public class FileApp {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("読み込みたいファイル名を入力してください: ");
        String fileName = scanner.nextLine();

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

        scanner.close();
    }
}
Java

ここでは、

ファイル読み込みの技術的な失敗 → IOException
それをユーザー向けメッセージに変換するのは main

という構造でした。

5日目では、
「IOException をそのまま扱うのではなく、“アプリ独自の例外”に変換する」
という一段上の設計をやってみます。


アプリ独自の例外クラスを作る

「技術用語」ではなく「アプリの言葉」で表現する

IOException という名前は、
開発者には分かりやすいですが、
アプリの世界観としてはちょっと技術寄りです。

例えば、
「このアプリにとっての“ファイル読み込み失敗”」を
ひとつの概念として扱いたくなります。

そこで、独自の例外クラスを作ります。

public class FileLoadException extends Exception {

    public FileLoadException(String message, Throwable cause) {
        super(message, cause);
    }

    public FileLoadException(String message) {
        super(message);
    }
}
Java

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

アプリ専用の名前(FileLoadException)をつけた
元の例外(IOException)を中に“原因(cause)”として持てるようにした

これで、

技術的な例外(IOException)
アプリの意味を持った例外(FileLoadException)

を分けて扱えるようになります。


IOException を“包んで投げ直す”

FileLoader をアプリ例外対応に書き換える

FileLoader を、
IOException をそのまま投げるのではなく、
FileLoadException に包んで投げ直す形にします。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileLoader {

    public static void printFile(String fileName) throws FileLoadException {
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            throw new FileLoadException("ファイルの読み込みに失敗しました: " + fileName, e);
        }
    }
}
Java

ここで起きていることを、丁寧に言葉にします。

BufferedReader や FileReader の中で IOException が起きる
catch (IOException e) で一度受け止める
「このアプリにとって意味のあるメッセージ」をつけて
FileLoadException として投げ直す

つまり、
「技術的な例外を、アプリの言葉を持った例外に翻訳している」
ということです。

なぜ“包んで投げ直す”のか

理由はシンプルで、

上の層(main やアプリ本体)は
「ファイル読み込みに失敗した」という意味だけ分かればいい
細かい技術的な理由(パスが違う、権限がない、など)は
必要なら cause(元の例外)から辿れる

という分離ができるからです。


呼び出し側は“アプリの例外”だけを意識する

FileApp を FileLoadException 対応にする

main 側は、
IOException ではなく FileLoadException を扱うようにします。

import java.util.Scanner;

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

        while (true) {
            System.out.print("読み込みたいファイル名を入力してください(q で終了): ");
            String fileName = scanner.nextLine();
            if ("q".equalsIgnoreCase(fileName)) {
                System.out.println("終了します。");
                break;
            }

            try {
                FileLoader.printFile(fileName);
                System.out.println("=== 読み込み完了 ===");
            } catch (FileLoadException e) {
                System.out.println("ファイルを読み込めませんでした。ファイル名や権限を確認してください。");
                System.out.println("(開発者向け情報)" + e.getMessage());
                // e.getCause() をログに出す、という使い方もできる
            }

            System.out.println();
        }

        scanner.close();
    }
}
Java

ここでの重要ポイントは、

main は IOException を知らなくていい
「このアプリにとっての失敗」を FileLoadException という一種類で扱える
内部の詳細は e.getCause() を見れば分かる

という構造になっていることです。


「技術の例外」と「ドメインの例外」を分けて考える

例外にも“層”がある、という感覚

ここまでの流れを、層で整理するとこうなります。

下の層(Java の標準ライブラリ)
→ IOException, FileNotFoundException など技術的な例外

中間の層(FileLoader のような技術ラッパー)
→ 技術例外を受け取り、アプリ例外に変換する

上の層(アプリ本体・UI)
→ アプリ例外(FileLoadException)だけを意識して振る舞いを決める

この分け方ができると、

UI 層は「ユーザーに何を伝えるか」だけ考えればよくなる
技術層は「どうやってファイルを読むか」だけに集中できる
例外の扱いも、それぞれの層にふさわしいレベルで書ける

という、かなり気持ちいい設計になります。

ToDo アプリに当てはめるとどうなるか

例えば、ToDo アプリで「保存に失敗した」ときも同じです。

下の層:ファイル保存で IOException
中間層:SaveFailedException(アプリ独自の例外)に変換
上の層:SaveFailedException を catch して「保存に失敗しました」と表示

「技術の例外 → アプリの例外 → ユーザーへのメッセージ」
という三段階の流れが、どのアプリでも使えるパターンになります。


例外メッセージを“設計する”という視点

ユーザー向けメッセージと内部メッセージ

FileLoadException を作ったとき、
コンストラクタに message を渡しました。

throw new FileLoadException("ファイルの読み込みに失敗しました: " + fileName, e);
Java

この message は、
「アプリの世界での意味」を表す言葉です。

一方、IOException の e.getMessage() は、
「OS やライブラリが教えてくれる技術的な詳細」です。

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

ユーザーには「何が起きたか」「どうすればよさそうか」を伝える
開発者には「どこで何が壊れたか」の詳細を残す

という二重構造です。

例外クラス自体が“仕様書”になる

FileLoadException というクラス名を見ただけで、

このアプリには「ファイル読み込みに失敗する」というケースがある
それを例外として扱う設計になっている

ということが分かります。

つまり、
「例外クラスそのものが、アプリの異常系仕様を表現している」
と言えます。

これは、現場のコードを読むときにめちゃくちゃ効きます。


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

今日いちばん大事なのは、
「例外は“技術的なエラー”だけじゃなく、“アプリの世界の出来事”として設計できる」
という感覚です。

技術的な例外(IOException など)を
アプリ独自の例外(FileLoadException など)に包んで投げ直す
上の層は「アプリの例外」だけを意識して振る舞いを決める
ユーザー向けメッセージと内部向け情報(cause)を分けて扱う

ここまで来ると、
try / catch はもう「赤いエラーを消すための道具」ではなく、
「アプリの異常系を言葉として設計するための道具」
になっています。

もし余力があれば、
割り算アプリにも InvalidInputException のような独自例外を導入して、

「入力がおかしい」という意味を持った例外
「0 で割った」という意味を持った例外

を自分で設計してみてください。

その瞬間、
あなたの中で「例外処理」は、
完全に“プロの道具”に変わります。

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