閉包(クロージャ)設計のレビュー:関数が副作用を持つ構造への指摘法
はじめに
Pythonでは、関数が他の関数を内包し、その内部から外側の変数にアクセスするクロージャ(closure)の利用が日常的に行われます。
この構造自体は柔軟で強力ですが、「副作用を持つクロージャ」が暗黙に設計されているコードは、レビューにおいて注意深く扱う必要があります。
本記事では、関数が外部状態を操作・保持・更新するような構造を持つ場合に、レビューアーとしてどこを読み解き、どこを指摘すべきかを整理していきます。
まず典型的な「問題のある」クロージャ構造を観察する
外部状態を抱え込んだクロージャ
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter())
print(counter())この構造は一見、関数型のように見えますが、実際はcountというミューテーション可能なスコープ変数を内包しており、副作用のある関数です。
レビュー指摘例
@Reviewer: `nonlocal`を使用した変数保持は、状態の可視性が低く、テストやスレッド安全性に難があります。
意図的な状態管理であるなら、構造を分離して明示的に管理対象を表現した方が安全です。クロージャとは
クロージャとは、ある関数(外関数)が定義したローカル変数を、
その関数のスコープ外でも保持し続ける内関数の構造を指します。
Pythonでは、関数を返す関数の中でnonlocalやglobalが登場するとき、
設計上の副作用が潜んでいるケースが少なくありません。
副作用とは何か?レビュー時の視点整理
副作用とは、ある関数の実行が外部の状態を変更することです。
具体的には以下が含まれます。
- 関数外部の変数を書き換える
- ファイルやネットワーク、DBにアクセスする
- グローバル状態を更新する
- ログ出力・printなどの出力操作を行う
副作用チェック観点
@Reviewer: この関数が外部の変数を更新する設計であれば、副作用が存在します。
設計意図が明確でないまま副作用を含むと、再利用やテストが困難になります。設計分離の観点:関数に「状態」と「振る舞い」を持たせる是非
Pythonでは「オブジェクトにするほどではない小さな状態」を関数に持たせたくなることが多々あります。
しかし、その設計が再利用性・可読性・状態管理の明瞭さを犠牲にしていないか、レビューで確認する必要があります。
状態保持を関数に埋め込む例
def greeting_factory():
greeted = set()
def greet(name):
if name not in greeted:
print(f"Hello, {name}!")
greeted.add(name)
return greetレビューコメント
@Reviewer: `greeted`のような副次状態を関数内に抱える設計は、意図が明示されない限り、可読性と責務の分離において疑問が残ります。
単純な関数ではなく、構造体もしくはクラスへの分離を検討すべきです。オブジェクト指向での代替案
クラスによる明示的な状態管理
class Greeter:
def __init__(self):
self.greeted = set()
def greet(self, name):
if name not in self.greeted:
print(f"Hello, {name}!")
self.greeted.add(name)比較レビューコメント
@Reviewer: クロージャを使用した例より、クラスによる責務分離の方が状態の意図や再利用性に優れています。
特に拡張やユニットテストを意識する場合、オブジェクト指向設計は有力な選択肢です。「副作用を持つ関数」の構造
副作用を持つクロージャのレビュー観点
- 関数がどのスコープの変数に依存しているか(
nonlocal,globalを確認) - 副作用の対象は外部環境か内部状態か(出力、書き込み、更新)
- 関数単体でテスト可能か(ステートフルな動作かどうか)
- オブジェクト化によって可視化・分離できる設計ではないか
「pure function」との対比:副作用のない関数の良さ
副作用のない関数(pure function)
def double(x):
return x * 2このような関数は:
- 状態に依存せず
- 呼び出し結果が常に同じ
- テストが容易
- デバッグがしやすい
pure function評価
@Reviewer: 関数がpureであれば、レビュー負荷もバグトレースも大幅に軽減します。
設計段階でpureに近づける意識を持つことが、結果的に品質向上につながります。結論:レビューアーが果たすべき設計意図の読み解き
クロージャは「関数が第一級市民である」Pythonならではの表現手法ですが、
その自由度ゆえに副作用が隠れてしまう構造になりやすいのが問題です。
レビューアーの役割は、以下を見極めることにあります:
- 状態を抱える意図が設計として明確か
- その状態は関数が持つべきものか、構造化すべきか
- 副作用のある実装が意図的か偶発的か
- pure function にできる余地はないか
副作用は設計ミスではありませんが、意図が曖昧なまま副作用を許容することは、
レビューアーとして見逃してはならない構造上の問題です。
