導入:なぜ例外を「再スロー」するのか
実務開発において、すべての例外を最下層で解決することは不可能です。特定のレイヤーでは「ログを出力する」という責務だけを果たし、残りの処理は上位レイヤーへ委ねる必要があります。このとき、単に例外を握りつぶすのではなく、情報を維持したまま再スロー(re-throw)する作法を知っておくことは、システムの堅牢性を保つために極めて重要です。
基礎知識:例外処理の責務分離
Haskellにおいて、例外のキャッチと再スローは「関心の分離」を体現する仕組みです。具体的には、catch 関数を用いて例外を捕捉し、ログ記録といった副作用を実行した後、throwIO によって例外を再送出します。ここで重要なのは、元の例外情報(スタックトレースや例外の内容)を失わずに上位へ伝播させることです。
実装:再スローの論理的な手順
再スローを行う際の定石は、catch ブロック内で例外オブジェクトを受け取り、副作用(ロギングなど)を実行した後に、再びその例外を投げるという流れです。これにより、呼び出し元はあたかも例外が最初からその場所で発生したかのように処理を継続できます。
サンプルプログラム:安全な再スローの実装例
以下は、ファイル読み込みエラーを検知してログを吐き出し、そのまま上位へ例外を投げる実用的なコード例です。
import Control.Exception
import System.IO
— 再スローを行うための関数
readConfig :: FilePath -> IO String
readConfig path = readFile path `catch` handleErr
where
— 例外を捕捉してログを記録し、再スローするハンドラ
handleErr :: IOException -> IO String
handleErr e = do
— ここでログ出力やメトリクス送信などを行う
putStrLn $ “エラー発生: ファイル読み込みに失敗しました – ” ++ show e
— throwIO で元の例外を再スローし、呼び出し元に責任を委ねる
throwIO e
main :: IO ()
main = do
— 存在しないファイルを指定して例外を発生させる
content <- readConfig "config.txt"
putStrLn content
応用・注意点:現場で陥りやすい罠
実務で注意すべき点は、「例外の握りつぶし」です。catch ブロック内で何もせず(あるいは適当な値を返して)終了してしまうと、上位レイヤーはエラーが発生したことに気づけず、不正な状態でプログラムが続行されてしまいます。
また、再スローを行う際は、「例外の型」にも注意してください。catch で捕捉する型を広げすぎると、本来プログラムを停止させるべき致命的なエラーまで捕捉してしまう可能性があります。基本的には、ハンドリング可能な特定の例外型(IOExceptionなど)に絞って捕捉し、それ以外は上位へ透過させる設計を心がけましょう。これにより、予期せぬバグの温床を未然に防ぐことができます。

コメント