if, elsif, elsif, ... なコードをどう書き直せばわかりやすいか悩む修行

WEB から持ってきた文字列を標準化するコードを書いている。いろいろなスタイルで書かれた文字列を「使えるかたち」に揃えるコード、とも言える。きれいに書くのが難しい。ある正規表現にマッチするかどうか調べ、マッチしたらそこから情報を切り出し、マッチしなかったらまた別の正規表現にマッチするか調べ、というのを繰り返す。普通に書くと if, elsif, elsif ... が延々と続くコードになる。それだとやっかいな点がいくつかある。

いずれも 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 モジュールはありそうだなあ。