2010年9月4日土曜日

Perl best practices[Perlベストプラクティス] 6章 制御構造 6.16~6.22


このエントリーをはてなブックマークに追加
ぎゃー、日曜になってる。
著作権的に、ソースコードの引用しすぎかな、とも思ったけど
Perl best practicesの日本語版は、GoogleBookで読めるようだから、もーまんたいかな?

ネストしまくりのif文を使用しない

コードが読みにくいし、実行コストも高い。
とにかく人間が頭に記憶していられる短期記憶はしょぼいので、読みにくいコード、長いコードは保守しにくい。
以下の節から対策案が紹介されている。

値の交換

ある値を何かに変換する作業、例えば、一から十を1から10に変換するなど、は大量のif-elseの条件文の中に正規表現を書くのではなく、参照テーブルを用いる。

my %num_for = {
    'zero'  => 0,  '零' => 0,
    'one'   => 1,  '一' => 1,  'une'      => 1,
    'two'   => 2,  '二' => 2,  'deux'    => 2,
    'three' => 3,  '三' => 3,  'trois'    => 3,
    'four'   => 4,  '四' => 4,  'quatre' => 4,
    'five'    => 5,  '五' => 5,  'cing'     => 5,
);

sub words_to_num {
    my ($words) = @_;

    my @words = split /\s+/, words;

    my $num = $EMPTY_STR;
    for my $word (@words) {
        my $digit = $num_for{lc $word};
        if (defined $digit) {
            $num .= $digit;
        }
    }

    return $num;
}

print words_to_num('one zero eight');
変更時も参照テーブルを変更するだけで、関数には手をつけなくていい。
また参照テーブルは、スカラー定数である必要はなく、サブルーチンでもいい。
なので、以下のような、引数によって呼び出すサブルーチンを変えることもできる。

package Debugging;
use Crap;
use Log::Stdlog { level => 'debug' };

my %debug_mode = {#匿名サブルーチンのテーブル
    off   => sub{},
    logged => sub{ return print {*STDLOG}  debug =>  @_; },
    loud   => sub{                 carp   'DEBUG: ', @_; },
    fatal  => sub{                croak   'DEBUG: ', @_; },
};

sub import{
    my $package = shift;
    my $mode = @_ > 0 ? shift : 'loud';

    my $debugger = $debug_mode{$mode};#ここでテーブルから匿名サブルーチン選択

    use Sub::Installer;
    caller()->reinstall_sub(debug => $debugger);   #呼び出し元のPackageName(名前空間)でdebugと言う名前のサブルーチンで、中身は$debuggerに入ってる匿名サブルーチン

    #reinstall_subは以下のコードと等価
    #    do {
    #        no warnings 'redefine';
    #        no strict 'refs'; 
    #        *{$PackageName.'::'.$subname} = $sub_ref;
    #    }

    return;
}
これで、デバッグの種類が増えても、テーブルに追加するだけで、対応できる。

表形式の3項演算子


my $A = $B ? $BB
              :  $C ? $CC
              :  $D ? $DD
              ;
利点は、
  1. 代入文が一つ
  2. テーブルのように見える
  3. else文が絶対に必要
これは便利。他の言語でも使えますね。

do-whileを使用しない

do-whileループではnext,last,redoは使用できない。
しかも記述しても無視されるだけなのが、また厄介。
nextなどは、ラベルのついたループを検索して反復するが、do-whileはループではない。
do-whileはポストフィックス(接尾辞)の変更されたdoブロック。

do-whileのように必ず1回だけは処理をして欲しい場合は、
処理の最後にフラグを立てて($flag=1;)、ループ条件に!$flagを入れておく。
もしくは、while(1)の無限ループの最後に「return ループ終了条件サブルーチン;」を入れる。

データのチェックは早い段階で行う

すべてのデータが揃ってから、if文などで評価するのではなく、
一つ一つでのデータが得られるごとに評価して、不適切なら、処理を抜けるようにしておくと、効率がいい。
if文のネストも深くならない。

制御を一元化するためにループ構造をねじ曲げない

returnする箇所は1ヶ所にまとめるべきだという主張があるが、これは結局は複数箇所にフラグを用意したり、処理を中断する処理が随所に散らばっているため、returnを1ヶ所にすると、制御が一元化されて、ミスしにくいというのは錯覚。
コードも見にくくなるので、先ほどのデータチェックのとおり、その場で処理を抜けるようにする。


#return1箇所ver
while (   !eof
  && $tries < $MAX_TRIES
  && ( $int !~ $INTEGER || $int < $MIN_BIG_INT )
) {
    print 'Enter a big integer: ';
    $int = <>;
    if (defined $inf ) {
        chomp $int;

        if ($int eq $EMPTY_STR) {
            $int = 0;
            $tries--;
        }
    }
    else {
        $eof = 1;
    }
    tries++;
}


#ひとつずつ評価ver
INPUT:
for my $attempt (1..$MAX_TRIES) {
    print 'Enter a big integer: ';
    $int = <>;

    last INPUT if not defined $int;
    redo INPUT if $int eq "\n";
    next INPUT if $int !~ $INTEGER;

    chomp $int;
    last INPUT if $int >= $MIN_BIG_INT;
}

whileとforの使い分け

  • for
    • 固定回数ループのとき
    • 反復カウントや、条件式が1つのとき
    • 反復カウントがインクリメントのとき
  • while
    • 不規則にカウントされるもの
    • 反復カウント変数や、条件式が2つ以上のとき
    • 反復カウントがデクリメントされるとき
while文の使い方については、P109も読み直すと分かりやすい。

forを使用することで、固定回数ループであることを読み手に主張できる。
また、(Cスタイルでないforだと)カウント変数が不要になり、カウント変数の評価を間違えたり(<を<=にしたり)、カウント変数のインクリメントを忘れることがない。

固定回数ループといっても、例外は欲しい。この場合はredoを使うことで、反復回数を更新することなく、forの頭に処理を戻すことができる。

next,last,redoを使う場合はラベルを付ける。

  1. 途中で制御フローが変化することを知らせることができる
  2. コードが追加されたとき、バグが入りにくい。バグがでても見つけ易い

0 件のコメント:

コメントを投稿