2006年04月22日 15:00 [Edit]

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

というわけで、突然はじまりました勝手に添削のコーナー。

今回は、WEB+DB PRESS Vol.32の「Yahoo! Web サービス活用ガイド」から。


私もWEB+DB Pressへの連載をはじめたので、同誌のますますの(反映|繁栄)を祈ってやまないのだけど、それだけに、同誌にこういうサンプルコードがあるのは気になる。一応きちんと動くので、blogとかのentryであればこれでもよいのだけど、この手の雑誌はかなり長い間保管され、読者に何度も参照されることを考えれば、「その後」のことを考えて推敲しておく方がいいだろう。Damianも言っていたように、「ソースコードは未来の自分へのラブレター」なのだ。ましてや、雑誌に乗る場合は、読者に向けたラブレターでもあるのだから。

Vol. 32 pp. 94
 1:
 2:
 3:
 4:
 5:
 6:
 7:
 8:
 9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
#!/usr/local/bin/perl -w
use strict;
use CGI;
use LWP::Simple;
use XML::Simple;

use constant WEBAPI_BASEURL => 'http://api.search.yahoo.co.jp/WebSearchService/V1/webSearch?';
use constant MYYDN_APPID => 'YahooDemo';
use constant MAX_RESULTS => 10;

my $q = new CGI;
print $q->header(-charset => 'utf8'), $q->start_html();
print qq(<form><input type="text" name="query"><input type="submit" value="search"></form>);

if ($q->param("query")){
    (my $key = $q->param("query")) =~ s/(\W)/"%" . unpack("H2", $1)/ge;
     #リクエストURIの生成
     my $req_url = WEBAPI_BASEURL;
     $req_url   .= "appid=YahooDemo";
     $req_url   .= "&query=".$key;
     $req_url   .= "&results=".MAX_RESULTS;
     
     #APIへリクエストを送信
     my $yahoo_response = get($req_url);

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

     print $yahoo_xml->{'totalResultsAvailable'} . "hits";
     print "<ol>";
     foreach my $result (@{$yahoo_xml->{'Result'}}){
         print qq(<li><a href="$result->{'ClickUrl'}">$result->{'Title'}</a></li>);
     }
     print "</ol>";
}

print $q->end_html(), "\n";

一目見て、Perlが嫌いになりそうなソースである。

私がrefactorしたらこうなった。

 1:
 2:
 3:
 4:
 5:
 6:
 7:
 8:
 9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
#!/usr/local/bin/perl -T
use strict;
use warnings;
use Readonly;
use URI;
use CGI qw/:standard/;
use LWP::Simple qw/get/;
use XML::Simple;
use Encode qw/encode_utf8/; # to drop utf8 flag from XML::Simple

Readonly my $WEBAPI_BASEURL
    => 'http://api.search.yahoo.co.jp/WebSearchService/V1/webSearch';
Readonly my $MYYDN_APPID => 'YahooDemo';
Readonly my $MAX_RESULTS => 10;

print 
    header(-charset => 'UTF-8'),
    start_html(-lang=> 'ja',  -title=>$ENV{SCRIPT_NAME}, -encoding=>'UTF-8'),
    start_form(), textfield("query"), submit(-value=>"search"), end_form(),
    search_result(),
    end_html();

sub search_result{
    return unless param("query");
    my $uri = URI->new($WEBAPI_BASEURL);
    $uri->query_form( appid => $MYYDN_APPID,
                      query => param("query"),
                      results => $MAX_RESULTS );
    my $response = get($uri) or return;
    my $xml = XML::Simple->new->XMLin($response, ForceArray=>['Result']);
    return 
        $xml->{'totalResultsAvailable'},  "hits",
        ol(map {
            encode_utf8 li(a({href=>$_->{'ClickUrl'}}, $_->{'Title'}))
            } @{ $xml->{'Result'} } );
}

それでは、解説を進めよう。手元にPerl Best PracticesPerlプログラミング救命病棟があればなおいい。この二冊は、Perlスクリプトを書いてお金をもらっているひとは必ず手元においておくべきだろう。

まずは定数の扱いから。オリジナルではuse constantを使っていたが、これは駄目である。use constantの「定数」は、実は定数を返すsubなので、sigil ($@%&*)が定数から消えてしまう。何よりqq()でインターポレート出来ないのが痛い。実際19行目では結局定数ではなく文字列リテラルをそのまま使っており、まるで意味なしになっている。悪い例としてこれほど適切なのも珍しい。

Damian先生のお薦めは、Readonlyモジュールを代わりに使う事だ。残念ながら5.8現在ではcoreに入っていないが、5.10では入る公算が大だ。今から使って損はないだろう。それもいやなら、いっそ定数は使わないで済ませた方がましである。私は定数はあまり使わないが、それで困った事はほとんどない。どうしてもというときは明示的に

sub DEBUG { 1 } # DEBUG を定数として使う

としている。

逆にqq()の濫用の例もある。32行目では、$result->{'ClickUrl'}をqq()の中に入れているが、tagと相まって見づらいったらありゃしない(この点からも、Perl 6の->からの.への移行は歓迎)。こういう場合は

printf qq(<li><a href="%s">%s</a></li>), $result->{'ClickUrl'}, $result->{'Title'};

とすれば、ずっと見やすくなるではないか。perlにはqq()だけではなくsprintf()もprintf()もあるということを覚えておこう。もちろん文字列連結演算子としての.もあるし(Perl 6では~)、Here Textもある。これらをどう使い分けるかが腕の見せ所だが、第一優先は「一番読みやすくなる書き方」だと思う。

