D-7 <altijd in beweging>

Day to day life of a Perl/Go/C/C++/whatever hacker. May include anything from tech, food, and family.

カテゴリ:開発

前回の話ではLocale::Maketext::Simpleだけでは「どの言語を使うか」までは判定してくれません。そこで登場するのが I18N::LangTagsとI18N::LangTags::Detect。確認してないけど、最初の数行のコードの癖からして作者はLocale::Maketextを書いた人と一緒な気がする。

これの使い方は基本的に以下の通り:
use strict; use I18N::LangTag (); # なんかいっぱいエクスポートしちゃうので・・・ use I18N::LangTag::Detect; use Data::Dumper; print Dumper( [ I18N::LangTag::implicate_supers( I18N::LangTag::Detect::detect() # もしくは # I18N::LangTag::Detect->http_accept_langs() ) ]); # env LC_ALL=ja_JP perl script.plと実行すると以下のように出力されるはず # $VAR1 = [ # 'ja-jp', # 'ja' # ];

マニュアルには実はI18N::LangTags::Detect::detect() しか書かれていない。が、実はHTTPヘッダーを見たり、LC_ALLを見たり、みたいな関数が何個かある。Catalyst::Plugin::I18NではHTTPヘッダ以外見るところはないので http_accept_langs()を直接呼んでいる。コマンドラインの環境変数も同じように確認してほしかったら上記のdetect()を使うのがよろしい。

I18N::LangTags::implicate_supers() を使っているのは、言語タグの中の国指定とかを取り外したバージョンを埋め込むため。例えば en_GB (英語・イギリス)とen_US(英語・アメリカ)でそれぞれ国際化ルールを別にしてもいいけど、たいていの場合はひとつ「en」用の国際化データしか用意しない場合が多いので、それを差し込んでおかないとen_GB/en_US用のデータがない→じゃあ国際化できない>< みたいな事になってしまう。

implicate_supers()関数はこれを行ってくれるので、例えば以下の用にLC_ALL=ja_JPという形で言語タグを指定していた場合、ja-jpとjaの二つの言語タグを返してくれるわけだ。

前の記事ではどの言語にローカライズするのかをloc_lang()等で変更する、と書いたが、要はこの結果をloc_lang()に渡すようにすればいいのだ:
loc_lang( I18N::LangTags::implicate_supers( I18N::LangTags::Detect::detect() ) );

以下次号。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

Config::Generalは構文がプログラマーには美しくないのだけど、YAMLはインデントが必要!という事がわからないとコピペして設定を使い回す人が困るという問題があるんだよね、と一応前提を言って置いて・・・

soffritto::journal

なんでConfig::Generalをあきらめたかというと、

   connect_info dbi:mysql:myapp
   connect_info username
   connect_info mypassword

のあとにどうやってmysql_enable_utf8 => 1を書いたらいいか分からなかったから><

そういう場合はこうするとよいと思うのです:
use strict; use Config::General; use Data::Dumper; my $string = <<EOM; connect_info dbi:mysql:dbname=hoge connect_info username connect_info password <connect_info> mysql_enable_utf8 1 </connect_info> EOM my $cfg = Config::General->new(-String => $string); print Dumper({ $cfg->getall }); # 結果 $VAR1 = { 'connect_info' => [ 'dbi:mysql:dbname=hoge', 'username', 'password', { 'mysql_enable_utf8' => '1' } ] };
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

MojoMojoを使ってみて国際化についてちょっと研究してみた。CPANにはLocale::Maketext, Locale::Maketext::Lexicon, Locale::Maketext::Simple等々色々あって正直よくわからんかったのだが、今回調べてみてようやく把握した。

まず先に行っておくけど、Locale::Maketext::* 系のモジュールのコードは正直クレイジーなので、暇じゃなきゃあんまりソースコードを漁るのはお勧めしない。

国際化の大まかな流れは、文字列IDがあって、それに対応する言語の「翻訳」が存在する、という感じ。Catalystを使っているなら、Catalyst::Plugin::I18Nを使用して、MyApp::I18N名前空間以下に.poや.moファイルなどをおくのが主流。

問題はHTML::FormFuなど、他の国際化されたコンポーネントを入れ込む時や、プラッガブルな形で国際化ファイルを追加する時。一部はLocale::Maketextを使っていたり、一部はLocale::Maketext::Lexiconを使っていたり・・・Catalyst::Plugin::I18NはLocale::Maketext::Simpleを使っている。

