読者です 読者をやめる 読者になる 読者になる

Best Practice of Subroutine Design

perl

大げさなタイトルだけれども、最近の思うところを。

引数に関して

ウィッシュリストを返すルーチンwishを作ったとする。

package Foo;

sub wish {
    my ($person, @wish_list) = @_;
    return "$person wants " . join(',', @wish_list) . ".\n";
}
1;

これに対し

use Foo;

my $wish = '';
$wish = Foo::wish('Kotaro', 'wii', 'PS3');
print $wish;

というふうに呼べば

Kotaro wants wii,PS3.

を得られる。

さて、ここで最後に!!!マークを付ける/付けないのオプションが必要ということなったら?
呼び出しが1箇所しかなければ、引数形式を変えればいいが…。

このルーチンの最大の間違いは@wish_listで受けていること。これだと一気に拡張性がなくなる。配列リファレンスであれば、後ろにオプションを追加することもできた。



低レベルレイヤでは引数変更したくなるケースはそれほどないかもしれないが、アプリケーションレイヤになると要求変更・追加は不可避だと思ったほうがよい。であれば、柔軟に受けられるようにハッシュないしハッシュリファレンスがベター。


wishサブルーチンを書き直してみる。

sub wish {
    my $ref_hash = shift;
    my $person    = $ref_hash->{person};
    my @wish_list = @{$ref_hash->{wish_list}};
    my $emphasis  =  $ref_hash->{emphasis};

    my $wish = "$person wants " . join(',', @wish_list);
    $wish .= "!!!!!!!!!!!" if ($emphasis);
    $wish .= ".\n";
    return $wish;
}

呼び出し側は少々冗長になるがこんな感じ。

my $$wish = Foo::wish({
    person    => 'Kotaro',
    wish_list => ['Wii', 'PS3'],
});
print $wish;

面倒ではあるが、引数として何を渡すか明確なので見通しがいい。

まとめ
  • リストをそのまま受けるのはNG。拡張性担保するためリファレンスにすること
  • さらなる拡張性確保のためにはハッシュ/ハッシュリファレンスで受けるとよい
戻り値に関して
  • 何かをチェックしてその結果を詳細と一緒に返したい
  • 条件満たさなかったら処理をそこで停止してreturnしない

というケースではどういう戻り値にすべきか悩むところである。

dieして呼び出し側でevalするのは1つの手。
ただ、本来はクリティカルなケースでのみdieすべき。

今のところは以下の書き方が自分の中でのBest Practice。

package Foo;

sub check {
    my $flg = shift;

    # check something...
    my $status = ($flg eq 'ok') ? 1 : 0;

    # make error response
    my $res = {};
    unless ($status) {
        $res = { err_msg => 'Error happens.' };
    }

    return ($status, $res);
}

呼び出す側は

my ($status, $res);
($status, $res) = Foo::check('ok');
print "ok: $res->{err_msg}\n" if not ($status);

($status, $res) = Foo::check('ng');
print "ng: $res->{err_msg}\n" if not ($status);
まとめ
  • 途中で処理を停止するようなルーチン書く時には return ($status, $res);形式にしておくとよいかも
  • $statusはscalar, $resはhash ref