kwsktr's study log

kwsktr のおべんきょログ

コーディングスタイルだけは一人前を目指す試み :: (3)

今日も『Perlベストプラクティス』でおべんきょ。


4章 値と式

4.1 文字列デリミタ

文字列展開デリミタは、実際に展開する文字列にのみ使用する

# 1つ以上の変数が確実に展開されるようにしたい場合には、文字列を二重引用符(")で囲む
my $spam_name = "$title $first_name $surname";
my $pay_rate  = "$minimal for maximal work";

# 変数を展開するつもりがない場合には、文字列を単一引用符(')で囲む
my $spam_name = 'Dr Lawrence Mwalle';
my $pay_rate  = '$minimal for maximal work';

# 展開しない文字列にリテラルの単一引用符が含まれている場合には、代わりにq{...}で囲む。
my $spam_name = q{Dr Lawrence ('Larry') Mwalle};
my $pay_rate  = q{'$minimal' for maximal work};

# 展開しない文字列にリテラルの単一引用符および対になっていないかっこが
# 含まれている場合には、デリミタとして角かっこ([])を使用する。
my $spam_name = q[Dr Lawrence }Larry{ Mwalle];
my $pay_rate  = q['$minimal' for warrior's work{{:-)];

# 関連する文字列値を幾つか作成している場合には、必要なデリミタの中から
# 最も汎用的なものを選択して、すべての文字列に使用する

my $title         =  q[Perl Best Practices];
my $publisher     =  q[O'Reilly];
my $end_of_block  =  q[}];
my $closing_delim =  q['}];
my $citation      = qq[$title ($publisher)];
4.2 空の文字列

空の文字列に""または''を使用しない

$error_msg = q{};  # からの文字列

'' と " を見間違える場合があるので、q{} と コメントで強調する。

4.3 1文字で構成される文字列

1文字で構成される文字列を見た目に紛らわしい方法で書かない

$separator = q{ };  # スペース1つ

#リテラルのタブは書かず、展開形式の\tを使用する
$separator = "\t";

# ',',は解読が困難なので、以下のようにする
my $printable_list = '{' . join(q{,}, @list . ')';
4.4 エスケープ文字

数値のエスケープではなく、名前付きの文字エスケープを使用する

# use charnames を使う
use charnames qw( :full );

$escape_seq = "\N{DELETE}\N{ACKNOWLEDGE}\N{CANCEL}Z";
4.5 定数

名前付きの定数を使用し、use constant を使用しない

素のリテラルは、数字の意味を説明する名前がついた、読み取り専用のレキシカル変数(CAPN の Readonly モジュールを使う)と置き換える。

print $count * 42;  # 42がどういう数字か解らないので良くない

use Readonly;
Readonly my $MOLYBDENUM_ATOMIC_NUMBER => 42;  # 明確になる

print $count * $MOLYBDENUM_ATOMIC_NUMBER;


Readonly my $$EMPTY_STR = q{};  # 空の文字列を扱う場合にも有効
4.6 先頭のゼロ

10進数の先頭に0をつけない

先頭のゼロはPerlでは意味を持つ => 8進数と見なされる。

4.7 長い数字

長い数字はアンダースコアを使って読みやすくする

$US_GDP = 10_990_000_000_000;
4.8 複数行の文字列

複数行の文字列は、複数の行にレイアウトする

$usage = "Usage: $0 <file> [-full]\n"
         . "(Use -full option for full dump)\n"
         ;

文字列に改行文字が埋め込まれているが、文字列全体がソースコードの1行に収まらない、という場合には、文字列を改行のあとで区切り、それぞれを連結する。

4.9 ヒアドキュメント

複数行の文字列が2行を超える場合には、ヒアドキュメントを使用する

$usage =<<"END_USAGE";
Usage: $0 <file> [-full] [-o] [-beans]
Options:
    -full  : produce a full dump
    -o     : dump is octal
    -beans : source is Java
END_USAGE
4.10 ヒアドキュメントのインデント

ヒアドキュメントによってインデントが崩れる場合は、ゼアドキュメントを使用する

■定義済みの変数を使用

use Readonly;
Readonly my $USAGE => <<'END_USAGE';
Usage: qdump file [-full] [-o] [-beans]
Options:
    -full  : produce a full dump
    -o     : dump is octal
    -beans : source is Java
END_USAGE

# そのあとのコード

if ($usage_error) {
    warn $USAGE;
}

■サブルーチン(ゼアドキュメント)を使用

sub build_usage {
    my ($prog_name, $file_name) = @_;
    
    return <<'END_USAGE';
Usage: $prog_name $file_name [-full] [-o] [-beans]
Options:
    -full  : produce a full dump
    -o     : dump is octal
    -beans : source is Java
END_USAGE
}

# そのあとのコード

if ($usage_error) {
    warn build_usage($PROGRAM_NAME, $requested_file);
}
4.11 ヒアドキュメントのターミネータ

ヒアドキュメントのターミネータには、
標準のプレフィックスが付いた大文字の識別子を1つ使用する

print <<'END_LIST';
get name
set size
put next
END_LIST
  • プレフィックスには'END_…'が推奨される。
  • << 'END_LIST' は『END_LISTまでのすべて』と考えると良い。
4.12 ヒアドキュメントの引用符

ヒアドキュメントを挿入する際には、ターミネータを引用符で囲む

ラベルを単一引用符(')で囲むと、ヒアドキュメントは変数を展開しなくなる。
ラベルを二重引用符(")で囲むと、ヒアドキュメントは変数を展開する。

4.13 裸のワード

裸のワードは使用しない

use strict しておけば、検出してくれる。

4.14 ファットコンマ

ファットコンマ(=>)はペアに使用する

%default_service_recode = (
    name   => '<unknown>',
    rank   => 'Recruit',
    serial => undef,
    unit   => ['Training platoon'],
    duty   => ['Basic training'],
);

キーと値のペアや、名前と値のペアのリストを作成する場合には、常に「ファットコンマ」を使用して、キーを該当する値に結びつける。
ハッシュエントリ、名前付きの引数、その他の名前と値の組にのみ使用したほうが良いだろう。

4.15 シンコンマ

文の順序付けにコンマを使用しない

# 2分検索
SEARCH:
for (do{$min=0; $max=$#samples; $found_target=0;}; $min<=$max;) {
    # 2分探索の実装
}

複数の文を1つの文として扱う必要がある場合は、doブロックを使う。
あるいは、式全体を一連の文に書き換える方法を考える。

($min, $max, $found_target) = (0, $#samples, 0);

SEARCH:
while ($min<=$max;) {
    # 2分探索の実装
}
4.16 優先度の低い演算子

優先度の高いブールと優先度の低いブールを混在させない

論理和の「or」と「||」、論理否定の「not $hoge」と「!$hoge」などは、優先度が異なるため、混在させると正しい振る舞いが解りにくい。
「and」「not」は使わないように。

4.17 リスト

リストはすべてかっこで囲む

コンマセパレータの優先度を適切なレベルまで引き上げる。

4.18 リストのメンバーシップ

文字列リストのメンバーシップの評価にはテーブル参照を使用し、
その他のリストのメンバーシップには any() を使用する

List::MoreUtilsモジュールのany()関数は、grepと同様、コードブロックと値りのリストを受け取り、コードブロックをそれらの値に順番に適用し、それらを$_として渡す。
異なる点は、値のいずれかが評価ブロックにパスした時点で、trueの値を返す。
いずれの値もtrueと評価されない場合には、falseを返す。

リストのメンバーシップ評価で eq を使用する場合には、any() を使用してはならない。
このような場合には、参照テーブルを使用するほうが効果的。

Readonly my %IS_EXIT_WORD
    = map { ($_ => 1) } qw (
            q quit bye exit stop done last finish aurevoir
    );

# そのあとのコード

if ( $IS_EXIT_WORD{$cmd} ) {
    abort_run();
}