まずそれぞれのおおまかな違い。Locale::Maketextが基本中の基本。これの大まかな動作としては、国際化用の名前空間を指定して、それ以下に言語毎の.pmを置く。例えば国際化用の名前空間がMyApp::I18Nだとしたら、日本語用の文字列ID → 翻訳文字列はMyApp::I18N::jaに入れる。このMyApp::I18N::ja内の%Lexiconというハッシュにこのルールをハッシュとして定義する。ちなみにこのモジュールを使う場合にはMyApp::I18Nのようなモジュールを作るわけだが、このモジュール、Locale::Maketextから継承する必要がある・・・っぽい。

これに対しLocale::Maketext::Lexiconは この%Lexiconのファクトリーと捉えるといいと思う。APIが非常にいけてないが(import()を使って定義を行うというひどいAPI)、この方式を使うと%Lexiconハッシュがtie()された物になり、後から追加するのが容易になる。Locale::Maketext は生のハッシュを使っているのでこのハッシュを自前で操作する必要があるが、Locale::Maketext::Lexiconを使えばその辺りのマージを自動的に行ってくれる。モジュールの継承はLocale::Maketextと一緒だが、こちらはハッシュを定義するのではなく、gettext形式の.poファイル、maketext形式の.moファイルなどを使える。

Locale::Maketext::SimpleはLocale::Maketext::Leixconの定義を簡単にしただけ。それ以上ではない。ただし、動的に翻訳ルールの追加はこのモジュールを通してはできないようにわざわざされている。

