副作用に気をつけろ!
2008.02.23
日々のプログラミングの雑感です。
やはり仕事のプログラミングですと、他の方が書かれたプログラムを修正して....というのが、大きなウェイトを占めるのは当然です。このとき、いつも私が気になること、というとか「私がいつも気をつけて実践している、複雑さを低減する有効な手段なんだけども、あまり他の人が理解していない」と見受けられることについて書きましょうか。
その基準、というようなものは、単純にこのコトバで表せます。
ゲッタは、外部から見える副作用を持つべきではない
ということです。この原則を私が理解し受け入れたのは、やはり Meyer の「オブジェクト指向入門」での主張がきっかけです。Meyer が作った言語 Eiffel は Pascal(Ada)ベースですから、
値を返す function(関数)← Java だとゲッタはこれ
と、
値を返さない procedure(手続) ← Java での典型はセッタ
をキッチリ構文上区別します。でしかも、「値を返す関数」では、「副作用がないこと」、言い換えると「オブジェクトの状態を変更しないこと」を強く推奨しています。ここでは「外部から見える」という限定が付くわけでして、勿論こういうかたちでの副作用はOKです。
- 1. そのオブジェクトのライフサイクルの中で「一度だけ」しか実行されない初期化の呼び出し(たとえばいくつかあるゲッタで最初に呼ばれたものが、全体の初期化をするとか)。
- 2. 参照を最適化するために、今まで取得されたデータをキャッシュする。
この2つのタイプの副作用は、外部からは完全に隔離されています。「副作用があったか」どうかは、外部的な振る舞いには一切の影響がありません。こういうのはOKなのです。
....仕事でよくトラブルの根源になるのは、やはり「副作用のあるゲッタしかない(しかも引数付きとか)」ケースで、「副作用がないつもり」でそれを呼び出して、想定外の結果を得ることがあるわけです。特に、引数付きで副作用のあるゲッタというと、これは単純に「引数付きのセッタ」と「引数がなく副作用もないゲッタ」に分離できるケースがほとんどなのです。
もし、「動作に必要なプロパティが全部揃ったこと」を前提として、何かの内部オブジェクトを構築する必要があるのならば、それはコンストラクタ引数でそういう制約(そのケースではセッタ自体コンストラクタ引数に解消すべきです。そうできるケースでは Immutable & FlyWeight の恩恵が受けれるかもしれません)を与えるか、たとえば activateOptions() のような名前の無引数のコマンドを用意して、これをトリガにして内部オブジェクトを構築すべきです。activateOptions() という名前は Log4j のAppender仕様で憶えて、気に入ってます。何するか分かりやすいでしょ。
勿論こういう明示的な処理化トリガコマンドはきっちりドキュメントして周知徹底すべきです。私はこの分離が、「何か暗黙のセッタ呼び出し順に依存してなされる初期化」や「他のプロパティへの依存性があるゲッタ」よりもずっと優れている、というように思うわけです....
....こう考えてみて、この構図って実は REST とも似ているようにも思います。REST原理だと、
- GET は、副作用がなく内部状態を変更しない(だからキャッシュできる)
- PUT, DELETE, POST は副作用があり、内部状態を変更する。この時、PUT, DELETE は更に「べき等であるべし」という制約がある。
こう見てみると、まったく同じことですね。更に考えれば「セッタのべき等性」も制約に付け加えてもいいのかもしれません。「追加的」な変更は常に set~() ではなくて、add~() か append~() という名前で明示する、というルールも導入したいところです。
皆さんはいかがでしょう?
投稿者 : 杉浦 こずえ | 投稿日時 : 2008.02.23 08:51
あすなろBLOGのトラックバック・コメントは承認制になっています。
すぐにブログに反映されませんので、ご了承ください。





