クラス設計レビュー:__init__の肥大化をどう読み解くか
はじめに
Pythonにおけるクラス設計レビューで頻出するのが、__init__メソッドの肥大化である。
この問題は単なる行数の多さだけでなく、次のような設計的な兆候を含んでいる:
- テストが困難(依存が多い)
- インスタンス生成時に副作用がある
- 初期化と設定処理が混在
- 責務が明示されていない
レビューアーは__init__の肥大化を、「単なる初期化の問題」ではなく、「構造と責任の不明確さ」として捉え、指摘すべきである。
典型例:設定も処理も詰め込んだ__init__
initが肥大化したクラス例
class EmailClient:
def __init__(self, user_config):
self.smtp_server = user_config.get("smtp_server")
self.smtp_port = user_config.get("smtp_port", 587)
self.sender = user_config.get("email")
self.logger = get_logger("email")
self.connection = smtplib.SMTP(self.smtp_server, self.smtp_port)
self.connection.starttls()
self.connection.login(self.sender, user_config.get("password"))Comment
@Reviewer: `__init__`内でログ設定・通信接続・ログイン処理まで含まれており、初期化の範疇を超えています。設定と副作用(外部接続)は責務分離すべきです。このようなコードは「クラス設計というより“手続きの固まり”」となってしまっており、拡張性も保守性も著しく損なわれる。
責務が混在する構造図
構造的には、本来はsetup系メソッドに分離されるべき責務が、コンストラクタに押し込められていることが分かる。
責務ごとの分離パターン(構造改善案)
改善案:副作用は明示的に分離
class EmailClient:
def __init__(self, smtp_server, smtp_port, sender, logger=None):
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.sender = sender
self.logger = logger or get_logger("email")
self.connection = None
def connect(self, password):
self.connection = smtplib.SMTP(self.smtp_server, self.smtp_port)
self.connection.starttls()
self.connection.login(self.sender, password)このように、インスタンス生成と副作用を伴う処理を明示的に分離することで、次のようなメリットが生まれる:
- テストが容易になる(モック可能)
- 実行タイミングを制御できる
- 引数の意味が明確になる
よくある__init__肥大化の兆候とレビュー観点
| パターン | 問題の兆候 | レビュー観点 |
|---|---|---|
5行以上のget()連続 |
ユーザー設定やDIコンテナをそのまま押し込んでいる | パラメータオブジェクトの導入を検討 |
__init__内でopen()やAPI接続あり |
副作用を伴う初期化 | 外部接続の責務分離を提案 |
| ログ処理・キャッシュ初期化含む | ロジックの準備を全部抱えている | setup()など別メソッドで責任を分離 |
| 引数が7個以上ある | コンストラクタに詰め込みすぎ | @dataclassまたはファクトリ導入を検討 |
会話形式レビュー例:__init__の責務分離に関するやり取り
クラス設計レビューの実例
class ReportGenerator:
def __init__(self, config):
self.config = config
self.template = load_template(config["template_path"])
self.db_conn = create_connection(config["db_url"])
@Reviewer`load_template()`や`create_connection()`は副作用を伴うため、生成後に明示的に実行できるよう責務を分離しませんか?@Developerクラス使用時にテンプレートとDBを必ず初期化したい意図があります。@Reviewerそれでも副作用は関数呼び出しで明示化することで、テスト時のモックや状態制御がしやすくなります。
設計上の迷いがある読者は、これらの検索語で情報を探しており、それに応える構造的・再現可能な指摘基準を提示することがレビューアーの役割となる。
ファクトリメソッドの活用:責務を外に出すアプローチ
レビュー指摘としては、ファクトリメソッドパターンを紹介するのも有効である。
これは、副作用や設定依存の処理をコンストラクタ外に出し、必要な情報を構造化して渡す設計思想である。
ファクトリ関数を使った責務分離
def create_email_client(user_config):
smtp_server = user_config.get("smtp_server")
smtp_port = user_config.get("smtp_port", 587)
sender = user_config.get("email")
return EmailClient(smtp_server, smtp_port, sender)このように複雑な設定処理や前処理をコンストラクタから分離することで、テスト可能性と責務の透明性が高まる。
まとめ:レビューアーとしての最重要観点
__init__の肥大化は、見た目よりも構造的な問題として捉えるべきである。
レビューアーがチェックすべき視点は以下である:
- コンストラクタに副作用を含めていないか
- 初期化以上の責務が埋め込まれていないか
- 設定とロジックが混在していないか
- 引数が多すぎてクラス間の依存が強くなっていないか
設計レビューでは、コードそのものよりも構造と役割の分離に注目し、将来の保守性とテスト容易性を支える判断を下していきたい。