ざっくりとはこんな感じ。で、迷うのがあちこちに「Class」引数で名前空間を指定するとかそういう事が書いてある割に、Locale::Maketext::LexiconをLocale::Maketext::Simpleは.poファイルや.moファイルを使用する場合はそれらのファイルが存在するディレクトリを明示的に指定する必要がるということ。これ、ドキュメントにはなかなか書いてない(パスも指定できるよ!とは書いてあるけど、それが必須とは特に書いてない)。ちなみに Perlが認識できる auto/MyApp/I18N/*.po 等のパスにあればその必要はないけど、普通の人はautoにファイルなんていれないから、パスを指定するのがよいと思う。

というわけで以上がすごい悩んで二日ほどwarn()宣言をあちこちに入れながらわかった大筋。実際に投入する際にはもう少々BKが必要なのだが、そこまで書くとすげぇ長いのでまた今度。


    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

mojomojo.png
JPA用にWikiを設置しようと思って、MojoMojoを試してみた。エディタとかが思っていたよりはるかに高機能でよかったので「えいや」っと目に付くところをだいたい日本語化しちゃった。

poファイルの行数で見るとあと半分くらい残ってるんだけど、まぁとりあえず使い始める分にはいいんじゃないだろうか、っていお感じ。

ちなみにこれはレポジトリのHEADから入手可能。今の段階では、初期コンテンツ部分(「MojoMojoへようこそ!」とか)を日本語化するには、mojomojo.confに"default_lang ja"っていう行がないと駄目。まぁまだほとんどそっちには手をつけてないんだけど。

相変わらずインストールにかかる手間はちょっぷりつらいんだけど、すでにPerl使いだったら特に問題ない感じかなぁ。

ちなみにこの一連の作業でgithubバージンをロストした。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

JPA用Tracでごにょごにょしてたら急に添付ファイルをアップロードできなくなった。Apacheのログを見ていると以下のようなエラー

exceptions.RuntimeError: 'generator ignored GeneratorExit' in <generator object at 0x87a638c> ignored

ソースコードをgrepしてみても特にそれらしき文字列はないので、ぐぐってみたらこのようなTrac本体に対する古いチケットを発見した

これと、実際にインストールしてあったソースコードを比較するとexcept GeneratorExitというクローズだけが違っているっぽい。なのでそれを当てて、再度アップロードしたら問題なく動いた。他のチケットを読むとPython2.5でしか再現しないとか言ってるんだけど、どうなんだろうね。とりあえずなんかのメモのために。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

HTTP::Responseに格納された日本語ページをちゃんとデコードしてunicodeで持ちたかったので最初
my $body = $response->decoded_content();

ってやってたんだけど、これだとcp932 (!= Shift-JIS)でこけることが多くて、とりあえずヘッダーとMETAヘッダで見るかと思って以下のようにして、decoded_content() にcharsetが必要であれば渡されるようにした。
my %opts; if ( my $ct = $res->content_type ) { if ($ct =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } if ( my $ref = $res->content_ref ) { if ($$ref =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } my $body = $res->decoded_content(%opts);

しかしこれでも化ける。一番化けるのはcontent-typeにもMETA部分にもchrasetが指定されてない場合。ここでようやくHTTP::Responseのコードを読んだら、charset_strictってオプションつけるとFB_CROAK() をEncode::decode() に渡してくれる事に気づいた。これならせめてエラーは分かるだろ、と思って以下のようにした。
my %opts = ( charset_strict => 1, ); if ( my $ct = $res->content_type ) { if ($ct =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } if ( my $ref = $res->content_ref ) { if ($$ref =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } my $body = $res->decoded_content(%opts);

それでもまだ駄目。っていうかFB_CROAK()してるのにcroakしないでやんの。と、思ってもう一回ソースコードを読んだら、
my $charset = $opt{charset} || $ct_param{charset} || $opt{default_charset} || "ISO-8859-1";

とかしてcharsetに何も指定しないとiso-8859-1とかになるようになってる!これじゃあなんでも適当にデコードしちゃうじゃないか。というわけで、このような状態の時に最も使われている確率の高いcp932をデフォルトの文字コードとして使用するようにしてみた。これでとりあえず動いた:
my %opts = ( charset_strict => 1, default_charset => 'cp932', ); if ( my $ct = $res->content_type ) { if ($ct =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } if ( my $ref = $res->content_ref ) { if ($$ref =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } my $body = $res->decoded_content(%opts);
追記:なんか書き忘れてるなぁ、と思っていたらやっぱり忘れてた。ここまでだと、まだcp932じゃないページで、なおかつヘッダー等から情報が得られない場合はまだ変なことになる。なので、さっきのcharset_strictとあわせて、さらにハック
my %opts = ( charset_strict => 1, default_charset => 'cp932', ); if ( my $ct = $res->content_type ) { if ($ct =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } if ( my $ref = $res->content_ref ) { if ($$ref =~ /charset=Shift_JIS/) { $opts{charset} = 'cp932'; } } my $body; eval { $body = $res->decoded_content(%opts); }; if ($@) { foreach my $charset qw(cp932 euc-jp iso-2022-jp utf8) { eval { $body = $res->decoded_content(%opts, charset => $charset); }; last unless $@; } }
まぁハックだけどとりあえず動く。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

さて、昨日からなんか熱が入っているWAFベンチ大会なんですが、あのベンチ結果は基本的にはただのHello, Worldなので、どれだけ高速にHTTPリクエストを受け取ってから、クライアントにHello, Worldの文字列を送り返すか、というそれだけのベンチです。

そして、この部分を速くするために、新興フレームワークのそれぞれはものすごいマイクロオプティミゼーションを行っているので、そこに普通の感覚でコードを重ねていけば当然スピードは一気に劣化するでしょうし、そこからどれだけコードを追加するのかはもうケースバイケースです。

逆に、Catalyst等のフレームワークは、追加する必要のあるコードがかなり少な目に抑えられますが、シンプルなアプリケーションには明らかに重すぎるわけですね。

掲示板を書くならMENTA/Yacafi/NanoA等でよいでしょう。もっと巨大で複雑なシステムを書くのにそれらを使うと多分書くコードの量がはるかに多くなり、人的コストがかかるでしょうから、Catalystのほうが全然楽でしょう。

かといって、全てにCatalystが対応できるわけじゃなし・・・。そういうわけで、TPOを知りましょう。

(TODO: TPF-J(仮)的には、MENTA/Yacafi/NanoAのうちのどれかを「PHPの代わり」として押したい)
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

tokuhirom/yappo/kazuho/hidekさんあたりが軽量なWAFの開発をなんか熱病に浮かされたかのようにしはじめたので、こりゃ負けてられないとApp::Benchmark::WAFを書きましたよ。

Apache::Testを使って、apacheから実際にHTTPリクエストサイクルを通って、CGIモードとmodperl(メモリに一度載せたらそれを再利用)する状態でベンチを取れるようにした。"make test"とすれば全てのフレームワークに対してab -c 100 -n 1000して計測する。
現在実装している中で、俺のMacBook上での計測値は以下の通り:
[11/13 12:00] # [cgi-app (cgi)]: 22.49 [#/sec] (mean) # [cgi-object (cgi)]: 28.05 [#/sec] (mean) # [cgi (cgi)]: 156.49 [#/sec] (mean) # [http-engine (cgi)]: 2.39 [#/sec] (mean) # [menta-nocompile (cgi)]: 103.25 [#/sec] (mean) # [menta (cgi)]: 123.22 [#/sec] (mean) # [nanoa (cgi)]: 84.84 [#/sec] (mean) # [yacafi-packed (cgi)]: 117.12 [#/sec] (mean) # [yacafi (cgi)]: 110.72 [#/sec] (mean) # [catalyst (modperl)]: 182.26 [#/sec] (mean) # [cgi-app (modperl)]: 809.98 [#/sec] (mean) # [cgi-object (modperl)]: 1194.43 [#/sec] (mean) # [cgi (modperl)]: 1571.60 [#/sec] (mean) # [http-engine (modperl)]: 766.97 [#/sec] (mean) # [nanoa (modperl)]: 857.54 [#/sec] (mean)
まぁ生のCGIとかは正直開発のコストパフォーマンスを考えるといくら速くてもありえないんだけど、ある程度の指標にはなるね。
ちなみにCatalyst/Mojoはまだちゃんと作ってない。明日には多分新しい数値がでてると思う。
これより下は、過去の計測結果。
[11/13 11:00] # [cgi-app (cgi)]: 26.04 [#/sec] (mean) # [cgi-object (cgi)]: 34.93 [#/sec] (mean) # [cgi (cgi)]: 170.54 [#/sec] (mean) # [http-engine (cgi)]: 3.04 [#/sec] (mean) # [menta-nocompile (cgi)]: 118.41 [#/sec] (mean) # [menta (cgi)]: 136.35 [#/sec] (mean) # [nanoa (cgi)]: 108.99 [#/sec] (mean) # [yacafi-packed (cgi)]: 142.30 [#/sec] (mean) # [yacafi (cgi)]: 137.94 [#/sec] (mean) # [catalyst (modperl)]: 223.71 [#/sec] (mean) # [cgi-app (modperl)]: 889.85 [#/sec] (mean) # [cgi-object (modperl)]: 1260.74 [#/sec] (mean) # [cgi (modperl)]: 1754.09 [#/sec] (mean) # [http-engine (modperl)]: 776.04 [#/sec] (mean) # [nanoa (modperl)]: 988.89 [#/sec] (mean)
[11/13 03:00] # [cgi-app (cgi)]: 28.01 [#/sec] (mean) # [cgi-object (cgi)]: 34.92 [#/sec] (mean) # [cgi (cgi)]: 167.27 [#/sec] (mean) # [http-engine (cgi)]: 2.83 [#/sec] (mean) # [menta (cgi)]: 51.60 [#/sec] (mean) # [menta (cgi)]: 60.31 [#/sec] (mean) # [nanoa (cgi)]: 99.48 [#/sec] (mean) # [yacafi (cgi)]: 125.62 [#/sec] (mean) # [catalyst (modperl)]: 148.17 [#/sec] (mean) # [cgi-app (modperl)]: 635.42 [#/sec] (mean) # [cgi-object (modperl)]: 1060.14 [#/sec] (mean) # [cgi (modperl)]: 1406.93 [#/sec] (mean) # [nanoa (modperl)]: 522.71 [#/sec] (mean) # [http-engine (modperl)]: 610.42 [#/sec] (mean)
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

charsbarさんに指摘されて、色々と問題点が発覚したのでテストコードをリファクタリングして、新しいバージョンをさきほどアップしました!
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

XML::LibXML 1.67から、使用頻度の高いXPathを適用するさいに前もってコンパイルできる方法が追加された(1.67は使えないので、1.68)。それがXML::LibXML::XPathExpression。同じXPathをコンパイルした場合、しなかった場合、それぞれの比較のベンチマーク結果は以下の通り:
Rate compiled regular compiled 27322/s -- -11% regular 30675/s 12% --
約一割か・・・微妙・・・。コードは以下の通り:
use strict; use Benchmark qw(cmpthese); use XML::LibXML 1.68; my $xml = XML::LibXML->new->parse_string(<<EOXML <foo> <bar>1</bar> <bar>2</bar> </foo> EOXML my $xpath = '/foo/bar'; my $compiled_xpath = XML::LibXML::XPathExpression->new($xpath); cmpthese(100_000, { compiled => sub { $xml->findnodes($xpath); }, regular => sub { $xml->findnodes($compiled_xpath); } });
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

XPathExpressionがサポートされてやった!と思っていたら、Mac OSX 10.5.5についてくるlibxml2 2.6.16だとコンパイル通らない。xmlTextReaderErrorFuncでsyntax errorとか、なんじゃこりゃ?という感じ。

一応修正を試みてみた。まず、なんかしらんけどSchema系のところでこけるので、とりあえずSchemaを無理矢理黙らす。多分、xmlSchemaSAXPlugPtrという型が2.6.16と2.6.21とかあたりの間で足されただけだとは思うんだけど、そもそもどこで使われてるか探し出すのが面倒なので、とりあえず、Schema全体をオフするために、63行目に

#undef LIBXML_SCHEMAS_ENABLED

を追加。次に、Reader系のところも黙らすために114行目から、118行目をifdefでさくっとくくる

#ifdef LIBXML_READER_SUPPORT
     /* error handling */
    xmlTextReaderErrorFunc errorFunc;    /* callback function */
    void                  *errorFuncArg; /* callback function user argument */
