if, elsif, elsif, ... なコードをどう書き直せばわかりやすいか悩む修行
WEB から持ってきた文字列を標準化するコードを書いている。いろいろなスタイルで書かれた文字列を「使えるかたち」に揃えるコード、とも言える。きれいに書くのが難しい。ある正規表現にマッチするかどうか調べ、マッチしたらそこから情報を切り出し、マッチしなかったらまた別の正規表現にマッチするか調べ、というのを繰り返す。普通に書くと if, elsif, elsif ... が延々と続くコードになる。それだとやっかいな点がいくつかある。
- 下の方の elsif でエラーがあっても if のある位置で発生したと報告されるときがある
- 条件節内のスコープが共通なので、自由に my 宣言ができない
- if ステートメントが百行とか続くのが気持ち悪い。落ち着かない。
いずれも if が長く続くことが問題になっている。
とりあえず、元々の if, elsif を含む部分を関数にして、各条件にマッチしたら情報の切り出し後、即 return するように書き換えてみた。
use strict; use warnings; my $s = 'test'; if (my $y = $s =~ qr{(a)}) { print "a\n"; } elsif (my $y = $s =~ qr{(b)}) { # line 9 print "b\n"; } else { print "else\n"; } sub p { if (my $y = $s =~ qr{(a)}) { print "a\n"; return; } if (my $y = $s =~ qr{(b)}) { print "b\n"; return; } print "else\n"; return; } p;
sub p の前が書換え前のコード、sub p 以降が書換え後のコード。実行結果。
$ perl d.pl "my" variable $y masks earlier declaration in same scope at d.pl line 9. else else
いや、awk みたいに「パターン」とそれにマッチしたときに動かす「アクション」を登録していく仕組みのほうがいいな。
こんなんになった。
use strict; use warnings; my @rules = (); sub register { my ($reg, $action) = @_; push @rules, [$reg, $action]; } sub resolve { my ($s) = @_; foreach my $rule_ref (@rules) { my ($reg, $action) = @{$rule_ref}; if (my @args = $s =~ $reg) { return $action->(@args); } } } if (1) { # ... (*) register( qr{\A (\S+) \s* (are) \s* (.+) \z}xms, sub { my ($n, $v, $p) = @_; return "are/$n/$p"; } ); register( qr{\A (\S+) \s* (is) \s* (.+) \z}xms, sub { my ($n, $v, $p) = @_; return "is/$n/$p"; } ); register( qr{\A (.*) \z}xms, sub { my $x = shift; return "else($x)\n"; } ); } else { @rules = ( [qr{\A (\S+) \s* (are) \s* (.+) \z}xms, sub { my ($n, $v, $p) = @_; return "are/$n/$p"; }], [qr{\A (\S+) \s* (is) \s* (.+) \z}xms, sub { my ($n, $v, $p) = @_; return "is/$n/$p"; }], [qr{\A (.*) \z}xms, sub { my $x = shift; return "else($x)\n"; }], ); } my $s = 'this is a test.'; my $result = resolve($s); print "$result\n";
(*) の箇所が 1 の場合 (そのままのとき)、ルール (パターンとアクションの対) を register 関数で登録する形。0 にするとルールをあらかじめ列挙しておく形。コンパイルにかかる時間は前者の方が速そうだけれど、トータルでは後者の方が速そう。見やすさ、保守のしやすさは ... どっちも変わらないか。
こんなのを実現する CPAN モジュールはありそうだなあ。