2010年9月7日火曜日

Perl best practices[Perlベストプラクティス] 9章 サブルーチン


このエントリーをはてなブックマークに追加


サブルーチンは括弧付き、かつ&無しで呼び出す



  • サブルーチンは現在の名前空間で定義されている場合は括弧無しで呼び出せる。

    • これは覚えておくと良い。裸の文字列がサブルーチンとして認識されうるということ!

    • 括弧がないと、組み込み関数と区別しにくくなる

    • &はビット演算子でもあるので、構文によっては紛らわしいことになる。

    • サブルーチンが引数を取らない場合も括弧をつける。そうすれば型ではないことがはっきりする。obj()->update($status);





組み込み関数と同じ名前を使用しない


 サブルーチンと組み込み関数の名前が衝突すると、どちらが優先されるかは、その組み込み関数による。


常に@_を展開する



  • $_[1],$_[2]は何を表しているのか謎

  • @_はエイリアスなので、レキシカル変数にコピーしておくことで、元の変数をうっかり変更しない

  • 1行のリストで展開するのが良いが、例外として、一つ以上の引数を検証する必要がある場合や、インラインコメントを付ける場合は、shiftを使用する。

  • 引数の検証にサブルーチンを使う場合は、3章で紹介したように内部処理専用のサブルーチンを表す名前をつける。(頭にアンダーバー)

    • エラーの送出は、検証用のサブルーチンを呼び出した側の名前空間になるようにすること。(どこで呼び出されてエラーになったのかが知りたい情報)

    • 頻繁に呼び出す場合は、効率のために検証をサブルーチンではなく、インライン展開する。





引数が3つ以上ある場合は、ハッシュを利用する


 ハッシュで変数名をキーとして、サブルーチンの引数に渡すと、引数の順番を気にしなくてよくなる。
$line = padded({ text=>$line, cols=>20, centered=>1, filler=>$SPACE });
 これを、{}を省略して、ハッシュではなく、名前と値のペアとして渡すと、コンパイル時にエラーが出なくなるので、注意。
 通常使用する名前のない引数と、今回の名前付き引数を混在させると、オプション用の引数を明確に分離することができる。



$line = padded( $line, {cols=>20, centerd=>1, filler=>$SPACE} );

sub @added {
my ($text, $arg_ref) = @_;
#以下略
}




引数が欠けているかの検証は、引数の数や、定義されているかで判断する



  • 論理演算子を用いた検証は一般に誤り

    • 引数の文字が'0'であったり、テキストが空文字だった場合、例外が送出される。



  • 引数が定義されているかで評価

    • if any {!defined $_} $text, $cols, $filler;



  • undefが引数として渡される場合

    • definedが使えないので、引数の数が予めわかっている場合は数をチェックする



  • 引数の一部がオプションの場合

    • オプション引数がハッシュであると、オプションがいくらあっても引数の数としては、1個であるため、チェックがし易い。オプションが完全に省略されていた場合は、カウントされない。





引数のデフォルト値



  • ||や||=を使用しない。

  • 初期化のコードと、実際に値を使用するコードは分離すること。

  • デフォルト値の数が多い場合はテーブルを利用する。



Readonly my %PAD_DEFAULTS => (
cols => 78,
cnterd => 0,
filler => $SPACE,
);

sub padded {
my ($text, $arg_ref) = @_;

my %arg = ref $arg_ref eq 'HASH' ? (%PAD_DEFAULTS, %{arg_ref})
;
#略



 このコードのミソは、(%PAD_DEFAULTS, %{arg_ref}) にあって、%PAD_DEFAULTSが%{arg_ref}の前にあるため、まずはデフォルト値で%argが初期化され、次に%{arg_ref}で上書きされる。(ハッシュなのでキーアクセスだから)
 このため、arg_refで指定されなかった値にはすでにデフォルト値がセットされる。


スカラーをreturnするときは、常にreturn scalarを使用する。



  • returnのコンテキストは呼び出し側のコンテキストで決まる


 例えば、以下のように、要素数が知りたいために、スカラーコンテキストでhow_many_defined()を呼び出した際に、誰かが、以下のようにした場合、リストコンテキストとなり、リストの最初の要素が返されてしまう。



my ($found) = how_many_defined(@raw_samples);



戻り値はユーザがマニュアルを読む前に予想するものにする


 リストなどと異なり、スカラーは複数の種類(BOOLや数、文字列、参照など)が存在する。この複数ある種類の中から、1つだけを返すことが理想となる。このため、ユーザがサブルーチン名等を見たときに、どんな値がreturnされるのをユーザが期待しているかを考える必要がある。
 一人で考えるのは限界があるが、まずは、サブルーチンのカテゴリーをもとに考える。カテゴリーは同種(Cでいう配列)、異種(Cでいう構造体)、反復(readline関数など)。



  • 同種

    • 同種は各要素が同じ重要度であり、スカラーコンテキストで要求された場合、通常、要素数が要求されているはず。



  • 異種

    • 重要度が高い要素が含まれていることが多く、スカラーコンテキストではその要素が返されることが期待されているはず。だが、すべて重要だと期待されることもある。(localtimeやgettimeなど)



  • 反復

    • readlineなどに代表されるように、スカラーコンテキストでは、1回の反復の結果を返すことが要求されているはず。





ユーザが期待する戻り値を特定できない場合は、複数のreturnを準備する


Contextual::Returnを使用する


use Contextual::Return;

sub get_server_status {
my ($server_ID) = @_;

my %server_data
= _ascertain_server_status($server_ID);

return (
LIST { @server_data{ qw( name uptime load users )}; }
BOOL { $server_data{uptime} > 0; }
NUM { $server_data{load}; }
STR { "$server_data{name}: $server_data{uptime}"; }
HASHREF { \%server_data; }
);
}



 これほど多くの選択肢を準備する必要がないとしても、Contextual::Returnを使用することで、コードがすっきりするし、今後追加する際の保守性も向上する。wantarrayなどを使用すると、ifの分岐でコードが散乱するが、このモジュールだと、return文の中で表のように記述できるので、各コンテキスト間での違いを際立たせることができる。


サブルーチンプロトタイプを使用しない(一般に誤解を与える)


 sub clip_to_range($$@){....}の($$@)は、一つ目の引数がスカラーであることを要求しているように見えるがそうではない。実際は一つ目の引数がスカラーコンテキストで評価されることをperlに要求している。
つまり、clip_to_range(@range,@samples)のような呼び出しをした場合、サブルーチン側で@rangeを元に、2つのスカラー値に分割されたり、スカラーではないことを理由にエラーになるのではなく、配列の要素数が得られることになる。
 また、この場合($$@)の@には引数が与えられないことになるが、これもコンパイルエラーにはならない。「空のリスト」もリストだから。
 このように、プロトタイプを使用してもコードの堅牢性は大して向上しない。


常に明示的なreturnを使用する


 とにかくreturnは最後につけること。そうしなければ、「最後に評価された式」の結果が帰ってくるが、それはperlコンパイラが密かに変換した式で行われたかもしれない。そうなると、帰り値を元に自分で書いたコードを読んでも理解できない。


returnは自動で呼び出し元のコンテキストに応じたfalseを返すので余計な指定はしない


 return undef;などと指定すると失敗する可能性がある。それは呼び出し元がリストコンテキストで呼び出された場合。この場合、リストの要素としてundefが格納される。もし次に、if文でスカラーコンテキストで評価されるとしたら、リストの要素数は1なので、常にifの評価はtrueになる。





0 件のコメント:

コメントを投稿