#endif

あと、もうひとつただのifdefの構文エラーがあるので、そいつを修正2262行目で、

int well_formed;

を追加。これでとりあえずコンパイルは通る。が、t/30xpathcontext.tが落ちる。ちょっと理由は不明。

とりあえず、このバージョンはちょっと使えなさそう〜。ちなみにRTで報告しようと思ったのだが、まだRTに1.67がなかった><

あとで報告する。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

さっそく全部のクラスをimmutableにしてみて、差を計測してみた
# With Immutable Classes timethis 10000: 32 wallclock secs (30.82 usr + 0.26 sys = 31.08 CPU) @ 321.75/s (n=10000) # Without Immutable Classes daisuke@beefcake-7 trunk$ perl benchmark.pl timethis 10000: 75 wallclock secs (73.32 usr + 0.51 sys = 73.83 CPU) @ 135.45/s (n=10000)
わお!immutableしただけでこれかよ!
コードはこれだけだよ
use strict; use Benchmark qw(timethis); use Bread::Board; timethis(10_000, sub { my $c = container 'MyApp' => as { service 'log_file_name' => "logfile.log"; service 'logger' => ( class => 'FileLogger', lifecycle => 'Singleton', dependencies => [ depends_on('log_file_name'), ] ); container 'Database' => as { service 'dsn' => "dbi:sqlite:dbname=my-app.db"; service 'username' => "user234"; service 'password' => "****"; service 'dbh' => ( block => sub { my $s = shift; DBI->connect( $s->param('dsn'), $s->param('username'), $s->param('password'), ) || die "Could not connect"; }, dependencies => wire_names(qw[dsn username password]) ); }; service 'application' => ( class => 'MyApplication', dependencies => { logger => depends_on('logger'), dbh => depends_on('Database/dbh'), } ); }; });
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

