甥っ子が生まれた
どうやら弟君はビールの飲みが足りなかったようである(違
それじゃひとつになっとりませんがな
本筋とは全然関係ないですが、danさんのコードでは出口がふたつですよね。
sub search_result{ my $q = shift; if ($q->param("query")){ my $uri = URI->new($WEBAPI_BASEURL); $uri->query_form( appid => $MYYDN_APPID, query => $q->param("query"), results => $MAX_RESULTS ); my $response = LWP::Simple::get($uri); if ($response){ my $xml = XML::Simple->new->XMLin($response, ForceArray=>['Result']); my @result = ($xml->{'totalResultsAvailable'}, "hits"); push @result, "<ol>"; for my $r ( @{ $xml->{'Result'} } ){ push @result, encode_utf8(sprintf qq(<li><a href="%s">%s</a></li>), $r->{'ClickUrl'}, $r->{'Title'}); } push @result, "</ol>"; return @result; # 出口 } } # 出口 }
ほんとの出口ひとつコードはこんなんですよ。
sub search_result{ my $q = shift; my @result = (); if ($q->param("query")){ my $uri = URI->new($WEBAPI_BASEURL); $uri->query_form( appid => $MYYDN_APPID, query => $q->param("query"), results => $MAX_RESULTS ); my $response = LWP::Simple::get($uri); if ($response){ my $xml = XML::Simple->new->XMLin($response, ForceArray=>['Result']); push @result, ($xml->{'totalResultsAvailable'}, "hits"); push @result, "<ol>"; for my $r ( @{ $xml->{'Result'} } ){ push @result, encode_utf8(sprintf qq(<li><a href="%s">%s</a></li>), $r->{'ClickUrl'}, $r->{'Title'}); } push @result, "</ol>"; } } return @result; # 出口 }
んで、
「出口は一つ」がベストというのはよく聞く言葉なのだけど根拠がよくわからない。
出口ひとつコードのひとつのキモは関数のブラックボックス化を徹底できることでしょう。Perl5の場合はコンテキストが柔軟すぎていまいちありがたみを感じられませんが、上の例ならsearch_resultの返り値はかならず配列だよというのがコードの中間読まなくても明確にわかる。if文ばかりだとインデントがうるさいということであれば
sub search_result{ my $q = shift; my @result = (); { last unless $q->param("query"); my $uri = URI->new($WEBAPI_BASEURL); $uri->query_form( appid => $MYYDN_APPID, query => $q->param("query"), results => $MAX_RESULTS ); my $response = LWP::Simple::get($uri) or last; my $xml = XML::Simple->new->XMLin($response, ForceArray=>['Result']); push @result, ($xml->{'totalResultsAvailable'}, "hits"); push @result, "<ol>"; for my $r ( @{ $xml->{'Result'} } ){ push @result, encode_utf8(sprintf qq(<li><a href="%s">%s</a></li>), $r->{'ClickUrl'}, $r->{'Title'}); } push @result, "</ol>"; } return @result; # 出口 }
なんて書いてもよいですが、とにかく中がどうであろうと関数としての返り値(の型)はケツ見りゃわかる。途中でreturn undef;なんてものが混じることは規約的にありえない――ということにしておけば関数の型宣言するのと同じ効果が得られますでしょ、と。
#もちろんこれがいまどきのbest practiceかというと???ですけどね。
出口ひとつコードのもうひとつのメリット?
いまどきのLLを使う分にはたいして意識する必要もないでしょうが、出口ひとつコードがよいと言われる理由にはもうひとつ、広い意味でのガーベジコレクションの問題もありますね。サブルーチン中でいきなりdieして怒られないのはPerlのコード(パッケージやら何やら)が暗黙のうちにBEGINやDESTROYを前後に配した出口ひとつコードになっているからであって、その仕組みがなかったり、仕掛けをしてガーベジコレクションの実行を遅らせているような環境では、どこかのブロックできちんと出口をひとつにして不要なものを消してやらないと、ゴミが残ったり、最悪帰り道がわからなくなって暴走したりする。
激しくダメな例ですが、たとえば古いCGIあたりに見つかりそうなこんなコード。
sub load_counter { my $lock = './lock'; my $counter; unless (-d $lock) { mkdir $lock, 0600; open IN, 'counter' or die; # ここも出口 $counter = <IN>; close IN; rmdir $lock; } return $counter; }
こんな実装でdieしてしまうとロックディレクトリが残ってえらい目にあうわけですが、ここでdieなんぞせんで、たとえば
sub load_counter { my $lock = './lock'; my $counter; unless (-d $lock) { mkdir $lock, 0600; if (open(IN, 'counter')) { $counter = <IN>; close IN; } rmdir $lock; } return $counter; }
とかなっていればもう少し関数の安全性やら独立性やらが高くなる。もちろんほんとはmkdirやrmdirの返り値も見るべきだし、そもそもそんな安全でない実装するよりおとなしくflockなりなんなり使った方がいい。もっと堅牢にしたければunlessのブロックをさらにevalでくくるとかなんとかする手もあるわけですが、その辺はまあ、よしなにしてくださいませ。
いいことづくめのように見えても、たとえば関数の隠蔽が過ぎると何かあったときにデバッグやユニットテストが大変だったりなんて側面もありますし、そもそもある程度高級な言語だからこそ出口ひとつなんてことができるわけで、アセンブラとか回路の一部分レベルで出口ひとつコードなんてありえんですしね。凝り固まっちゃいけませんや。
ネストが深くなると嘆く前に
goto文を使いましょう(笑
や、冗談抜きで、lastとかnextとか、言語によってはbreakだったりcontinueだったりしますが、この手の実質goto文をいかにうまく使うかってもコードの可読性を高めるポイントのひとつなんでないかなと。
ふたつ下の例でもif文のネストを減らすのにブロックとlastを使っていますが、Basic(On Error GoTo ...)がいみじくも明示しているように、エラー/例外処理ってのはどうやったってどこかへ飛ばないと始まらない。や、if文の条件処理だって同じですやね。内部的にはelseやelsifのところまで飛んでいるはずですよ。
fbisさんがいわゆるswitch文をif 〜 elsif 〜 else 〜で実装してますが、こんなのも
sub func { my ($param) = @_; my $ret; # 戻り値用の変数 SWITCH: { if (!@_) { $ret = undef; last; } if (!defined $param) { $ret = 0; last; } if ($param !~ /^\d+$/) { $ret = 0; last; } if ($param > 10000) { $ret = 0; last; } if ($param == 0) { $ret = 1; last; } if ($param % 2 == 0) { $ret = 2; last; } if ($param % 2 == 1) { $ret = 3; last; } } return $ret; }
とでもすれば出口ひとつで済む話。
もちろんいちいちlastせんで直接returnしたっていいし、ifを後置できるPerl5的にはその方がきれいだと思いますが、短い区間のgotoはもっと積極的に使っていいと思うし、gotoの距離が長くなりすぎるようなら途中をサブルーチン化(抽象化)することを考えた方がいいかもしらんのじゃないかな、と。
ま、これもまたTMTOWTDIということで。