副作用のない関数-SIDE-EFFECT-FREE FUNCTIONS (p.250) †
要約 †
副作用のない関数-SIDE-EFFECT-FREE-FUNCTIONS
- 「操作」は、大きく「命令(Command)」と「問い合わせ(Query)」の二種類に分けられる。
- 問い合わせは、システムから情報を取得する。
- 命令(modifierとしても知られる)は、システムに何らかの変更を及ぼす。
- 英語で「SIDE-EFFECT」は「意図しない結果」を意味するが、コンピュータサイエンスでは、システムの状態に関するあらゆる影響を指す。
- ここではさらに「SIDE-EFFECT」の意味を狭め、「将来の操作に影響を及ぼすような、システム状態に関するあらゆる変更」と定義する。
***
問題 †
- 操作が引き起こすきわめて「意図的な変更」に対しても「SIDE-EFFECT」という用語が採用されたのはおそらく、複雑なシステムでの経験から来ている。
- ほとんどの操作は他の操作を呼び出すが、呼び出し階層が深くなると、結果を予測するのが難しくなってしまう。
- クライアントの開発者は、第2層や第3層の操作が引き起こす影響は意図していないかもしれない。これは、あらゆる意味で副作用に相当する。
- 複雑なデザインにおいて、デザインの各要素は予測不可能な結果を引き起こすような方法で相互作用するものだ。
- SIDE-EFFECTという語の使用は、このような相互作用が避けがたいことを明確に示す。
問題サマリ
- 複数のルールや、組み合わさった計算群同士が相互作用する場合、結果を予想するのはきわめて難しくなる。
- 操作を呼び出す開発者が結果を予測するためには、その操作の実装を理解し、かつその実装の委譲先すべての実装を理解する必要がある。
- 開発者がヴェールに穴を開けねばならない(インタフェースの中を覗き見なければならない)なら、インタフェースによる抽象化の利点は限定的になる。
- 安全に結果が予測できるような抽象を使えない場合、開発者は振る舞いのリッチさを制限しなくてはならなくなる。これは、組み合わせの爆発を防ぐためである。
解決 †
- 副作用を発生させずに値を返すような操作をは、「関数(Function)」と呼ばれる。
- 関数は何度も呼ぶことができ、その都度同じ値を返す。
- 関数は、呼び出し階層の深さを気にすることなく他の関数から呼び出すことができる。
- 関数は、副作用を持つ操作に比べてテストが簡単である。
- 上記のことから、関数はリスクを低下させる、と言える。
- もちろん、ほとんどのソフトウェアシステムで「命令」を避けることはできないが、2つの方法で問題を抑制できる。
- 命令と問い合わせを異なる操作として厳密に分離し、副作用を引き起こすメソッドはドメインデータを返さないようにし、可能な限りシンプルにする。
- 命令が必要なケースでは、既存オブジェクトの状態を変更するのではなく、操作の結果として新たなValueObjectを作って返すようにする。
- ValueObjectは不変なので、操作はすべて関数になる。ValueObjectは関数と同様安全に使うことができ、テストも容易。
- 状態変更を伴うような複合的なロジックや計算は、2つの異なる操作に分離する。
- ValueObjectへの責務の移動によって、副作用が完全に消滅することも多い。
解決方法サマリ
Therefore:
- ロジックは、可能な限り(副作用なく戻り値を返すような)関数の中に置く。
- 命令は単純な操作として厳密に分離し、ドメイン情報は返さないようにする。
- 副作用をより厳密にコントロールする場合は、複雑なロジックをValueObjectに移動する。
***
例:Refactoring the Paint-Mixing Application Again †
- 画材店のプログラムは、顧客に標準的な絵の具を混ぜ合わせた結果を提示する。前回のサンプルの結果は、こんな感じ。
- Paint
- volume
- red
- yellow
- blue
- mixIn(Paint)
- mixInメソッドでは色んな事が起きているが、ここでは問い合わせから命令を分離する部分を追う。
- (忘れられているらしい)mixInメソッドに引き渡されるPaintオブジェクトのvolumeはどうなっている?
- この設計では引数のvolumeは変更されないが、概念モデルの文脈上論理的とは言い難い。
- 元々の開発者は、操作後の引数に興味がないので大した問題ではなかったようだが、これでは、結果として副作用が発生するかどうかを予想することは難しい(ASSERTIONSで詳しく議論する)。
- Colorを明示的なオブジェクトとして導入してみる。
- 単なるRGB色の光の合成とは異なることが既に明らかになっているため、"Pigment Color(顔料色)"と名付ける
- が、操作はすべてmixIn内に配置されており、あまり変わっていない。Colorを作ったので、関連する操作(mixIn)もそちらに移す。
- 今までは、mixiInでPaintの状態が変わっていたが、Pigment ColorはValueObjectなので、mixInの結果は新たなPigment Colorとして返される。
- 新たに導入されたPigment Colorは「副作用のない関数」を提供し、その結果は理解しやすく、テストしやすく、安全に使うことができ、他の操作と組み合わせやすいようになっている。
- 色の合成に関する複雑なロジックは完全にカプセル化されており、開発者がこのクラスを使う際には実装を理解する必要もない。
***
担当者のつぶやき †
- 全訳に近づいてしまった、やばい。
- WikiだとすべてBulletで書いてしまう…ふと気づくと他の人とスタイルが全然違う…まずい。
- 問題描写の「SIDE-EFFECT」という用語が採用された理由は、少しわかりづらい気がする。要するに、元々(または個別に)は意図された副作用だったとしても、それらが複雑に組み合わさると結果を予測することは難しいため、結局は意図しない副作用が発生してしまうから、だと思うけどあってるかな。
みんなの突っ込み †
- 「副作用(side effect)」って、コンピュータサイエンスでよく使われる言葉で、オブジェクトの状態変化のことですよね。オブジェクト同士が状態変化を繰り返すことで、計算が進んでいくのが、オブジェクト指向の計算モデル。「副作用」という言葉を選んだのは、要するにそういうオブジェクト指向っぽいことをする場所を明示的に限定しろ、という含意からではないでしょうか。 -- 佐藤?