Bread::Boardに対してパッチ書いた。なんかルート要素に対しての操作が妙なことになってたのでIRCでStevanに相談して、動作確認してからごごご、っと。なんか忙しいと言ってたので取り込まれるのはいつになるやら。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

オブジェクト、特にビジネスロジックを書くときに、その結果をキャッシュに突っ込んでおくことがとにかく多い。

で、まぁそういう共通コードはベースクラスに入れておけばいいやって感じで書いてたんだけど、それももう5回目くらいになって最近もう本当にそれが面倒くさくなってきたので、丸ごとパッケージングした。それがMooseX::WithCache

話は単純で、要はcacheオブジェクトへのリファレンスをそれぞれのオブジェクトが持っていて、それに対するアクセスを簡単にするメソッドを生やしてやっているだけ。
package My::Thing; use Moose; use MooseX::WithCache; with_cache 'cache'; # デフォルトはCache::Memcached no Moose; no MooseX::WithCache; sub get_foo { my $self = shift; my $foo = $self->cache_get( 'foo' ); if (! $foo) { $foo = $self->get_foo_from_database(); $self->cache_set(foo => $foo); } return $foo; } #main.pl use My::Thing; my $thing = My::Thing->new( cache => Cache::Memcached->new({ servers => '127.0.0.1:11211' ]) ); my $foo = $thing->get_foo();
ってな感じで使う。
Mooseを使っているならhandlesでdelegationしてもいいようなものだけど、実はここでひとつキモがあって、ただそのままプロキシするんじゃなくて、キャッシュが本当に正しくあたっているのかとかを見るためにデバッグ機能をつけたり、復号キーをMD5形式にしたりと、意外とやることがおおいので、単純にhandlesでは処理できなかったのでこういう形になった。

