さて、内容は静的なクラスやメソッドとテストのしやすさを例に、プログラミングにおける重要な概念を説明しています。Lithiumのかかげる「consistency」を垣間見れる内容。
自分らスタティックをあちこちで使ってるね。テストするのがすごく大変なんじゃないの?
厳密に言って、スタティックなものは実に簡単にテストできるよ。 フレームワークだけでなく、その上のアプリケーションも含めて、全体をテスト可能なコードにしておけるように、Lithiumは関数プログラミングからいくつかのコンセプトを借りてきているんだ。 これらのコンセプトを理解するために、まずいくつかの用語を定義しておこう:
状態 :ソフトウェアを書くうえで必要不可欠なもの。ウィキペディアは状態を "システムの様々な条件を計測した値のスナップショット"と定義しているPHPアプリケーションの開発においては、文脈、もしくはスコープにによって異なるが、Webサーバーがindex.phpをロードしたときには、状態はスクリプトのリクエスト(GETやPOSTデータなど含む)、$_SERVERや$_ENVによって得られるシステムの情報、そのほかのスーパーグローバル変数によって定義されることになる。明白でないが、状態は現在の日付や時間なども含む。メソッド内で状態と言えば、渡されたパラメータ、メソッドがバインドされているオブジェクト(i.e. $this)に納められたもの、 そしてグローバルにアクセス可能なそのほかすべてのものが含まれる。
副作用 :この概念は状態と密接に関連する 副作用 とは、メソッドやそのほかのルーチン実行中に自分のスコープの外で引き起こす変化のことを指す。副作用には、グローバル変数の値、オブジェクトのプロパティ(i.e. $this)などが変更されることや、データベース、ファイ縷々システムに対する変更、参照渡しによるパラメータの値の変更、さらにはechoによる出力なども含む。
可変性 :可変性とは、ものが変化できる性質を持つことを指す。PHPにおける可変性の典型的な例は、変数やオブジェクトのプロパティにみられる。対照的に、不変性とは変更できないことを指す。たとえば、すなわちグローバルまたはクラスレベルの定数がそうだ。したがって、 可変性な状態 とは、変更可能なアプリケーションの状態(上記参照)の1要素と言うことができる。これらの変更が、いわゆる副作用を引き起こすもの、である。これは、明らかに見えるかもしれないが、後でもっと重要になってくる。
参照透明性 :渡されたパラメータのみによって返値の値が決まり、また自分のスコープの外の状態に対して何の副作用も発生しないという、メソッドや関数のもつ特質のこと。参照透過性を持つメソッドは、外部の状態を準備する必要がないので、テストするのが非常に簡単である。同じ値を渡せば、必ず同じ出力が得られるからである。また参照透過性を持つ関数は、キャッシュするのも簡単だ。同じパラメータであれば何度呼び出しても同じ結果が返るからである。このプロセスは、 メモ化 として知られている。
さて話を元に戻すと、よく言われている静的なもののテストが抱える問題点とは、変更可能なグローバルな状態と関わらなくてはいけない、ということですね。例として、いつ変更されるかわからない外部の(通常はグローバルな)情報に依存しているスタティックメソッドなんかをテストする場合のこと。シングルトンは、このアンチパターンの代表例だね。シングルトンの背後にある考え方は、ある時点で使用可能なインスタンスが一つ(たった一つ)だけあるということだ。
この考え方の本質的な問題は、シングルトンの属性がどうなっているのか知るすべがないこと。だってアプリケーションのどこからでも、それらを変更することができるわけだから。これは、ほぼすべてのソフトウェアがもつロジック的なバグの原因となっていることを証明しているように思う。唯一の解決策は、シングルトンの設計を、一度生成されたら属性を変更することができないように(つまり不変に)することだろう。 もしくは、シングルトンが必要ないような構造に設計しなおすか。
幸いなことに、Lithiumのすべての静的クラスは参照透明性なメソッドか、利用パターンとして不変を要求させるようなメソッドによって構成されている。 いくつか例を見てみよう。参照透明性のメソッドとしては、lithium\util\String::insert()はテンプレート文字列に値を挿入した文字列を返すし、lithium\util\Inflector::camelize()は単語や文章の別バージョンを生成します。これらの関数は外部に副作用を発生させないし引数だけによって決まる値を生成します。
不変な静的クラスの例としては、Adaptableを継承するクラス、例えばCacheやConnectionなどがあります。これらのクラスは、システムレベルのリソースへのアクセスを形成し提供してます。例えば、データベースへの接続、ユーザーのセッション、キャッシュの構成などです。これらのクラスは、どうやってアクセスするか、操作するかという情報を config()メソッドで得られる情報によって設定します。これは、アプリケーションのブートプロセスの間に、一度だけしか起こりません。そのクラスに対する以後のアクセスは(たとえばadapter()メソッドを通して)、いつも同じ属性を持った同じアダプターインスタンスが返されます。これにより、我々の水面下でアプリケーションの状態が変わってしまうことによって起こる問題を回避できます。
別の問題としてよく言われるのは、静的なもののテストはmockを用意するのが難しい、もっといえば依存性を取り替えるのが難しいことでしょう。以下の例を考えてみます。
class A {
public static function foo() {
// return some calculated value
}
}
class B {
public static function bar() {
$result = A::foo();
// perform some calculation on $result
return $result;
}
}
class B {
public static function bar($dependency) {
$result = $dependency::foo();
// perform some calculation on $result
return $result;
}
}
Lithiumはこの依存性の問題に対応するため、プロテクテッドな$_classes属性を一貫して使っています。次のコードはlithium\action\Dispatcherの一部です。
class Dispatcher extends \lithium\core\StaticObject {
// ...
protected static $_classes = array(
'router' => 'lithium\net\http\Router'
);
// ...
}
$router = static::$_classes['router'];
$result = $router::process($request);
すべての依存関係がこのように構成されているわけではありませんが、フレームワーク全体で使われている大事な規約です。例外として、変更できない依存関係はユーティリティメソッドへの依存であり、これらは参照透明性が保証されている場合がほとんどです。なので、ハードコーディングされた依存関係があるからと言って、テストが難しくなったり複雑になることはありません。
0 件のコメント:
コメントを投稿