Perl Hackers Hub

第52回Perlで堅牢な開発―構文チェック、静的検査、型制約(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは小林謙太さんで、テーマは「Perlで堅牢な開発」です。

堅牢な開発とは、異常時への対処や予防を十分行いつつ進める開発手法を指します。堅牢な開発を実現するためには設計手法を含めさまざまな取り組みが考えられますが、今回はPerlの構文チェックや型制約を活用する方法について述べます。

本稿は、Perl 5.28.0を用いて説明していきます。本稿のサンプルコードは、本誌「WEB+DB PRESS Vol.107」サポートサイトから入手できます。

Perlに約束を入れて、異常を予防する

異常が起きてから対処するより、そもそもの予防をしていくことが、影響を拡大させないために肝心です。そのために本節では、構文チェックについて書きます。

checkオプションで構文チェックを行う

Perlで書いたプログラムの構文が正しいかを確認するには、perl -c foo.plのように-c(check)オプションを付けてプログラムを実行します。perldoc perlrunに書いてあるとおり、このオプションはPerlの構文チェックだけでなく、BEGINUNITCHECKCHECKブロックのコードを実行します。たとえば次のコードでperl -cした場合、BEGINブロックの中が実行されてHELLOと標準出力して、pprintの記述が構文エラーであると指摘します。こういったブロックにアプリケーションロジックを記述することは、意図せぬ動作を招くため注意が必要です。

BEGIN {
  print "HELLO\n" # print HELLO
}
pprint "WORLD\n" # error! NOT print

一方、このように構文チェック以外にも処理が実行できるしくみを利用して、Perlインタプリタに構文チェックのやり方を指示できれば、プログラム全体の実行前により多くの問題に気付けます。

プラグマを利用し、より厳しい構文チェックを行う

Perlインタプリタにヒントを与えるためのしくみとして、$^{WARNING_BITS}やヒントハッシュと呼ばれる$^H%^Hといった組込みの変数があり、これを参照し構文チェック時や実行時の振る舞いを変更できます。

BEGIN {
    $^H{"main/myrand"} = rand;
}

sub hello {
    # caller
    my $hinthash = (caller(0))[10];
    if ($hinthash->{"main/myrand"} < 0.5) {
        print "HELLO";
    }
}

hello; # HELLOをランダムに出力

このコードでは、BEGINブロックで抽選した値に応じ、関数の挙動を変更しています。こういった変数は今後挙動が変わる可能性があるため、直接触ることは望ましくありません。実際には多くの場合、このしくみを利用したプラグマと呼ばれるモジュールを利用します。

まずは標準プラグマのstrictプラグマとwarningsプラグマを紹介します。これらのプラグマは本連載第7回「新人さんのための仕事で使えるPerl基礎知識」でも紹介されています。

strictプラグマで、安全でない構文を制限する

strictプラグマを使わなかった場合、次のコードのように変数$varの値を"var"という文字列だけで参照できます。柔軟ですが、意図せぬ挙動を招くので、strictプラグマを利用し、致命的エラーにすると良いでしょう[1]⁠。

our $var = "foo";
print ${"var"}; # 文字列 "var" のデリファレンス
# => foo

{
    use strict;
    print ${"var"}
    # => 致命的なエラー:
    # Can't use string ("var") as a SCALAR ref while "stri
ct refs" in use
    # at src/3_strict_refs_example.pl line 7.
}

strictプラグマがほかに制限できる構文について、詳しくはperldoc strictを参照してください。

warinigsプラグマで、警告ではなく致命的なエラーを選択する

warningsプラグマも、strictプラグマと同様に意図せぬ挙動になりがちなPerlの構文を制限します。たとえば、次のコードは文字列と数字の足し算をしたときに警告を出します。

use warnings;
my $total = "foo" + 123;
print $total;
# =>
# Argument "foo" isn't numeric in addition (+)
# at bar.pl line 2.
123

しかし、これは警告でよいのでしょうか。不確かなデータのまま、コードの実行が続くことで問題は肥大化します。warningsプラグマは、警告を致命的なエラーへと昇格する機能も備えています。具体的には、FATALオプションにどの警告カテゴリを昇格させるかを指定します。

use warnings FATAL => 'numeric';
my $total = "foo" + 123;
print $total;
# =>
# Argument "foo" isn't numeric in addition (+)
# at bar.pl line 2.

このコードでは、FATALオプションに警告カテゴリのnumericを指定しています。これにより、文字列と数字の足し算で致命的エラーとなり、それ以降のコードは実行されていないことがわかります。ほかに、FATALオプションにallを指定することで、すべての警告カテゴリを致命的エラーに昇格させることもできます。

表1に、選択できる主な警告カテゴリを紹介します。詳しくはperldoc perldiagを参照してください。

表1 主な警告カテゴリ
警告のカテゴリ内容
numeric文字列が、数値が必要な演算子の引数として与えられた
experimentalsignaturesといったPerlの実験的な機能の使用
deprecatedPerlで非推奨となった機能の使用
once一意な変数名がtypo(誤字)の可能性がある
redefineサブルーチンを再定義したとき
execsystem()exec()などで、指定されたプログラムが実行できなかったときなど
recursion無限再帰の可能性がある
newlineファイル名に改行文字が含まれる可能性がある
portable32ビットで表現できる数値を超えている

stricturesプラグマで、バランスの良い約束を入れる

構文チェックを厳しくするためにwarnings FATAL =>'all'を行うのも手ですが、現実的には、一部警告のままのほうが都合が良いです。たとえば、警告カテゴリのrecursionは無限再帰の可能性を示しているだけで、逆に意図せぬエラーを生み出すかもしれません。できる限り警告を致命的エラーに昇格し、取捨選択を行ってくれるプラグマがstricturesです。strictures2.000005を用いて説明します。

use strictures 2; # バージョン2を指定

上記は、次のコードと等価になります。

use strict;
use warnings FATAL => 'all';
use warnings NONFATAL => qw(
  exec
  recursion
  internal
  malloc
  newline
  experimental
  deprecated
  portable
);
no warnings 'once';

strictures 2を指定して、さらに、PERL_STRICTURES_EXTRA環境変数も有効にすると、次のようなプラグマも有効になります。

no indirect 'fatal'; ――(1)
no multidimensional; ――(2)
no bareword::filehandles; ――(3)

(1)new Fooといった間接オブジェクト記法を禁止し、(2)$hash{1,2}といったキーに配列を利用した呼び出しを禁止し、(3)<FH>といったBarewordによるファイルハンドルの扱いを禁止します。

構文チェックを一部外す方法

未然に問題に気付くために構文チェックは厳しいほうがよいと思いますが、一部、例外として処理したいケースもあると思います。Perlの場合、ブロックで手軽に構文チェックを緩めることができます。

use warnings FATAL => 'numeric';
{
    no warnings 'numeric';
    my $total = "foo" + 111 # NO error
}
my $total = "foo" + 111 # ERROR!!

Perl::Criticで静的検査を行う

次に、静的解析でコードを検査するPerl::Critic 1.132を紹介します。

なお、Perlの静的解析について詳しくは、本連載第27回「Perlにおける静的解析」第28回「Perlの構文解析器の作り方と応用例」で紹介しています。

perlcriticを使ってみる

Perl::Criticに同封されるperlcriticと呼ばれるコマンドラインツールを使ってみましょう。実際の用途としては、エディタで自動的にチェックするように設定することをお勧めします[2]⁠。

次のコードは、strictが有効になっていないため警告が出ています。

% echo "print 'HELLO'" | perlcritic
Code before strictures are enabled at line 1, column 1. S
ee page 429 of PBP. (Severity: 5)

strictプラグマを有効にして、警告を回避します。

% echo "use strict;print 'HELLO'" | perlcritic
source OK

先ほどの警告にあった(Severity: 5)は静的検査の厳しさを指し、1~5の段階のうち、最も直すべき指摘です。以下は厳しさを3と指定した場合の例です。

% echo "use strict;print 'HELLO'" | perlcritic -3
Code not contained in explicit package at line 1, column
1. Violates encapsulation. (Severity: 4)
Module does not end with "1;" at line 1, column 12. Must
end with a recognizable true value. (Severity: 4)
Code before warnings are enabled at line 1, column 12. Se
e page 431 of PBP. (Severity: 4)

この指摘の意味はそれぞれ、このコードのパッケージが明示されてない、1;で終わっていない、warningsプラグマを利用していない、という指摘です。これらは、手軽に書きたいワンライナーでは不要な指摘でしょう。次項にて、必要十分な指摘となるようルールを指定する方法を説明します。

.perlcriticrcで、静的検査のルールを指定する

Perl::Criticでは、~/.perlcriticrcファイルに静的検査の設定を記述します。基本的な設定例は次のとおりです。

severity = 3 ――(1)
theme    = security || bugs ――(2)

[-Subroutines::ProhibitSubroutinePrototypes] ――(3)

(1)で静的検査の厳しさを3に指定しています。(2)で静的検査のグループを指定でき、ここでは、セキュリティに関連するルールとバグを誘発する構文のチェックをしています。(3)では、Subroutines::ProhibitSubroutinePrototypesの先頭に-を添え、この構文ルールを適用しない設定をしています。

静的検査チェックのルール一覧を確認する

適用可能な静的検査のルール一覧はperlcritic --listで取得でき、.perlcriticrcを適用したルール一覧はperlcritic --list-enabledで取得できます。各ルールの概要をつかむのであれば、Perl::Critic::PolicySummaryを読むことをお勧めします。以下、ルールを一部紹介します。

InputOutput::ProhibitTwoArgOpen
open $fh, "< $file"と書くより、open $fh, '<', $fileと書く
Modules::RequireFilenameMatchesPackage
package Foo::Barというパッケージであれば、Bar.pmというファイル名である必要がある
Subroutines::ProhibitExplicitReturnUndef
失敗時の返却は、return undefと書くより、returnと書く
Subroutines::ProhibitReturnSort
スカラコンテキストでsortが呼び出されたときの挙動は定まっていないため禁止
Variables::ProhibitConditionalDeclarations
my $foo = $bar if $baz;という変数宣言を禁止
BuiltinFunctions::ProhibitStringyEval
eval "my $foo; bar($foo);と書くより、eval { my $foo;bar($foo) }と書く

ここまでに、プラグマやPerl::Criticを用いて未然に異常を防ぐ方法を紹介しました。アプリケーションによらない汎用的な解決手段ですので、ぜひ試してください。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