先ほどCPANにdev releaseであげておいた。そのうちアップされるでしょう。コードはcodereposにもあるよ!
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

プログラミングは作文に近いと思うのです。プログラムを書いている時に築いているロジックというのは、「まずこのデータをこっちにおいて、その後これに3.14をかけて、2倍にして、それを標準出力にプリント」みたいな感じで、情緒はないだけであとはまるっきり作文しているのと一緒なわけです。

でもって、作文と一緒で色んな書き方で同じ事に到達できる。さっきのは円の円周を求める式だけど、答えを出すだけなら、こういう説明の仕方もできる:「一時変数に3.14を格納後2倍にし、一変数にさらに引数としてもってきたデータを乗算し、しかるのちに標準出力に出力する」。まぁこの程度のロジックならほぼどんな書き方をしても理解できるよね。

面倒くさくなるのはオブジェクトとかがでてきたり、プログラムの流れそのものがあんまりストレートじゃなくなってきた時。これがプログラミング言語が基本的に英語を基本としているということとも絡まって、なかなか上手に作文ができなくなってくる。

学校に言ってた頃の授業で、英語嫌いでした?

そうかもしれないね。でも、プログラマになっちゃったからしょうがないね。英語、最低限でいいから、覚えよう!そして自然な文章を書くようにロジックを組むことを心がけよう!

で、多分手続き型の処理を書くときはそれほど問題じゃないと思うのです。難しいのはオブジェクト。で、これが絶対だってわけでもないけど、俺が気をつけている「ここだけは要チェック」というチェックシートを書き出してみたいと思う:

1. オブジェクトは常に S - V - O

オブジェクトは常に主語(Subject)、メソッドが動詞(Verb)、そして引数が目的語(Object)としてとらえてみてください。公文式の国語をやっていた人には簡単だね!☆

2. 目的語が必然の場合も、動詞名に加える

例えば、オブジェクトの内部を変換(convert)するメソッドを書いていて、そのメソッドが変換するする対象は1種類しかないので、引数を求めるのは面倒だった、と仮定しよう。このメソッド、なんと命名しますか?例えば...
$object->convert;
これは悪い例。なぜかは、普通に文章として読んだ場合の事を考えてください。これだけだと「変換した」しかわからない。あなたが英語が得意じゃないとして、ただでさえ読みにくい英語ベースのプログラムを読み直すときに、これでバグを見つけられますか?

こういう場合もちゃんとメソッド名に書く!
$object->convert_to_floating_point();
ほら、これなら「浮動小数点に変換」ってわかるじゃない!

3. 助詞を抜かさない

2で書いた
$object->covert_to_floating_point();
ですが、これも助詞がなければなんのこっちゃわからなくなります。
$object->covert_floating_point(); # "to"を抜かした
これを日本語で書くと「浮動小数点変換」。でもそれだけだと助詞がないから「浮動小数点」と「変換」の関係性がわからない。

それって浮動小数点「から」?それとも「に」?それとも、これって、浮動小数点「を」使ってなにか変換する?

今まで見てきたソースコードの中で日本人が最もこの罠に陥りやすいと思う。 というか、ラテン語ベースの言語はみんな助詞がもっと明示的な物だと認識してると思うんだよね。日本語はそのあたりが曖昧。

というわけでどんなに自明に思えても、助詞込みで書くのがおすすめ
$object->convert_to_floating_point(); # 小数点「へ」 $object->convert_from_floating_point(); # 小数点「から」 $object->convert_with_floating_point(); # 小数点「で」



とりあえず時間がないのでこれまで!


    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr


File::Tempオブジェクトはファイルハンドルであり、同時にoverloadを使用すると文字列に見える、という妙なオブジェクトなんですね。なので、以下のコードは無用
my $expect = 'hogehoge'; my $out = File::Temp->new; diag $out; open my $fh, '>', $out; # ここ print $fh $expect; close $fh; my $contents < io $out; is $contents, $expect;

