Scala若干、難しい話ですが、設計力を上げたいプログラマ向けなエントリ。一緒に考えていただければ幸いです。堅牢なアプリケーションを実現する上で「副作用を最小限に抑える」という設計思想は、非常に重要な示唆を含んでいます。その「副作用」とは、そもそもどんな定義なのでしょうか。effect"と定義していて、以下のように解説されています。effectという言葉は"意図的ではない結果"のことを示すが、コンピュータサイエンスでは、システムの状態へのあらゆる影響を意味する。我々の目的においては「将来の操作に影響を及ぼすであろう、システムの状態に関するあらゆる変更」と定義する。Separation:CQS)に見ることができます。CQSでは以下のような原則が示されています。「あらゆるメソッドは、アクションを実行するコマンドか、呼び出し元にデータを返すクエリかのいずれかであって、両方を行ってはならない。これは、質問をすることで回答を変化させてはならないということだ。「質問をすることで回答を変化」するようなことをSQLで考えるならば、SELECTしたらUPDATEされるようなものでしょうか。こんなSQL文は実在しません。実在したら怖くて使えないはずですが。SELECTするとUPDATEも起きるとしたら...もちろん、SQLはこの原則に反しませんが、自分たちで作成するプログラムでは、注意しないと「命令」と「問合せ」の混同が起き、思わぬ「副作用」が発生するかもしれません。その悪影響は計り知れないため、「状態」の操作は細心の注意を払う必要があります。上記で紹介した「副作用」の文脈「将来の操作に影響を及ぼすであろう、システムの状態に関するあらゆる変更」には「状態」が含まれます。そもそも「状態」はどういう目的で導入されたのか、「実装パターン」から知ることができます。P56から引用コンピュータ処理の先駆者たちは、プログラミングのためのメタファーに、時間とともに変化する「状態」という考え方を取り入れました。人間の脳には、この「状態」を扱うための、先天的または後天的な、幅広いさまざまな戦略が備わっている。そもそも、人間が「状態」を扱うメンタルモデルを持っているので、それをプログラミング言語にも取り入れることで、扱いやすくしたという自然な流れです。オブジェクト指向言語でも「状態」のカプセル化という設計手法は常套手段になっています。状態は、プログラマにとって問題にもなる。状態に対してなんらかの前提を抱いた途端、コードはリスクにさらされている。間違った思い込みをすることもあるだろうし、あるいは状態のほうが変化するかもしれない。自動リファクタリングのような、望ましいプログラミングツールの多くは、状態という考えがなければ、今よりも容易に構築することができる。並行処理と状態は相性が悪い。状態がなければ、並行処理プログラムにおける問題の多くは消滅する。ここで言われているように「状態」を扱うことはいいことばかりではなく、このような問題も孕んでいます。複雑な「状態」に関連したコードを変更することは、プログラマを神経衰弱にさせます。並行処理でスレッドセーフを考える場合も「状態」に関連する処理に細心の注意を払う必要があります。これが「副作用」の側面です。状態は扱い方を間違えると「薬にも毒にもなる」といえます。「副作用」が伴う命令と、「副作用」がない問合せの二つに分離して、その影響を最小化することが求められるわけです。(純粋)関数型言語は副作用を扱いません。状態は変化せず不変だからです。Beck氏は、それを人間の脳、広義の意味でメンタルモデルとの親和性を考えると魅力的ではないと語っています。関数型のプログラミング言語では、変化する状態をまったく使用しない。した関数型言語で、多くの人に使われているものは今まで1つもない。我々の脳は、変化する状態に対応するように構造化され、条件付けられているので、状態は有用なメタファーだと私は思う。単一代入あるいは無変数のプログラミングでは、有効な思考戦略の多くが使えなくなるので、魅力的な選択肢とはならない.蛇足ですが、これはおそらくHaskellなどの純粋関数型言語に対する評価だと思われますが、Scalaは「変化する状態をまったく使用しない」言語ではなく、原則的に不変としながらも、場合によっては可変を扱える言語です。いう意味ではこの要求にも応えることができるのがScalaと言えるでしょう。ここから学べる教訓としては、人間のメンタルモデルを無視するような「副作用を完全に排除する」ではなく、「必要なところでは副作用を起こすが、それ以外では極力副作用を抑える」設計戦略が求められるのではないでしょうか。「副作用を最小限に抑える」設計戦略が求めれます。FUNCTIONSというパターンで示されています。DDDにおいても、プログラム上の操作は大雑把に命令と問合せ二つのカテゴリに分けられ、問合せはシステムの情報を取得することで、命令はシステムのあらゆる変更を及ぼす操作だと言及されています。このパターンは、CQSの原則に反しません。どのように解決するかはP251の以下の下り。itself.JavaEE勉強会の訳がありました。ロジックは、可能な限り(副作用なく戻り値を返すような)関数の中に置く。命令は単純な操作として厳密に分離し、ドメイン情報は返さないようにする。副作用をより厳密にコントロールする場合は、複雑なロジックをValueObjectに移動する。ValueObjectの概念に適合する場合のみ。副作用のない関数は(特に不変なValueObjectにおいて)、操作を安全に結合できるようにする。佐藤さんの分かりやすい説明も引用します。Functions(副作用の無い関数)パターン様々な副作用のあるメソッドを組み合わせてしまうと、どこでどんな影響が発生するか予測できなくなるため、プログラムの振る舞いを理解することが極端に難しくなってしまう。そのため、単に引数を加工して結果を返すだけのロジック部分はできる限り副作用の無い関数としてプログラム化し、実際に状態を変化させる操作(コマンド)を最小限に留める。副作用の無い関数同士ならば、組み合わせて使っても何が起こるか予測できなくなることはない。副作用の無い関数はまた、テストも容易である。値オブジェクトは状態が変わらないので、値オブジェクトがもつ振る舞いには基本的に副作用が無い。1メソッドだけでは表現できない複雑なロジックの場合は、値オブジェクトにそのロジックをもたせることで、そこに副作用が無いこと示すこともできる。以下のmixInメソッドでは、絵の具(Paintクラス)と絵の具を混ぜあわせます。絵の具の量(volume)と、絵の具の色である顔料(red,yellow,blue)を更新します。副作用が伴うわけです。FUNCTIONSパターンを適用して、できるだけ副作用を抑えたコードにしてみます。PaintのmixInではPigmentColorクラスが持つmixedWithメソッドを使って、顔料を混ぜています。PigmentColorクラスはバリューオブジェクトです。バリューオブジェクトは原則的に不変オブジェクトで、このPigmentColorも不変オブジェクトです。顔料の更新処理を関数化して副作用を起こさないにし、関数で得た新しいpigmentColorを入れ替える処理をmixInメソッドで行う。顔料を混ぜあわせて新たな顔料を生成して返す。副作用は起こさない関数として実装する。}}Paintクラスで直接顔料を更新するのではなく、その処理は顔料というバリューオブジェクトに移動しています。前述した通り、バリューオブジェクトは不変です。顔料の更新結果はthisには反映できないため、新たなPigmentColorオブジェクトを生成して返します。この処理は副作用が伴わない関数として実装します。関数は、引数であるpigmentColorやraitoの入力条件を受け取り、更新後のPigmentColorを出力として返しています。関数は”問合せ”です。PaintクラスのmixInメソッド内で、その顔料の更新結果をPaintのpigmentColorに反映する。この処理は副作用が伴う命令として実装します。ratio);つまり、副作用が伴うロジックを、副作用が伴わない(問合せとしての)関数と、副作用を伴う命令を分けて用いることで、副作用を安全に局所化することができるわけです。以下に示すような導入の効果があります。Colorは「副作用のない関数」を提供し、その結果は理解しやすく、テストしやすく、安全に使うことができ、他の操作と組み合わせやすいようになっている。色の合成に関する複雑なロジックは完全にカプセル化されており、開発者がこのクラスを使う際には実装を理解する必要もない。これは非常に実益のある設計パターンではないかと考えます。複雑なアプリケーションで、効力を発揮することは言うまでもありません。このパターンは言語を問いませんが、Scalaとの相性も抜群でしょう。Segregation)というアーキテクチャパターンが話題になったことがあります。これは文字通り、前述したCQSをアーキテクチャ全体に適用できるように発展させたパターンです。DDDをベースにしているため、DDDとは親和性が高いです。id:digitalsoulさんのブログの翻訳が分かりやすいです。RomanticismCQRSは、アーキテクチャレベルで副作用をうまく扱うための方法論のひとつと言えるでしょう。これに対して、Gregは同様の原則を用いつつ、それをシステムのアーキテクチャ全体に適用し、システムの更新処理(Command)を参照処理(Query)から明確に分離したのです。このうち更新処理は、私たちがすでに「ドメイン」として知っているもので、システムの価値を生むすべてのふるまいを含んでいます。参照処理は特定のレポートを行う必要性に特化したものです。ユーザがドメインのふるまいを実行できるようにするアプリケーションの画面を考えてみて下さい。このような伝統的なレポートは、データベースの参照によって実現されてきました。FrameworkCQRSの具体例を学ぼうと思ったので、いろいろ調べていたら以下のJavaのフレームワークを見つけました。Code中身はまだよく調べていませんが、このダイアグラムを見るとイメージがつかめるかもしれません。http://code.google.com/p/axonframework/wiki/ArchitectureOverview上段が命令で、下段が問合せで、上段の中央がドメイン層ですかね。この絵だけみるとコマンドが関連するところだけにドメイン層があって、問合せにはドメイン層は介在しないような絵になっていますが、サンプルコードをみた限りでは問合せにも薄いドメイン層があるのかな、という印象。いずれにせよ、どんな言語、どんなアプリケーションにおいても、「副作用」とうまく付き合う必要があると思います。一度考えてみるとよいかもしれません。