ぎゃー、日曜になってる。
著作権的に、ソースコードの引用しすぎかな、とも思ったけど
Perl best practicesの日本語版は、GoogleBookで読めるようだから、もーまんたいかな?
とにかく人間が頭に記憶していられる短期記憶はしょぼいので、読みにくいコード、長いコードは保守しにくい。
以下の節から対策案が紹介されている。
また参照テーブルは、スカラー定数である必要はなく、サブルーチンでもいい。
なので、以下のような、引数によって呼び出すサブルーチンを変えることもできる。
しかも記述しても無視されるだけなのが、また厄介。
nextなどは、ラベルのついたループを検索して反復するが、do-whileはループではない。
do-whileはポストフィックス(接尾辞)の変更されたdoブロック。
do-whileのように必ず1回だけは処理をして欲しい場合は、
処理の最後にフラグを立てて($flag=1;)、ループ条件に!$flagを入れておく。
もしくは、while(1)の無限ループの最後に「return ループ終了条件サブルーチン;」を入れる。
一つ一つでのデータが得られるごとに評価して、不適切なら、処理を抜けるようにしておくと、効率がいい。
if文のネストも深くならない。
コードも見にくくなるので、先ほどのデータチェックのとおり、その場で処理を抜けるようにする。
forを使用することで、固定回数ループであることを読み手に主張できる。
また、(Cスタイルでないforだと)カウント変数が不要になり、カウント変数の評価を間違えたり(<を<=にしたり)、カウント変数のインクリメントを忘れることがない。
固定回数ループといっても、例外は欲しい。この場合はredoを使うことで、反復回数を更新することなく、forの頭に処理を戻すことができる。
著作権的に、ソースコードの引用しすぎかな、とも思ったけど
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
;
利点は、- 代入文が一つ
- テーブルのように見える
- 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つ以上のとき
- 反復カウントがデクリメントされるとき
forを使用することで、固定回数ループであることを読み手に主張できる。
また、(Cスタイルでないforだと)カウント変数が不要になり、カウント変数の評価を間違えたり(<を<=にしたり)、カウント変数のインクリメントを忘れることがない。
固定回数ループといっても、例外は欲しい。この場合はredoを使うことで、反復回数を更新することなく、forの頭に処理を戻すことができる。
next,last,redoを使う場合はラベルを付ける。
- 途中で制御フローが変化することを知らせることができる
- コードが追加されたとき、バグが入りにくい。バグがでても見つけ易い
0 件のコメント:
コメントを投稿