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);
}
}
}
}
Javapublic 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 で割った」という意味を持った例外
を自分で設計してみてください。
その瞬間、
あなたの中で「例外処理」は、
完全に“プロの道具”に変わります。
