本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはmixiの広木大地さんで、
仕事やOSS
なお、
コードはコミュニケーションツール
「コミュニケーション能力」
コードの命名
コードの意図を正しく伝えるためには、
- パッケージ名/
クラス名/ メソッド名 - DBカラム名/
テーブル名 - サービスのURI
- グローバル/
レキシカル変数名
このように見ると予約語、
不明瞭な名前、マジックナンバーを避ける
適切に名付けられていないコードは意図が読み取りづらいものになりがちです。
sub calculate {
my ( $ctgry,$t ) = @_;
if( $ctgry == 1 ) {
return $t * hourly($ctgry) * ( 1 + 0.23);
} else {
return $t * hourly($ctgry);
}
}
上記のコードはロジックとしてはとてもシンプルでわかりやすいのですが、
- 1や0.
23など裸の数字が何を意味しているかわからない - 引数がどのようなものかよくわからない
- 関数名が一般的過ぎて何をしたいのかよくわからない
1点目のようなプログラム中の裸の数字は
上記のコードを文脈が明らかになるように書き換えてみましょう。
use constant WHITE_WORKER => 1;
use constant BLUE_WORKER => 2;
use constant INCREASING_RATE => 1 + 0.23;
sub get_wages {
my ( $user_category,$working_hours ) = @_;
if( $user_category == WHITE_WORKER ) {
return $working_hours *
wages_an_hour($user_category) *
INCREASING_RATE;
} else {
return $working_hours *
wages_on_hour($user_category);
}
}
このようにすると、
バグを誘発しやすい変数名
関数スコープ内のレキシカル変数も重要な名前です。この名前の付け方一つで、
my $item_1; my $item_2; my $item_3;
上記のように連番で定義された変数は、
my @items;
my ($head,$body,$tail);
ほかにも、
my $item;
my %item;
my @item;
それぞれ異なるシジル
次のように、
my $item;
my %name_to_item;
my @items;
my @item_list;
ぱっと良い名前が思いつかないときに先のような変数名を付けてしまいがちですが、
コメントとドキュメント
コメントやドキュメントも重要なコミュニケーション手段です。
悪いコメント
よく見かける悪いコメントは次のようなものです。
# ここはこんな感じにしておく
# 絶対に変更しないコト!!
上記のように事情や意味がよくわからない文章を書いてはいけません。
# parsed_queryをsetupする
$parsed_query->setup;
上記のように自明なコメントは、
# 終了したのでコメントアウト
# call_some_func(@array_of_arguments);
# TODO : あとで実装する
上記のようにコメントアウトやTODOをメモするのではなく、
# deprecated : 廃止予定
上記のようにコメントで伝えるのではなく、
表明による予防的コーディング
次のようなコメントを目にすることがあります。
# $minは0<= * <= 59の間
my $min = shift;
こういった入力や出力が満たすべき性質についてのコメントは、
my $min = shift;
assert 0 <= $min && $min <=60;
Carp::Assertなどを用いて上記のように記述しておけば、
コードをコメントで分割しない
機能追加や仕様変更が多くなると、
sub add_items {
my ($self,$items) = @_;
# ここは○○をしている
:
# ここは○○をしている
:
if( ! *** && ! ***) {
# ○○の場合
:
} else {
# ○○が出ない場合
:
}
}
上記ではコメントの記述で機能を分割しています。このようなコメントを記述するのではなく、
コメントとドキュメントの違い
Perlでは、
- コメントは、
内部実装の理解のために必要な情報 - ドキュメントは、
ライブラリ利用者のために必要な情報
コメントの役割は補助的なものです。できるだけ、
ドキュメントには、
コメントやドキュメントを記述しても、
関数とオブジェクト
オブジェクト指向や関数の切り出し、
論理式を明快にする
コード上に分岐条件として現れる論理式は、
if( !A && !B ) {
:
}
上記のような論理条件があった場合、
unless(A or B ) {
:
}
条件が複雑過ぎる場合は、
if( is_enable($a) ) {
:
}
ポイントは、
また、
if ( $self->has_extra_space ) {
:
}
上記は次のように書き換えることができます。
return unless $self->has_extra_space;
このように条件に適応しない場合にすぐに戻り値を返したり、
破壊的代入を避ける
次の関数のように、
sub hoge {
my $x = shift;
:
$x = _get_fuga();
:
for my $item ( @list ) {
$x.= _get_moga( $item );
}
my $y;
if( is_enable($x) ) {
$y = "hello";
} else {
$y = "world";
}
}
こういった処理は代入ごとに意味合いが変わっていることが多いので、
sub hoge {
my $query_string = shift;
my $query_object = _parse_query( $query );
my $api_request =
join '/',
map{$_->key.'='.$_->value}
@{$query_object->params};
return ( is_enable( $mogaed_array) ) ?
'hello':
'world';
}
このように変数への代入をできるだけ一度に制約することで、
副作用と状態
関数の一つ一つを丁寧に記述しても、
# 副作用のない関数
sub add {
my ( $a , $b ) = @_;
return $a + $b;
}
# 引数に含まれない入力の例
sub div_with_env {
my ( $a , $b ) = @_;
return ( $a / $b ) unless ( $ENV{USE_RATIONAL} );
return Rational->new( $a , $b );
}
# 引数に含まれない入出力を持つ関数
my $register = 0;
sub add {
my ( $a , $b ) = @_;
my $b ||= $register;
return $register = $a+b;
}
こういった隠れた入出力を持つ関数は、
オブジェクトとカプセル化
副作用・
package RegisterCalc;
sub new {
my ( $class,%option ) = @_;
return bless { register => 0} ,$class;
}
sub set_register {}
sub get_register {}
sub add {}
sub div {}
のように、
求めよ、聞くな
オブジェクト指向では、
たとえば次のコードを見てみましょう。
if( $user->type == User::OFFICIAL ) {
return get_entries_for_official($user);
}
if( $user->type == User::FRIEND ) {
return get_entries_friend($user);
}
if( $user->type == User::OTHER ) {
return get_entries_other($user);
}
この場合、
if( $user->is_official );
if( $user->is_friend );
if( $user->is_other );
さらに、
$user->get_entries;
のように処理が隠ぺいされている状態がより良いと言えます。
このようなデータと処理の関係に関する経験的な教訓として、
オブジェクトが持つ状態とそれに関連した処理を