エラー処理の改善：Chain of Responsibilityパターンで例外管理を簡潔に

## はじめに
ソフトウェア開発で重要なエラー処理は、コードが複雑化しやすく、ビジネスロジックを理解しづらくすることがあります。この記事では、Chain of Responsibilityパターンを使い、エラー処理を改善してコードの可読性と保守性を向上させる方法を紹介します。

## Chain of Responsibilityパターンとは

Chain of Responsibilityは、リクエストを送信者と受信者から分離し、複数のハンドラーが順に処理するデザインパターンです。

以下の図は、このパターンを使用したエラー処理の流れを視覚化したものです：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b2043327-2d83-88cd-6c65-f451dbe5eacb.png)

この図では、エラーが発生してから、複数のエラーハンドラーを経由して処理される様子を示しています。各ハンドラーは自身が担当するエラーを処理し、処理できない場合は次のハンドラーにエラーを渡します。

## なぜChain of Responsibilityパターンを使うのか

このパターンの主なメリットは以下の通りです：

責任の分離: 各ハンドラーが特定のエラーのみを処理。
柔軟性: エラー処理の順序や条件を動的に変更可能。
拡張性: 新しいハンドラーを追加するだけで対応可能。
可読性向上: エラー処理がビジネスロジックと分離されるため、コードが読みやすくなる。

## ユースケース

Chain of Responsibilityパターンが有効なシナリオ：

複雑なエラー処理: 金融や医療システムで多様なエラーを処理する場合。
ログ管理: 異なるログレベルをハンドラーで処理する場合。
認証・認可: 認証や権限チェックを段階的に行う場合。
イベント処理: 異なるイベントを適切に処理する場合。


## 実装例

以下は、Pythonを使用してChain of Responsibilityパターンを実装したエラー処理の例です。

```plantuml
@startuml
skinparam defaultFontName "源ノ角ゴシック"
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #fef3c7
skinparam noteBorderColor #d97706
skinparam classFontSize 14
skinparam noteFontSize 12

abstract class ErrorHandler {
  - next_handler: ErrorHandler
  + __init__(next_handler: ErrorHandler)
  + handle(error: Exception): bool
  # {abstract} handle_error(error: Exception): bool
}

class DatabaseErrorHandler {
  + handle_error(error: Exception): bool
}

class NetworkErrorHandler {
  + handle_error(error: Exception): bool
}

class GenericErrorHandler {
  + handle_error(error: Exception): bool
}

class Exception {
  + message: str
}

class DatabaseError {
  + message: str
}

class NetworkError {
  + message: str
}

ErrorHandler <|-- DatabaseErrorHandler : 継承
ErrorHandler <|-- NetworkErrorHandler : 継承
ErrorHandler <|-- GenericErrorHandler : 継承
Exception <|-- DatabaseError : 継承
Exception <|-- NetworkError : 継承
ErrorHandler o-- ErrorHandler : 次のハンドラー >

note right of ErrorHandler
  抽象基底クラス
  - handle()でチェーン処理を制御
  - handle_error()は具象クラスで実装
end note

note right of DatabaseErrorHandler
  データベースエラーハンドラー
  - DatabaseErrorの場合に処理
  - それ以外はFalseを返す
end note

note right of NetworkErrorHandler
  ネットワークエラーハンドラー
  - NetworkErrorの場合に処理
  - それ以外はFalseを返す
end note

note right of GenericErrorHandler
  汎用エラーハンドラー
  - 他のハンドラーで
    処理されなかったエラーを処理
end note

note bottom of ErrorHandler
  handle()の処理フロー
  1. handle_error()を呼び出し
  2. 処理成功ならTrue
  3. 失敗かつ次のハンドラーがあれば委譲
  4. どちらもない場合はFalse
end note

@enduml
```

