fc2ブログ

無精で短気で傲慢なプログラマ

UNIX や web やプログラムの技術的なことを中心に。

続・これ、読みやすいの?

perl - There's more than one best practice
> この点ははっきり言おう。こういう形でreturnできるのはperlの美点だと。

途中で return できるのは別に perl 独自の機能ではないですよね。「perl
なら許される」ということであれば、ちょっと理解できない。どんな言語で
あろうと、基本は出口はひとつがベストと考えます。ただし実際にプログラム
を組むと、例外処理・エラー処理が必要になるので、なかなかそうもいかない。
ではどういう return なら容認するかというと、わたしの場合は
   sub func {
      return if $debug;
      ...
   }

という関数先頭付近で値のチェックをして return するタイプ。あるいは
関数の途中であっても、
   sub func {
      ...
      $ret = subfunc1();
      return if ( ! $ret );

      $ret = subfunc2();
      return if ( ! $ret );

      $ret = subfunc3();
      return if ( ! $ret );
      ...
   }
といったように、後から読んだ人に
  「この関数内ではこういう戻り方をする」
  「だから新たなチェックをする場合は、この書き方を真似しろよ。
   if とか使うなよ」
とアピールしているなら OK。小飼さんのソースからは、書き手の意思やメッセージが
見えない (しいて言うなら「俺は return にはこだわらない」かな?)。書き手の意思を
こめられない場合は、if 文を使うべきだと思います。

で、この場合は return 云々以前に、そもそも関数の切り出し方がイマイチなのでは
ないでしょうか。search_result 関数は

 - Yahoo! にリクエストを投げてレスポンスを取得
 - レスポンスを解析
 - 取り出したデータに OL・LI 要素を付加して返す

という順序凝縮になっており、「この関数は何をしてるの?」という問いに対して、
「~です」とスパッと答えることができない。だから結果的に return に無理が出てくる。

最初に見たときは「OL 要素というオブジェクトを返す関数」かなと思ったのですが、
よく見ると

 ('x hits', '<ol>', '<li>...</li>', '<li>...</li>', '</ol>')

と先頭にヒット数がついた配列を返しているので、ちょっと違う。

> そして、今回は関数化はもう一つの重要な利点を帯びている。
> それは、関数search_result()が、このサンプルコードの「ホット
> スポット」だからだ。読者が注意して読むべきなのは、そこだけ
> なのだ。あとは定数の設定、モジュールの宣言、結果の出力(print)
> といった「日常茶飯事」であり、読み飛ばしてかまわないところなのだ。

なるほど、そういう視点はありませんでした。しかし、ホットスポットと
いいつつも query が指定されていなければ return したり、HTML 生成を
行っていたり、いまいちホットスポットにしきれていない。トータルで
読みやすくなっているのか? というと、ちょっと疑問です。

結局のところ、仮にホットスポット化によってわかりやすくなるとしても、
return がいまいち、関数の切り出し方がいまいち、という結果になるなら、
関数化しないという選択肢も決して間違いではないでしょう。

なお、仕事でやるなら
   $response = get_response()
   ($hit, @data) = parse_response($response)
と機能凝縮で分けます。しかし今回のソース短さでは引数・戻り値の受け渡しが
煩雑になるだけで、関数に切り出すメリットがありません。


で、わたしが一番気になるのは、雑誌の読者がこのソースを理解できるの?
ということです。まぁ、はてなユーザが相手なら問題ないのかもしれませんが、
たとえばラクダ本の存在すら知らなくて、CGI&Perlポケットリファレンス
で全部済ませてしまう人も多いわけです。

そういう人たちに map・URI.pm・CGI.pm をふんだんに盛り込んだソース
を与えても、意味がわからんと投げ出す人・ぱっと見て読もうともしない人が
少なからずいるような気がします。そうなると、Yahoo! API の使い方を伝える
という本来の目的が達成できない。

特に
   return
      $xml->{'totalResultsAvailable'}, "hits",
      ol(map {
         encode_utf8 li(a({href=>$_->{'ClickUrl'}}, $_->{'Title'}))
         } @{ $xml->{'Result'} } );

