はしがき
JavaというとEclipseを使っていて、データ通信を行うメソッドの呼び出し時等、例外発生の場合の処理が出来ていないとファイルに❌マークがついて怒られる。そしてそれの処理にとりあえずtry-catchを入れる。
ここまではしばしば見られる光景かと思います。
Javaにはメソッド名の後に、「throws ExeptionName」という記載の仕方で、例外をメソッドの呼び出し元に委ねる回避手段があります。
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ThrowsExample {
// throws: FileNotFoundExceptionが発生する可能性があることを宣言
public static void readFileData(String filePath) throws FileNotFoundException {
// 例外処理は書かず、問題があれば呼び出し元に「投げる」
File file = new File(filePath);
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}
// ここではFileNotFoundExceptionは処理されない
}
public static void main(String[] args) {
String path = "non_existent_file.txt";
// throws宣言されたメソッドを呼び出すため、try-catchが必須になる
try {
System.out.println("--- throwsで例外を処理 ---");
readFileData(path);
} catch (FileNotFoundException e) {
// 例外処理の責任はmainメソッドが負う
System.err.println("エラー: ファイルが見つかりませんでした: " + path);
// 実際はここでログ出力などを行う
}
}
}
「どこまでthrowsで、例外発生時の処理を後回しにして、どこでcatchするか?」というのが今回のお題です。
教科書通りなら責任分離だけど、血の通った例えを一句詠うなら
よく言われるのは「責任分離」の一言になってしまいますが、もう少しアナログに書いてみましょう。
それは、 「例外をキャッチした場面によって、投げたエラーの重要度や処理を変えたいユーティリティはthrowsにしておく」が具体的な一手です。
例えば、熱源の燃費を計算するシステムを開発していたとして、運用中に燃料の増減が合わなかった時に燃料漏れとして例外を検知するとします。
このユーティリティが、例えばスパや宿泊施設で運用されている基幹システムで使用されていて、
調理場での熱源消費量から計算を行うサービスクラスと、大浴場のお湯を沸かしているサービスクラスに実装されていたとします。
例外発生時に生の火🔥が人の前でリアルタイムに使われている恐れがある事もあり、例外をキャッチしたサービスクラスによってハンドリングを必然的に変えなければなりません
(ガス漏れしてる場所をピンポイントに、なんとかしないと、人命に関わりますし💀一律に巨大施設の人間を全員その敷地から避難させるような雑なハンドリングをすれぱパニックなわけです。
遊園地の5つ位エリアがある一角のレストランでガスが少し漏れた位で、数km離れている全エリアの人間を避難させるわけにはいきませんよね。)
補足
今回のケースが異常系(例外)な理由
例外は又の名を実行時エラーと呼ぶものですから、健全に動いていた処理を何らかの理由で止めたりする訳ですが、今回のケースだと燃費を計算しています。
ガスが漏れ続けている状態で普段とは違うユースケースの記録を、燃費記録として例えばAIに自動でディープラーニングさせていたら精度が壊れてしまいますから、もしそうであればシステムはエラー扱いで止めるのが賢明な選択の一つでしょう。
そのシステムのメインの機能が「検知自体」が目的であれば、正常系で構わないかもしれません。
ただ、基幹システムや他Iotのような“検知とは別の目的がメイン”で使われているシステムで起きるエラーは、こういった異常系として文脈上適する一例として、トピックとして挙げたわけです。
※実際の仕様はニーズにより異なります。
コードにするなら
// 燃料漏れ例外クラス
public class FuelLeakException extends Exception {
private final LeakSeverity severity;
private final String location;
public FuelLeakException(String message, LeakSeverity severity, String location) {
super(message);
this.severity = severity;
this.location = location;
}
public LeakSeverity getSeverity() {
return severity;
}
public String getLocation() {
return location;
}
}
// 漏れの深刻度を表す列挙型
public enum LeakSeverity {
MINOR, // 軽微
MODERATE, // 中程度
SEVERE, // 重大
CRITICAL // 致命的
}
// 燃費計算ユーティリティクラス - 例外をスローするだけで処理はしない
public class FuelEfficiencyCalculator {
public static double calculateEfficiency(double fuelInput, double energyOutput) throws FuelLeakException {
double expectedConsumption = energyOutput * 0.85; // 理想的な効率係数
// 燃料の消費量が期待値と大きく異なる場合、漏れの可能性がある
if (fuelInput > expectedConsumption * 1.2) {
double leakAmount = fuelInput - expectedConsumption;
LeakSeverity severity = determineSeverity(leakAmount);
throw new FuelLeakException(
String.format("燃料漏れの可能性があります。過剰消費: %.2fL", leakAmount),
severity,
"不明" // 位置情報はこの時点では特定できない
);
}
return energyOutput / fuelInput;
}
private static LeakSeverity determineSeverity(double leakAmount) {
if (leakAmount < 5) return LeakSeverity.MINOR;
if (leakAmount < 15) return LeakSeverity.MODERATE;
if (leakAmount < 30) return LeakSeverity.SEVERE;
return LeakSeverity.CRITICAL;
}
}
// 調理場熱源管理サービス
public class KitchenHeatingService {
private static final String LOCATION = "中央調理場";
private EmergencyNotifier emergencyNotifier;
private KitchenEvacuationSystem evacuationSystem;
// コンストラクタ、他のメソッド等は省略
public void monitorKitchenHeating(double fuelInput, double energyOutput) {
try {
double efficiency = FuelEfficiencyCalculator.calculateEfficiency(fuelInput, energyOutput);
System.out.println("調理場の熱効率: " + efficiency);
} catch (FuelLeakException e) {
// 調理場での燃料漏れ - 人がすぐ近くにいる可能性が高い
System.err.println("警告: 調理場で" + e.getMessage());
// 調理場固有の対応
switch (e.getSeverity()) {
case MINOR:
// 軽微な漏れ - スタッフに通知して修理を手配
emergencyNotifier.notifyMaintenance(LOCATION + ": 軽微な燃料漏れ");
break;
case MODERATE:
// 中程度の漏れ - 調理場のスタッフに避難指示
emergencyNotifier.notifyStaff(LOCATION + ": 燃料漏れ警報");
evacuationSystem.evacuateKitchenStaff();
break;
case SEVERE:
case CRITICAL:
// 重大または致命的な漏れ - レストランエリア全体を避難
emergencyNotifier.triggerAlarm(LOCATION);
evacuationSystem.evacuateRestaurantArea();
emergencyNotifier.callFireDepartment(LOCATION + "での重大なガス漏れ");
break;
}
}
}
}
// 大浴場加熱システム管理サービス
public class BathHeatingService {
private static final String LOCATION = "大浴場ボイラー室";
private EmergencyNotifier emergencyNotifier;
private BathEvacuationSystem evacuationSystem;
// コンストラクタ、他のメソッド等は省略
public void monitorBathHeating(double fuelInput, double energyOutput) {
try {
double efficiency = FuelEfficiencyCalculator.calculateEfficiency(fuelInput, energyOutput);
System.out.println("浴場ボイラーの熱効率: " + efficiency);
} catch (FuelLeakException e) {
// 大浴場ボイラー室での燃料漏れ - 通常はスタッフのみが立ち入る場所
System.err.println("警告: ボイラー室で" + e.getMessage());
// ボイラー室固有の対応
switch (e.getSeverity()) {
case MINOR:
// 軽微な漏れ - 監視を続行
emergencyNotifier.notifyMaintenance(LOCATION + ": 軽微な燃料漏れ");
break;
case MODERATE:
// 中程度の漏れ - ボイラー室を一時封鎖
emergencyNotifier.notifyStaff(LOCATION + ": 燃料漏れ警報");
evacuationSystem.lockdownBoilerRoom();
break;
case SEVERE:
// 重大な漏れ - 大浴場のお客様を避難
emergencyNotifier.triggerAlarm(LOCATION);
evacuationSystem.evacuateBathArea();
break;
case CRITICAL:
// 致命的な漏れ - 施設の一部を避難
emergencyNotifier.triggerAlarm(LOCATION);
evacuationSystem.evacuateBathAndSurroundingAreas();
emergencyNotifier.callFireDepartment(LOCATION + "での致命的なガス漏れ");
break;
}
}
}
}
// 通知システムインターフェース
interface EmergencyNotifier {
void notifyMaintenance(String message);
void notifyStaff(String message);
void triggerAlarm(String location);
void callFireDepartment(String message);
}
// 避難システムインターフェース(実装は省略)
interface KitchenEvacuationSystem {
void evacuateKitchenStaff();
void evacuateRestaurantArea();
}
interface BathEvacuationSystem {
void lockdownBoilerRoom();
void evacuateBathArea();
void evacuateBathAndSurroundingAreas();
}
共通の計算ユーティリティ(FuelEfficiencyCalculator)が例外をスローするだけで処理はせず、それぞれのサービスクラス(KitchenHeatingServiceとBathHeatingService)がコンテキストに応じた異なる例外処理を行っています。
これにより、ガス漏れ発生場所と漏れの深刻度に応じた適切な対応ができる柔軟なシステムが出来上がるのです。