```plantuml
@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam maxmessagesize 60

actor Client
participant "ErrorHandler\nChain" as Chain
participant "DatabaseErrorHandler" as DBHandler
participant "NetworkErrorHandler" as NetHandler
participant "GenericErrorHandler" as GenHandler

Client -> Chain: handle(error)
activate Chain

Chain -> DBHandler: handle_error(error)
activate DBHandler
alt エラーがDatabaseError
    DBHandler --> Chain: 処理完了(True)
    Chain --> Client: エラー処理完了
else エラーがDatabaseErrorでない
    DBHandler --> Chain: 未処理(False)
    Chain -> NetHandler: handle_error(error)
    activate NetHandler
    alt エラーがNetworkError
        NetHandler --> Chain: 処理完了(True)
        Chain --> Client: エラー処理完了
    else エラーがNetworkErrorでない
        NetHandler --> Chain: 未処理(False)
        Chain -> GenHandler: handle_error(error)
        activate GenHandler
        GenHandler --> Chain: 処理完了(True)
        deactivate GenHandler
        Chain --> Client: エラー処理完了
    end
    deactivate NetHandler
end
deactivate DBHandler

deactivate Chain

@enduml
```


```python
from abc import ABC, abstractmethod

class ErrorHandler(ABC):
    def __init__(self, next_handler=None):
        self.next_handler = next_handler

    @abstractmethod
    def handle_error(self, error):
        pass

    def handle(self, error):
        if self.handle_error(error):
            return True
        elif self.next_handler:
            return self.next_handler.handle(error)
        return False

class DatabaseErrorHandler(ErrorHandler):
    def handle_error(self, error):
        if isinstance(error, DatabaseError):
            print(f"データベースエラーを処理: {error}")
            return True
        return False

class NetworkErrorHandler(ErrorHandler):
    def handle_error(self, error):
        if isinstance(error, NetworkError):
            print(f"ネットワークエラーを処理: {error}")
            return True
        return False

class GenericErrorHandler(ErrorHandler):
    def handle_error(self, error):
        print(f"一般的なエラーを処理: {error}")
        return True

# エラークラスの定義
class DatabaseError(Exception):
    pass

class NetworkError(Exception):
    pass

# エラーハンドラーチェーンの作成
error_handler_chain = GenericErrorHandler(
    NetworkErrorHandler(
        DatabaseErrorHandler()
    )
)

# エラー処理の使用例
def test_error_handling():
    errors = [
        DatabaseError("データベース接続エラー"),
        NetworkError("ネットワークタイムアウト"),
        ValueError("不正な値"),
    ]

    for error in errors:
        print(f"\n処理するエラー: {error}")
        error_handler_chain.handle(error)

if __name__ == "__main__":
    test_error_handling()
```

このコードを実行すると、以下のような出力が得られます：

```
処理するエラー: データベース接続エラー
データベースエラーを処理: データベース接続エラー

処理するエラー: ネットワークタイムアウト
ネットワークエラーを処理: ネットワークタイムアウト

処理するエラー: 不正な値
一般的なエラーを処理: 不正な値
```

## 実装のポイント

1. **抽象基底クラス**: `ErrorHandler`クラスを抽象基底クラスとして定義し、共通のインターフェースを提供します。
2. **具体的なハンドラー**: 各エラータイプに対応する具体的なハンドラークラスを実装します。
3. **チェーンの構築**: ハンドラーオブジェクトを連鎖させて、処理の順序を定義します。
4. **エラーの委譲**: 各ハンドラーは、自身で処理できないエラーを次のハンドラーに委譲します。

## 注意点

1. **パフォーマンス**: 長いチェーンの場合、全てのハンドラーを通過するまでに時間がかかる可能性があります。
2. **デバッグの複雑さ**: エラーがチェーンのどこで処理されたかを追跡するのが難しくなる場合があります。
3. **適切な使用**: 小規模なプロジェクトや単純なエラー処理では、このパターンは過剰な設計になる可能性があります。

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9fa9a1aa-1962-33e6-d0c2-13e714cecbae.png)


Chain of Responsibilityパターンは、リクエストを複数のハンドラーが順に処理し、送信者と受信者を分離するデザインパターンです。これにより、エラー処理の責任が分散され、柔軟性や拡張性が高まり、コードの可読性と保守性が向上します。特に、複雑なエラー処理や段階的な認証・ログ管理が必要なシステムで効果を発揮します。