次にロジック。オリジナルではHTMLの出力の途中でAPIにアクセスし、XMLをパースしてその結果を出力しているが、単に見た目が汚いだけではなく、それだけWeb ServerとCGI Scriptの通信時間が長くなってパフォーマンス的にも悪い。加工処理と出力処理は分けるのが賢明だ。

次に、"en passant"。オリジナル16行目だ。

こういうことはJAPHスクリプトの中だけでやっていただきたい。「私はいかにもPerlを知っています」というようでかっこいいように感じるかもしれないが、それは自意識過剰というものだ。書くなら

# my ($var = $oldvar) =~ s/$regexp/$replacement/ は駄目!
my $var = $oldvar;
$var =~ s/$regexp/$replacement/;

とすべきである。しかもこの場合、後述のように、この部分は実はURIモジュールを使う事により不要になってしまうのである。

LWPが入っていれば、一緒にURIモジュールが付いてくるのだが、これを使わないというのは不思議である。オリジナルの18-21行目でやっていることは、弾版では26-28行目に登場するが、どちらがわかりやすいだろうか?

わかりにくそうな処理があったら、まずそれをやるモジュールを探してみる。あればそれを使う。なければモジュールにしてCPANに上げる。これがBest Practiceである。

そして、CGIモジュールの活用。$q->param()だけが芸じゃない。折角HTMLの展開にも使っているのであれば、もっともっと活用しよう。

いつ$q->param()を使って、いつuse CGI qw/:standard/を使うかというのは、実はnon-trivialな質問なのだけど、このようにCGIスクリプトの中だけで文字列生成が完結する場合には、use CGI qw/:standard/などを使って余計な$q->を省き、テンプレートなどを使って処理が別になっている場合は、$qを使って処理を渡すというのがきれいかつわかりやすい切り分けだと思う。今回は前者なので、そのようにしてみたらこんなにコードがすっきりした。

ちなみに、このサンプルコードのオリジナルの作者は、Yahooの中の人々である。みなさんは「Yahooでもこれくらい」と安心しただろうか、それとも「もしかしてプロダクションコードもこんな感じ!?」と不安になっただろうか。

#近藤さん、ちゃんと仕事しましょう

Dan the Contributing Writer Thereof


この記事へのトラックバックURL

この記事へのトラックバック
404 Blog Not Found:perl -勝手に添削 - WEB DB Press Vol.32 pp.94
404 Blog Not Found:perl -勝手に添削 - WEB DB Press Vol.32 pp.94【】at 2012年01月19日 18:45
いまいちベストなやり方がよくわかりません。 私は定数はあまり使わないが、それで困...
[Perl]定数を使う【DQN起業日記】at 2008年12月24日 05:06
自分用の備忘録。 Perlのソースコードの書き方のお勉強に。 一連の議論はこの順番に読むとよい。 404 Blog Not Found:perl - 勝手に添削 - WEB DB Press Vol.32 pp.94無精で短気で傲慢なプログラマ | これ、読みやすいの?404 Blog Not Found:perl - There\'s more than on...
Perlの書き方のお勉強に【ブログが続かないわけ】at 2006年04月24日 22:35
というわけで、好評につき続けます、勝手に添削のコーナー。 今回はハテナオヤ作、Hatena::API::Auth 0.02です。
perl - 勝手に添削 - Hatena::API::Auth【404 Blog Not Found】at 2006年04月24日 21:28
404 Blog Not Found:perl - 勝手に添削 - WEB DB Press Vol.32 pp.94 一連の議論は見ていて面白かった。で、眺めていて一番面白かったコードはnaoyaさんのですね。おお!そこまでやっちゃうのか!って感じで。 で私もここでコードを…なんてことは、『Perl Best Practices』....
CGI.pm のここが気に入らない【理事のひとりごと】at 2006年04月24日 12:53
perl - 勝手に添削 - WEB+DB Press Vol.32 pp.94 について。 わたしなんかよりよっぽど perl を知っている人なのだろうから機能的な 点についてはコメントしないが、はたしてこの添削後のコー
これ、読みやすいの?【無精で短気で傲慢なプログラマ】at 2006年04月23日 02:26
というわけで、突然はじまりました勝手に添削のコーナー。 WEB+DB PRESS 今回は、WEB+DB PRESS Vol.32の「Yahoo! Web サービス活用ガイド」から。
perl - 勝手に添削 - WEB+DB Press Vol.32 pp.94【404 Blog Not Found】at 2006年04月23日 02:14
んー。リアル。エレナ、この画像を見つけたときに、あまりにもの出来だったから、自分のと見比べちゃったよ(笑)
はいこれ。【蓬田エレナ】at 2006年04月22日 23:45
この記事へのコメント
ma2さん、
ありがとうございます。追補しました。
これからも読者の声を迅速に繁栄し、ますます反映することを誓います:)
Dan the Typo Generator
Posted by at 2006年04月25日 11:21
ほんと些細なことですが
ますますの反映 → ますますの繁栄
でしょうか。
Posted by ma2 at 2006年04月25日 11:15
numbさん、
あ、本当だ。これがないと
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
が送出されちゃうんですよね。
#これ、$q->encoding('UTF-8')とかで包括指定できるようにした方がいいかも。
#でも、CGI.pm自体は伏魔殿だからなあ(Perl 5.8対応でpatchを送った時に通読したけど)。
Dan the Man with Too Many Encodings to Handle
Posted by at 2006年04月23日 02:07
18行目に
-encoding => 'UTF-8',
を加えた方が良いのでは?
Posted by numb at 2006年04月23日 01:52