を読んで、foreach ( @{ $xml->{'Result'} } ) でまわす書き方に書き
換えられない人は結構多いんじゃないでしょうか。

「そういう読者はターゲットにしていない」というのもひとつの考え方ですが、
あまりにもったいないなぁと。

最初のソースで解説しておいて、より美しいソースはコレだぜ! という展開
なら納得できますが、最初からリファクタリング後のソースを出すのはどう
なんでしょう。小飼さんのソースで編集からの OK が出ますか? (わたしは
雑誌執筆経験がないので、純粋な疑問です)

> それにしても惜しい。「無精で短気で傲慢なプログラマ」さんは、こうした
> 議論を進めるにあたって、サンプルコードを用意すべきであった。私のそれは
> きちんと動作検証までしてある(もちろんAPPIDは私のに変えてある)。少々
> 傲慢さが足りないのではないだろうか。

blog のタイトルで「俺って優秀だぜ」と自慢するのもおこがましいので当初は、
 「無精で短気で傲慢なプログラマを目指して」
としようかと思ったのですが、長かったので削ったのです。なので無精な
わけではないのです :-)

で、わたしの場合は、元のソースの方がわかりやすいと思いました。ただし
わたしなりの色を出すとすれば以下のようにします。おもしろみのないソースで
すみません。

[狙いと言い訳]
 - 目的は「Yahoo! API の使い方の解説」を読者に伝えること。
 - 読者を選ばないため、できる限りモジュールを使わない。
 - import せず LWP::Simple::get() と書くことで、モジュールを知らない読者が
  混乱しないよう配慮。
 - デリファレンス結果を一度配列 @results に格納し、リファレンスをわかって
  いない読者に配慮。
 - Model と View の分離をちょっとだけ主張してみる。
 - Model から View のデータ受け渡しの @click_urls と @titles は非常にかっこ
  悪いが、Class::Struct を使ったり、クラスをうほどでもないので、ここは妥協。
  (多分なにかしら適当なモジュールがあると思いますが、知らないので)
 - LWP::Simple::get に失敗した場合は致命的エラーなので die。
  fatalsToBrowser で画面に表示。改造時の利便性を考慮。
 - parse_args は、インタフェース仕様書を兼ねている。プログラム内で必要な
  入力はここで全部用意しろよ、途中で直接環境変数を参照したりするなよ、
  サニタイズするならここでやれよ、という意図を込めたつもり。
 - 一度も動かしてません。構文チェックもしてません。申し訳ない。

#!/usr/local/bin/perl
use strict;
use warnings;
use LWP::Simple;
use XML::Simple;
use Encode;
use CGI::Carp qw(fatalsToBrowser);

my $webapi_baseurl = 'http://api.search.yahoo.co.jp/WebSearchService/V1/webSearch';
my $myydn_appid = '..';
my $max_results = 10;

my $query = parse_args($ENV{'QUERY_STRING'});

my $hit_num;
my @click_urls;
my @titles;

if ( defined $query ){
    # リクエストURIの生成
    my $req_url = "$webapi_baseurl?appid=$myydn_appid&query=$key&results=$max_results";

    # APIへリクエストを送信
    my $yahoo_response = LWP::Simple::get($req_url) or die "$!";

    # 取得したXMLをパースする
    my $xmlsimple = XML::Simple->new();
    my $yahoo_xml = $xmlsimple->XMLin($yahoo_response, ForceArray=>['Result']);

    # 件数取得
    $hit_num = $yahoo_xml->{'totalResultsAvailable'};

    # URL とタイトルを取得
    my @results = @{$yahoo_xml->{'Result'}};
    foreach my $result (@results){
       push(@click_urls, $result->{'ClickUrl'});
       push(@titles, $result->{'Title'});
    }
}

print "Content-type: text/html; charset=utf8\n";
print "\n";
print "<html><body>\n";
print "<form><input type='text' name='query'><input type='submit' value='search'></form>\n";

if ( defined $query ){
   print "$hit_num hits\n";
    
   print "<ol>\n";
   for ( my $i=0 ; $i<@click_urls ; $i++ ){
      printf("<li><a href='%s'></a></li>\n",
             $click_urls[$i],
             Encode::encode_utf8($titles[$i]));
   }
   print "</ol>\n";
}
print "</body>\n";
print "</html>\n";

