1. 導入:なぜ「例外を投げない」ことが重要なのか
皆さんは、プログラムを書いているときに「突然の例外(Exception)」に悩まされたことはありませんか?予期せぬ場所で例外が発生すると、システムはクラッシュし、原因究明も困難になります。特に、ビジネスロジックの中で例外を投げると、呼び出し元がそれを捕捉(catch)できているか確認し続ける必要があり、コードが非常に複雑になります。
「NoExceptionモナド」の考え方は、「そもそも例外を投げない設計にする」というアプローチです。これを導入することで、システムをより予測可能にし、クラッシュ率を劇的に下げることができます。
2. 基礎知識:Either型と全域関数
この考え方の鍵となるのが「Either型」です。Eitherは、「成功(Right)」か「失敗(Left)」のどちらか一方を保持する箱のようなものです。
また、「全域関数(Total Function)」という言葉も重要です。これは、どんな入力に対しても必ず何らかの値を返す関数のことです。例外を投げる関数は、特定の入力で「値を返さずに異常終了」するため、部分関数と呼ばれます。私たちは、全ての入力に対して結果を返す全域関数を目指すことで、安全なシステムを構築します。
3. 実装の考え方:副作用を外に追い出す
NoExceptionモナドのレイヤーでは、IO(ファイル読み込みやDBアクセスなどの副作用)を直接行いません。代わりに、純粋な計算のみを行い、結果をEither型で包んで返します。これにより、ビジネスロジック層が「例外で止まることのない、テストしやすい純粋な領域」になります。
4. サンプルプログラム
以下のコードは、数値を割るというシンプルな処理を、例外を投げずにEither型を使って安全に実装する例です。
// 成功を表すRightと、失敗を表すLeftを想定したEither型の簡易的なイメージです
// 実際には言語のライブラリ(ScalaのEitherやHaskellのEither等)を使用します
// 例外を投げず、安全に除算を行う全域関数
function safeDivide(numerator, denominator) {
if (denominator === 0) {
// 0で割るというエラーを「例外」ではなく「データ(失敗)」として返す
return { type: 'Left', error: 'ゼロ除算はできません' };
} else {
// 成功した場合は結果をRightで包む
return { type: 'Right', value: numerator / denominator };
}
}
// 実行例
const result = safeDivide(10, 0);
if (result.type === 'Left') {
// 呼び出し元は必ずエラー処理を強制されるため、予期せぬクラッシュが起きない
console.log('エラーが発生しました:', result.error);
} else {
console.log('計算結果:', result.value);
}
5. 応用・注意点:現場での運用
NoExceptionモナドを導入する際の最大のポイントは、「どこまでをNoExceptionにするか」の境界線を決めることです。
・ビジネスロジック層:ここは徹底的に例外を排除します。すべてをEitherの結果として扱います。
・境界層(Adapter/Controller):外部システム(DBやAPI)と通信する場所では例外が発生し得ます。ここで例外をキャッチし、Either型に変換してビジネスロジックへ渡すのがコツです。
このパターンを導入すると、最初は「いちいちEitherを確認するのが面倒」と感じるかもしれません。しかし、それは「今まで見えていなかったエラーを、コードが明示的に指摘してくれている」という証拠です。この「型」の力を信じて、安全なコードを書いていきましょう!

コメント