1. 導入:なぜエラー処理の分離が重要なのか
実務のコードで最も読みづらくなる原因の一つが、ビジネスロジックの間に混入する「if err != nil」や「try-catch」の嵐です。これら「異常系」の処理がメインロジックに散らばると、コードの意図が隠れてしまい、保守性が著しく低下します。本記事では、関数型プログラミングの知見を活かし、エラー処理をビジネスロジックから分離し、成功パス(Happy Path)を宣言的に記述する手法を解説します。
2. 基礎知識:モナドによるエラーの抽象化
関数型言語では、エラー処理を「失敗する可能性のある計算」として抽象化します。例えば、Haskellの「Either」型やScalaの「Try/Either」などは、計算の結果が「成功値」か「失敗値」のどちらかであることを表現するコンテナです。これにより、エラー判定を個々の関数で行うのではなく、専用の層(スタックの解釈層)に委譲させることが可能になります。これを「関心の分離」と呼びます。
3. 実装/解決策:Either型を用いたパイプライン構築
エラー処理を分離するための鍵は、ロジックを「モナド」という文脈に乗せることです。do構文(またはflatMap)を使用することで、各ステップでエラーが発生した瞬間に後続の処理をスキップし、一気にエラーハンドリング層へ制御を移すことができます。これにより、開発者は「成功したとき」の処理だけに集中して記述できるようになります。
4. サンプルプログラム:TypeScriptによるEitherパターン
TypeScriptでEither型を模した簡潔な実装例です。外部ライブラリを使わず、エラー処理の概念を掴むための実装です。
// 成功と失敗を表現する型定義
type Either
// 正常系処理
const getUser = (id: number): Either
id > 0 ? { tag: 'right', value: { id, name: 'Tanaka' } } : { tag: 'left', value: 'ユーザーが見つかりません' };
const validate = (user: { id: number, name: string }): Either
user.name ? { tag: 'right', value: user.name } : { tag: 'left', value: '名前が空です' };
// メインロジック(成功パスのみを記述)
const processUser = (id: number) => {
const userResult = getUser(id);
// 疑似的なdo構文:成功時のみ次の処理へ進む
if (userResult.tag === 'left') return userResult;
const validateResult = validate(userResult.value);
return validateResult;
};
// 実行と結果の解釈(エラー処理層)
const result = processUser(1);
if (result.tag === 'right') {
console.log('成功:', result.value);
} else {
// ここでエラー処理を一元管理する
console.error('エラー発生:', result.value);
}
5. 応用・注意点:現場での運用
この手法を採用する際の注意点は、「エラー情報の粒度」です。単純に文字列でエラーを返すのではなく、ドメインごとの「エラー型」を定義し、パターンマッチングを用いて適切に振り分けるようにしてください。また、過度な抽象化は逆にチームメンバーの学習コストを上げる可能性があります。まずは、複雑な条件分岐が重なる箇所から導入し、段階的に「成功パスの可読性」を向上させるアプローチを推奨します。エラー処理をロジックから剥がすことで、あなたのコードは格段にテストしやすく、見通しの良いものに変わるはずです。

コメント