2010年1月19日火曜日

疎結と委譲

連載をやるといいつつまだ全然下書きができてない

しかし、最近オブジェクト指向、アプリケーション設計を考えることが多くなってきたため、デザインパターンちょっとだけだけど触れといてよかったと感じています。ちょっとでも知っていることで、解決への糸口が見つかる。そんな感じです。

疎結させること

オブジェクト間の依存をなるべく減らすことは、オブジェクトが集中すべき業務に対してより集中できるようになることが多々あります。例えば、テレビのリモコンの中身まで気にして生活している場合は、それは疎結されていなくて、ただリモコンはボタンがあって電源を入れるのとチャンネルを変えるのがある程度くらいのインターフェースを知っていればOKというのは、リモコンとユーザとか疎結されていると考えます(自分は)

このインターフェースに対して働くというのが疎結させる方法の1つだと最近考えています。

委譲させること

委譲は「あとは任せた!」という表上は信頼というタテマエと、裏では「そんな仕事にゃ興味ねーんだよ!」という臭い物には蓋をする的な意味合いがあります(ぇ。先ほどのリモコンの場合、ボタンを押した後の処理(電気が流れて、赤外線が放射されてなどなど)は、ユーザからは隠蔽されていますし、ユーザはリモコンにそのことを何も気がつかずに委譲しています。そしてリモコンは赤外線を放射した後は、知らん顔で、あとのチャンネル変更などの処理はテレビ本体のチャンネル変更担当機関に任せちゃいます。これが委譲。

疎結と委譲で各機能の役割が明確になってくる

そうなると部品化(モジュールやプラグイン)が簡単にできるようになったり、テストしやすくなったりすると思います。ここ1年半ほど、一人でプログラムを組む事ばかりだったので、とにかく再利用性に気をつけてきましたが、やっとちゃんとした部品が作れるようになりそうな気配がしてきましたw というかそうしなければならない

マッシュアップの時代へ

話は飛躍しますが、これからはAPI(いろいろありますが)やライブラリやフレームワークをごりごり集めて何かを作り出す力が必要になるんじゃないかと思います。今まで個人ではこなせなかった作業がオープンソースやAPIのおかげで、どんどんできるようになってきています。そしてRails3のモジューラビリティ(話が飛び過ぎ)。スクラッチからすべて起こしてやっていくのが古いとは言いませんが、個人でやっていく場合、マッシュアップは要だと思います。生産性や効率という意味では、再利用可能なプログラムが裏切ることはそうないと思います。ただし再利用されないとか、汎用性が必要ない場合は最適化したコーディングも必要だと思います。いつも最終的にはバランス感覚ですね。

2010年1月15日金曜日

CakePHPの語形変化を使う

Inflectorを使いましょう

//○
$model_name = Inflector::classify($this->name);

//× 直接書くのはスマートじゃない
$model_name = 'User';
そもそも、CakePHPに限らず語形変化が規約によって定められているフレームワークはInflector(語形変化用クラス)を持っているので、それを使った方が絶対に良いです。CakePHPの場合はInflectorのドキュメントがあります。

2010年1月13日水曜日

Subversionで無視ファイルを設定する方法

svn propedit svn:ignore >ディレクトリパス<

svn propedit svn:ignore config --editor-cmd=vim
--editor-cmdはEDITORかSVN_EDITORが設定されていない場合に、どのエディタを使って設定するかを指定するためのものです。エディタで開いたら、database.ymlとか無視したいファイル名やパターンを入力して書き込んで終了!

Windowsの場合はThumbs.db、Macの場合は.DS_Storeなど、毎回コミット時に邪魔してくれるやつらをこれで無視できます。たぶんTortoiseSVNでも、EclipseのSubversiveやSubclipseでも簡単に設定できたはずだけど忘れました。もしできてなかったなら、たぶん毎回コミット時にコミットしないファイルのチェックをはずす作業中に発狂してるはずだし。。。

版管理に入れないコツ

例えばRailsならdatabase.ymlやlog/*.log、CakePHPならconfig/core.phpなどなど、実行環境によって設定が異なるものは外します。その代わりに、設定サンプルとして、database.yml.sampleや、config/dist-core.phpなど別名ファイルを用意しといて、それをコピー(ファイル名はconfig.phpにするなど)して簡単に設定できるようにしておくと良いです。さらにさらにFTPにまで言及すると、これらの設定ファイルは基本的に編集しないはずなので、間違って編集するのをさけるためにあえてパーミッションを644から444にします。おすすめです!

バージョン管理は異なる開発環境間のファイルの共有にもなります。共有すべきでない、ローカル(環境)に依存するところは、多少手間ですが、上記のように工夫することで、無用な競合やエラーを回避できます。あとは手順書があれば完璧ね!Wikiに書くでもいいわよ!(誰

2010年1月8日金曜日

CakePHPでテストする時にFixtureのデータを再利用してDRYにしよう

DRYはDon't repeat yourself。コードの重複を省くこと

CakePHPのbakeで生成されたテストは微妙というより、そのままでは使えない。とくに$expectedのところ。

function testUserFind() {
  $this->User->recursive = -1;
  $results = $this->User->find('first');
  $this->assertTrue(!empty($results));
//ここ!
  $expected = array('User' => array(
    'id'  => 1,
    'name'  => 'Lorem ipsum dolor sit amet',
    'created'  => 'Lorem ipsum dolor sit amet',
    'modified'  => 'Lorem ipsum dolor sit amet'));

  $this->assertEqual($results, $expected);
}

// こうしたい
function testUserFind() {
  $this->User->recursive = -1;
  $results = $this->User->find('first');
  $this->assertTrue(!empty($results));
  $this->assertEqual($results, $this->UserFixture->records[0]);
}

この$expectedで定義している配列はFixtureでも定義しているんだし、そもそもFixtureで入れたデータをもとに検証するのだから、ここではそのままFixtureを利用すれば良いだけの話のはずなんだけど。。。

App::importでFixtureを呼び出す

やり方は簡単です。

//user.test.php)
// ..省略
App::import('Fixture', 'User');

class UserTestCase extends CakeTestCase {
//..省略
  function startTest(){
  //..省略
    $this->UserFixture =&  ClassRegistry::init('UserFixture');
  //
}
これで上記の「こうしたい」コードで動くようになります。

さらにコードの品質を上げる

コードの読みやすさも大事ですが、分かりやすさも大事です。そしてPHP配列の場合順番が変わる可能性がある(やり方次第だけど)のと、文字列を使って意味を明確するために連想配列を使うことをおすすめします。それと、Fixtureの$records以外はデータベースに挿入されないので、save時のテストデータや不正なデータなどはプロパティを分けて用意します。こんな感じ。。。

//user_fixture.php
class UserFixture extends CakeTestFixture{
  var $records = array('admin' => array(
    'id' => 1,
    'name' => 'Admin',
    'flag_admin' => 1,
    'created' => '2010-01-08 05:19:23',
    'updated' => '2010-01-08 05:24:52'
  ));

  // 不正なデータはIDを400〜499にするとかルール漬けして下さい!
  // 500台はインジェクションコードを埋め込んだデータとかね!
  var $invalids = array('name' => array(
    'id' => 400,
    'name' => null,
    'flag_admin' => 1,
    'created' => '2010-01-08 05:19:23',
    'updated' => '2010-01-08 05:24:52'
  ));
}

// user.test.php
//Fixtureの利用の仕方は上記参照
//名前が空の不正なデータの場合のテスト(バリデーション設定済みという設定)
class UserTestCase extends CakeTestCase{
  function testSaveInvalidNameUser(){
    $this->User->create();
    $this->User->set($this->UserFixture->invalids['name']);
    $this->assertFalse($this->User->save());
  }
}

残念なことに、このサンプルコード自体はテストしてないので動くか分かりませんが、雰囲気はつかめると思います。Fixtureを修正した時にテストコードの$expectedを修正する作業が減ります。メンテナンス性が向上しますね。

※ empty関数のところが!emptyemptyになってるのはコードハイライターのバグです

PHPがテンプレート言語っぽいのになぜそれでもテンプレートエンジンを使うのか

それが合理的だからです

もし合理的でない場合は使う必要がないだけです。

例えばエスケープ

生のPHPはHTMLのエスケープ処理がされない

$name = '<script>';
echo $name; #=> <script>
//CakePHPの場合
echo h($name); #=> &lt;script&gt;

//Twig (デフォルトでエスケープするよう設定できる)
{{name}}
//Smarty
{$name|escape}
このサンプルだけだとあまりメリットを感じないかもしれないけど、大抵の場合エスケープはしないといけないし、他にもnumber_formatなど整形する関数やヘルパなどを使うと思います。そのときに関数をe(h(g(d(f($name))))なんて書いて括弧の数が合わずにエラーを出したりするより、{$name|truncate|escape}と書いた方が読みやすい。

デフォルトでエスケープするのが主流になりそう

今知ってる範囲ではRails3では(2.3でも設定で可)デフォルトがエスケープ処理になり、直接データを表示したい時はrawを使うようになりました。Smartyもそうなるんじゃないかなーと勝手に思ってます。Twigは最近知ったテンプレートエンジンですが、なかなか興味深いですね!趣味でWeb制作をする人も多い世の中なので、デフォルトでエスケープするようになる方がいいし、仕事でもその方が効率がいいです。インジェクション対策の一つですね。

理由もなくSmartyを嫌うのはやめて

一時期Smartyが流行り、そして脱テンプレートエンジンが流行ったと認識してますが、どちらにもメリットデメリットがあると思います。問題はMVCのVのところで、テンプレートエンジンを使った方が、Vが行うべきタスクにより集中でき効率と品質が上がるかであって、そうでもないのなら使わなければいいだけの話です。PHP自体がテンプレート言語みたいなものだから、と思い込んで使わないというのは変です。

不変と可変の分離と抽象化と委譲と委譲と委譲の普遍性(1)

しまった、もう独り言というタグは消したんだった

このブログはどのラベルよりも「独り言」というラベルが貼られた記事数が一番大きかったのですが、ある日突然消えたんだった。

今回も独り言のようになりそうなのだけど、話がでかいので回を分けたり、ちゃんと図を作成して、自分が考えてることを整理するためにも記事を書こうと思います。

パターンに学ぶもの

デザインパターンを学びつつ、そして先日、プロジェクトの工程の話をしていた時にふと閃いたのが、不変な要素と可変な要素は抽出して分離したり、もしくはできるだけ疎結させて委譲できるようにした方がいいということです。いきなり抽象的な話かもしれませんが、考えること自体は簡単です。例えば、プロジェクトの場合、契約書は不変化されたものです。可変なものはヒトです。極端な話だけど。ヒトが途中で抜けてプロジェクトが出だしにもどるようなことがあるとすれば、そこは契約や書面でおさえておかなければならないし、それによってヒトに対する依存度を下げることができる。この考え方は、オブジェクト指向でも似てると思う。

なんじゃそりゃ!?ということを思われてそうだけど、それをうまく言葉化してここに載せれることができたら、それは自分の中でうまく整理できたことになるので、ちょっと頑張ってみたいと思います。ま、不定期ということで。

ラベルを何にしようか迷い中

2010年1月5日火曜日

CakePHP fixturesを使う際にbeforeSaveとかされない件

誰か良い解決方法知ってたら教えてください

ソースを見てみたら、モデルを経由せずに、普通にINSERT {$table}とかしてるからびっくらこいた。じゃあinsertする前に自力でやりたいことやらせていただく!という感じで解決。

サブクラスのinitで処理して親クラスのinitを呼び出す

(ソースを読み間違ってたらごめんなさいだけど、)残念なことにモデルを使ってsaveしないので、beforeSaveを使っても動かない。ので、再度beforeSaveでやっている手順を記述。あーDRYじゃないよ〜><。

// 独自の暗号化処理の例
class UserFixture extends CakeTestFixture {
 //...省略...
  function init(){
    foreach($this->records as $k => $v){
      $this->records[$k]['password'] = User::encrypt($this->records[$k]['password']);
    }
    parent::init();
  }
}