引数が異様に多い[*1]関数があったとして、それが様々な理由から仕方のない流れだったとした場合。
def fnc(arg1, arg2, arg3, arg4, arg5, arg6, arg7...):
pass
愚直に引数を増やしていくとメンテナンスがツライことになるので、この処理自体を切り出したいと考えるわけだが、そんな時、次の2つの実装パターンを思いつくわけだけど。
「引数自体を構造体orクラスにする」は大体以下のようなイメージ。
class XxxPrams(object):
def __init__(self, arg1, arg2, arg3, arg4, arg5, arg6, arg7...):
self.arg1 = arg1
self.arg2 = arg2
:
params = XxxPrams(arg1, arg2, arg3, arg4, arg5, arg6, arg7...)
def fnc(params):
#なんかの処理
Pythonの場合は名前付きパラメータも使えるが、それは一旦置いておく。
この場合、引数の必須チェックや整合性チェックをXxxPramsの責務とできるのでビジネスロジック側の処理が簡潔になる。
ただ、XxxPramsがビジネスロジックを実行するモジュールと分離されることで、保守性が低下する[*2]。
次に、「関数自体をクラス化して、Builderパターンでパラメータをセットする」場合だと以下のようになる。
class XxxLogic(object):
def __init__(self):
pass
def arg1(self, arg1):
self.arg1 = arg1
return self
def arg2(self, arg2):
self.arg2 = arg2
return self
:
:
#以下argNまである
def execute():
#引数のチェックをしたりビジネスロジックを実行したり...
#利用時
logic = XxxLogic()
logic.arg1(arg1)
.arg2(arg2)
:
.execute()
こちらはビジネスロジックを実行するクラス自体が必要とするパラメータを明示的に表現できている点で「引数自体を構造体orクラスにする」案より優れているような気がする。
難点としては、パラメータのチェック処理やら何やらで本来のビジネスロジックとはそこまで関係のない処理が増えるので、処理内容の見通しが悪くなる点だろうか。
ここまで書いておいてアレだが、別にメソッドオブジェクト+普通のコンストラクタでいいんじゃないかという気もしなくもない[*3]。
ともあれ、引数の多い関数/メソッドはレイヤー境界のファサードになってる事が多いので、引数が多くなるのは仕方がない場合が多い。
なので、引数の多さからくる複雑性をどこまで簡潔に表現するかが重要になってくるのではなかろうか[*4]。