File::Temp->new()した段階ですでにFileHandleオブジェクトになっているはう。なのでこいつに直接書き込んじゃうのが吉
my $expect = 'hogehoge'; my $out = File::Temp->new; diag $out; print $out $expect; close $out; my $contents < io $out; is $contents, $expect;
で、undefined云々はどういうことかというと、File::Tempの問題というよりは受け手のIO::Allの問題。io $outってやると、$outはGLOBなので、ハンドルとして認識してしまうのです。でもclose $outしてるからハンドルはすでに使えないわけで・・・
それだけの問題なので、ここの直し方は簡単。$outを無理矢理文字列にしてしまえばOK
my $contents < io "$out"; is $contents, $expect;
そしてさらに、テンポラリファイルがたまってしまうなら、UNLINKオプションをつけちゃいましょう
my $out = File::Temp->new( UNLINK => 1);
このUNLINKオプションをつけておけば、File::Tempオブジェクトがスコープ外に達したときに自動的に一時ファイルが削除されます。closeしても平気だけど、スコープ外にでちゃうと操作できないのでそこだけ要注意
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

mstがcatalystについてのインタビューで次期CatalystでMooseだけじゃなくてIoC (Inversion of Control) とか (DI) Dependency Injectionとか使うような事を言っているよ!

IoCとかDIってJava界隈ではよく聞くけど、Perl界ではあまり聞かない。自分がちゃんと人に説明できるようにするついでにメモ:

まず、IoC はほぼDIと同じ物。なんか時々細かい差を強調する人もいるが、一般的には同じでOK。あと、この二つのコンセプトは別に新しくない。少なくともCatalystを使ったことのある人ならすぐ分かるはず。例えば、以下のよく見るコード:
my $model = $c->model('Hoge');
Catalystが先に'Hoge'というモデルを作成し、保存(Inject)しておいてくれたおかげで、上記のように我々はアプリケーション内のどこからでもHogeというモデルに名前だけアクセスすることができる。これがなかったらどうなるかというと、ただ単純に自分でそのモデルを作成することになる:
my $model = MyApp::Model::Hoge->new();
これはこれでいいけど、同じモデルを使い回したい場合にはあちこちで毎回同じような事をする必要がある。それだと「つか、このコンポーネントは一回作って、あとは使い回したいんだよ!」って時にいらいらしますね。

DIはそのようなアプリケーション内で使うコンポーネント及びその依存関係を別途先に作る事を指す。Catalystの場合は、MyApp->setup()段階でこのコンポーネント作成が行われ、アプリケーション内部ではその初期化等をほぼ気にすることなくすぐに使えるわけだ。だからCatalystにおいてはCatalystフレームワークそのものがDIの役目を担っている。

DIだけだと先に作るだけで、実際に使う時になんかパッケージ変数とかでその値にアクセスしなくてはいけなくなる。こんなイメージ:
package MyApp; our $MyApp::HOGE_MODEL = MyApp::Model::Hoge->new(); package MyApp::Whatever; sub foo { my $model = $MyApp::HOGE_MODEL; }
それは汚いし危険なので、コンポーネントの呼び出し用にService Locatorというのを使うわけです。Catalystだと$c->model()とかがそれに近いわけです。

これで一通りの仕組みができあがり。っていうか、こういう仕組み、わざわざ名前をつけないでも使ってる人は多いんじゃないか。この夏かかりっきりだったプロジェクトも実はこっそりそういう機能が入ってたしね。

ちなみにPerlだとBread::Boardとかがそれに使えそうです。

IOCだDIだ名前にとらわれすぎずに、より自分が楽できる方向に向けてアプリケーションを作ればいいと思う今日この頃でした。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

激遅なレスポンスなんだけど、dormandのmemcached記事。

dormando - Should you cache?

この中でキャッシュの使い方でよく見る間違った使い方を指摘している部分があって、ここがとても重要だと思うので書いておく

Where would you think to add caching to this system? I hope I've made it too obvious.
(システムのどこにキャッシュ機能を追加するべきだろう?)

At the query layer!
Use a database abstraction class and have it memcache resultset objects and...
No no no, that's a lie. I'm lying. Don't do that.
(DBクエリーのレイヤーだ!
DB抽象化クラスを使って、memcachedに結果のオブジェクトをキャッシュして・・・
いやいやいや、ごめん、今のは嘘。それはしないでくれ)


これ、自分を含めキャッシュを使い始めようとしている人たちがまず犯す間違いだと思うんだ。俺もやったよ。やっぱりやりたくなるよね。

