ラベル Refactoring の投稿を表示しています。 すべての投稿を表示
ラベル Refactoring の投稿を表示しています。 すべての投稿を表示

2013年8月3日土曜日

Out parameter, collecting parameter, visitor pattern

『Effective Java (第二版)』の「項目15 可変性を最小限にする」では不変オブジェクト (Immutable object) を推奨している。これは状態を持たないから、シンプルに扱える。反対に状態を持つ可変オブジェクト (Mutable object) は、扱いに気を遣う。扱おうとしているときにどういう状態なのか意識しないといけない。状態に起因したバグがあったら、最悪、状態が変更されている場所を全て確認しないといけない。

これに真っ向から対立しているのが、Out parameter。状態を変えるために渡す可変オブジェクトのことを指す。Webを検索してみると、アンチパターンとして紹介されている記事が見つかる。

でも、"Collectiong parameter"として『実装パターン』や『パターン指向リファクタリング入門』で紹介されていたりする。また、『パターン指向リファクタリング入門』では、その延長線上に"Visitor Pattern"を適用したパターンも紹介されている。

確かにこちらの方が合理的なケースもありそう。でも、基本的にはあまり使わない方がよさそう。コマンドとクエリは分離するのが原則だ。使うなら、Javadocに明記するなりしないと混乱を呼びそう。C#のoutキーワードに相当するものがJavaにはないから、引数の状態を変えるかどうか宣言だけから読み取れない。

ところで、out (C#)に使用例として「メソッドが複数の値を返すようにする場合に便利」だなんて書かれている。同時に扱いたい複数の値があるなら、クラスとしてまとめた方が読みやすくないだろうか。原則として、入力を与えたら戻り値が返ってくる関数として考えたい。

References

2013年5月18日土曜日

JavaScriptで依存性を排除するためにデフォルトパラメータを導入する

JavaScriptのクラスHogeをテストしたいのだけれど、次のような状況でそもそもインスタンス化が大変な場合にどうするか? という話。
  • Hogeはコンストラクタ内で別のクラスFugaをインスタンス化してプロパティに設定している。
  • Fugaはインスタンス化の際にサードパーティ・ライブラリのAPIを大量に呼び出している。
  • サードパーティ・ライブラリは、開発環境では使えない(例えば、REST APIを提供するサーバがまだ立っていない)。
この場合をコード例で表してみる。サードパーティ・ライブラリが使えないため、Fugaのコンストラクタ呼び出しでエラーが発生し、Hogeをインスタンス化できないとする。
Hoge = function () {
  this.fuga = new Fuga();
  // ...
}
// ...
Fuga = function () {
  this.thirdPartyLibrary = new ThirdPartyLibrary();
  // ...
}
// ...

FugaがThirdPartyLibraryをラップしているから、テスト時にはテストダブルに入れ替えたい。でも、そもそもHogeもFugaもインスタンス化できない。

こんな状況に対応するためのリファクタリングとして、『レガシーコード改善ガイド』は25.14「コンストラクタのパラメータ化」か、デフォルト引数の追加を紹介している。オーバーロードのないJavaScriptでは、コンストラクタをパラメータ化できないので、デフォルト・パラメータを指定する。

デフォルト・パラメータがtrueと見なせる(false、null、0、""、undefinedのいずれでもない)なら、こんな風に短く書ける。||は真偽値ではなくてfugaを返す性質を利用している。
Hoge = function (fuga) {
  this.fuga = fuga || new Fuga();
  // ...
}
// ...

もっとロバストな書き方は次の通り。こちらは、デフォルト・パラメータがfalseと見なされる場合も使える。実用的なのは、デフォルト値が0やfalseの時。
Hoge = function (fuga) {
  if (typeof fuga === 'undefined') {
    fuga = new Fuga();
  }
  this.fuga = fuga;
  // ...
}
// ...

References



2012年12月28日金曜日

Getter/Setterを減らす

Getter/Setterばかりが目立つようなクラスは、きっとドメインモデル貧血症に陥っている。だから、もっと振る舞いを持たせて、カラフルな名前を付けたり。それに、Setterで内部状態を外から操作できるようでは、カプセル化しきれていない。状態が変化しないImmutableなクラスを目指して、少なくともSetterは減らしていきたい。ただ、フレームワークが要求したり、と完全に消し去ることはできないと思う。というわけで、なるべくうまく付き合いたい。

なんてことを考えるようになった。そのキッカケや経緯、参照したパターンについてまとめておく。

『オブジェクト指向エクササイズ』("Object Calisthenics") の9つのルールに、Getter/Setterを禁じるものがある。
ルール9:Getter、Setter、プロパティを使用しないこと
Rule 9: No getters/setters/properties
初めてこのルールを知ったときは、意図には同意できたけれど、適用しようとは思わなかった。同意できたのは、単にフィールドをプライベートにして値の取得/設定をすることが、オブジェクト指向の特徴の一つであるカプセル化だという説明に違和感を持っていたから。Getter/Setterを通せばAPI変更に強くなるとは言え、値を自由に設定していたら、隠蔽できているとは思えない。適用しようと思わなかった最大の理由は、今思うと、実装をイメージできなかったからだと思う。だから、適用しようにもできなかった、の方が実態に近い。

「求めるな、命じよ("Tell, don't ask")」なんて言い換えられても、やっぱりよく分からなかったけれど、最近、少しずつ分かってきた気がする。痛い目を見たり(Setterでインスタンスへの参照を返していたせいで意図しない変更を加えてしまったり、名前がgetで始まるメソッドをGetterだと思って気楽に使っていたら思わぬ副作用があって面食らったり)、それで『Java言語で学ぶリファクタリング入門』や『リーダブルコード』、"Effective Java 2nd Edition"の内容を応用しようと試行錯誤したりして、ようやく使う/使わないの判断が少しクリアになった。

例えば、Remove Setting Methodのリファクタリングを適用すると、初期化のためだけのSetterを使わずに済ませられる。代わりに、(デザインパターン"factory method"を適用していたら、factory methodを経由して)コンストラクタで初期化することになる。そのまま、"Effective Java 2nd Edition"の"Item 15: Minimize mutability"してMutableにできれば、Setterは要らなくなる。あるいは、『リーダブルコード』のいう〈カラフルな単語〉を使った名前を付けようとすれば、そんな名前が付けられるくらい十分に抽象化しようとすれば、自然とGetter/Setterのような無味乾燥な名前は相応しくなくなる。

ただ、それでGetter/Setterがなくせるかというと、そうはならないと思う。現実問題、フレームワークから利用されるためにEntity Beanにする必要があるクラスも出てくるし、抽象度の低いクラスで融通が利かなくなる。Getter/Setterを使用しないというのは、あくまでエクササイズのルールであって、実戦における規約ではない。だから、リファクタリングSelf Encapsulate Fieldや"Effective Java 2nd Edition"の"Item 14: In public classes, use accessor methods, not public fields"があるんだと思う。

References