exit 0;

#--------
sub parse_args {
    my ($query_string) = @_;
    my $l_query = undef;
    foreach ( split('&', $query_string) ){
        my ($key, $value) = split('=', $_);
        $value =~ tr/+/ /;
        $value =~ s/(\W)/"%" . unpack("H2", $1)/ge;
        if ( $key eq 'query' ){
           $l_query = $value;
        }
    }
    return $l_query;   
}

スポンサーサイト



PageTop

これ、読みやすいの?

perl - 勝手に添削 - WEB+DB Press Vol.32 pp.94

について。

わたしなんかよりよっぽど perl を知っている人なのだろうから機能的な
点についてはコメントしないが、はたしてこの添削後のコードはきれいなのか?

気になるのは、
   sub search_result{
      return unless param("query");
      ...
      my $response = get($uri) or return;
      ...
と途中で節操なく return しているところ。

上記関数の引数がなく、$q がグローバル変数化しているところ。

最後の
   return
      $xml->{'totalResultsAvailable'}, "hits",
      ol(map {
            encode_utf8 li(a({href=>$_->{'ClickUrl'}}, $_->{'Title'}))
         } @{ $xml->{'Result'} } );
のごちゃごちゃ感はどうなんだろう? 元ソースの
   (my $key = $q->param("query")) =~ s/(\W)/"%" . unpack("H2", $1)/ge;
がダメで、こっちはいいのか?

わたしが CGI.pm 嫌いなので、こういうソースを見慣れていないだけ?

そしてちょっとショックだったのは、はてブ でわたしと同じ感想を持った人が
いないこと。世の中の流れってそうなの? わたしがズレてるわけ?


あと、元ネタの記事は読んでないけど、解説が目的の場合は読者の脳内
スタックをなるべく消費しない、つまり過度に関数化しない方がよいと思う。
まぁこれは人それぞれだろうけど。


2006/4/23 2:29 追記。

わかってない奴だなぁと言われて傷つくのが嫌だったので (軟弱者)、こっそり
リンクしていたのだが、即座にバレてしまったので後付けでトラックバックを打ちました。

2006/4/24 1:19 追記。

引用コードのインデントが違うとの ご指摘 が。修正しました。fc2 の使い方が
よくわからず、手で &nbsp; に置き換えたときのミスです。申し訳ない。
PageTop

正規表現における文字クラス内のドット

今日 30分はまったこと。

Perl の正規表現で、ドットの部分を改行コードにもマッチしてほしくて
 m/[.\n]/
とし書いたが、全く意図どおりに動かなかった。

なぜなら文字クラス [~] の外にあるドットは「改行以外の任意の一文字」という意味だが、文字クラスの中にあるドットは ただのドットだから (Perl に限らず、あらゆる正規表現にあてはまる)。

よって、

if ( 'abc' =~ m/[.]/ ){
print "OK!\n";
}

はマッチしない。
PageTop

Perl の「裸のブロック」と next

今日 Perl で はまったこと。
foreach (1..5){
   {
     if ( $_ == 3 ){ next; }
   }
   print "$_\\n";
}

の結果は、1,2,4,5, ではなく 1,2,3,4,5 となる。

ラクダ本曰く
 「ブロックは、意味的には、1回だけ実行されるループと等価」
とのこと。知らなかった。

わたしは変数のスコープを狭くするために
{
   my $tmpbuf = &func($hoge);
   $tmpbuf =~ s/,//g;
   if ( $tmpbuf eq '' ){
     $flag = 1;
   }
}

などと裸のブロック (bare block) を多用するので、この仕様は嫌だなぁ。

ちなみに似たようなはまりやすい点として、perl では do { ... } while; 内での next・last・redo は効かない、というのがある。

ついでに言うと、C においては continue・break は裸のブロックに対して作用しない。
main(){
   int i;
   for (i=1 ; i<=5 ; i++ ){
     {
       if ( i==3 ){ continue; }
     }
     printf("%d\\n", i);
   }
}

の結果は 1,2,4,5 となる。
PageTop