Do it inside that $user object.
At the highest level possible. 
Take the whole object state and shovel it somewhere.
That object is its own biggest authority.
It knows when it's been updated, when it needs to load data, and when to write to the database.
It might've had to read from several tables or load dependent objects based on what you ask it to do.
(ユーザーオブジェクトのレベルでやるんだ。
最も外側のレベルでやるんだ。
オブジェクトの全体の状態をどかにキャッシュしてしまうんだ。
オブジェクト自身が自分の事を一番よくわかっている。
自分がいつ更新されたのか知っているし、いつデータが読み出されたのか知っているし、いつデータベースに書かれるべきなのか知っている。
ひとつのオブジェクトを作成するのに複数テーブルから読み込む必要があるかもしれないし、他のオ依存しているブジェクトを呼び出したりするかもしれないしね)


そういうことです。シンプルなシステムならいざ知らず、memcached を使うようなシステムではキャッシュ対象となるオブジェクトはDBのテーブル(もしくはそのオブジェクトの保存元)と1対1とは限らず、色々な依存関係が存在する。これをあまりmicro-managementすると、今度は逆にキャッシュ管理が複雑になりすぎてしまう。

例えば、$userオブジェクトが三つの部品でできていて、それぞれの部品をDBから読み込み、それぞれの部品のレイヤーでキャッシュしているとする。この場合$userオブジェクト本体はキャッシュする?したいよね。でもすると全体のオブジェクト$userがキャッシュされている間に部品Aがキャッシュから消されていたら?ん、どっちを取るの?いつ取るの?どうやってその状況をわかるの?とかとか。

それくらいだったら、細かい部品のキャッシュはとりあえず忘れて、$userというオブジェクト全体のレベルでキャッシュすればよいのだ。これなら、$user全体がキャッシュされているかどうかだけ。簡単だね!

    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

Mooseは「使いやすいオブジェクト定義ツール」じゃないよ。MooseはPerlにおける「オブジェクト指向の革命」ですよ。

http://dann.g.hatena.ne.jp/dann/20080814/p2
http://d.hatena.ne.jp/fbis/20080814/1218689670
http://d.hatena.ne.jp/a666666/20080813/1218628821
http://anond.hatelabo.jp/20080621124021

ぶっちゃけ"has"が使えるとか、勝手にnew()を作ってくれるとか、そういうことはどうでもいいのです。Mooseが革命児たる所以はオブジェクトの初期化・クリーンアップ・メソッドのコールチェインをPerlという言語において初めて(呼び出される順番とか、そういう意味で)を「正しく」実装しているのと、Roleという概念を実用化したことによって継承ツリーではなく、機能ベースの設計ができるようにしてくれ、我々が陥りがちなものすごく深い継承関係等を完全に抹消してくれるというところです。

それと、既存のシステムに加えられない、というのものナンセンスだ。Mooseは上記にもちょっと書いた深い継承関係とか、そういう実装的な依存のあるCatalystのようなシステムに直接放り込む事はできないが、シンプルな拡張等にはClass::Accessorベースのシステムでもシームレスに使える。

Class::MOPもどうでもいい。普通のユーザーは使わないもん。俺はライブラリを提供する側として今がりがり使おうとしているけど、それ以外使う必要はほとんどないよ。MOPはライブラリ提供者用の超便利機能だけど、ユーザーが意識する必要はない。

敢えて言おう。在る程度年期の入ったPerlユーザーでMooseを使えないツールだと言っている人はMooseの本質を取り違えている。Mooseをただのオブジェクト定義ツールと思っているから使えないのだ。Mooseはプログラミング・パラダイムです。

Mooseは今までPerlで定義できなかったクリーンなOOを可能にしてくれる。設計、実装、その他において、考え方がかわります。一度ちゃんと使い込むといいよ、ほんと。

ただ、みんな使え、っていうのも違う。本質を分かった上で使わない、という判断をしていただきたい、というところ。なんせパラダイムシフトしないといけないから。
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

http://search.cpan.org/~dmaki/Queue-Q4M-0.00012/

書き忘れてたけど、Queue::Q4M 0.00012で エンジンのステータス情報表示に対応した。このたび初めてまともにClass::MOPのcreate_anon_class()使った。これを使う事によって、将来的にステータスの出力が多少変わってもコードを返る必要はないはず。勝手にオブジェクトを規定してなおしてくれるのですよ!
    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr

このページのトップヘ