Perlで書かれた業務システムの移行について相談を受けることがあるので、実際に何が壊れるのかを手元で確かめました。
本番のシステムを移したわけではなく、移行元に相当する小さなデータとスクリプトを用意して動かした記録です。環境は Perl 5.38.2 と PHP 8.4.23。
■1. そもそも今のPerlでは動かない
移行以前の話として、古いCGIは現在のPerlでそのままは動きません。
$ perl -MCGI -e 'print "ok"'
Can't locate CGI.pm in @INC
CGI.pmはPerl 5.22でコア配布から外れました。use CGI; で書かれたスクリプトは、CPANから個別に入れない限り起動しません。サーバーのEOLに合わせてOSを新しくしたら動かなくなった、という相談はこれが原因のことがあります。
■2. Shift_JISの0x5C問題
これが一番厄介です。Shift_JISには、2バイト目が 0x5C(バックスラッシュと同じ値)になる文字があります。
表 -> 95 5c 2バイト目=0x5C
能 -> 94 5c 2バイト目=0x5C
十 -> 8f 5c 2バイト目=0x5C
ソ -> 83 5c 2バイト目=0x5C
「表」「能」「十」「ソ」——業務データに普通に出てくる文字です。
ここで、レガシーコードによくある「バックスラッシュをエスケープする」処理をバイト列のまま流すとどうなるか。「表計算ソフトの更新」という文字列で試しました。
処理前: 955c 8c76 8e5a 835c 8374 8367 82cc 8d58 9056
処理後: 955c5c 8c76 8e5a 835c5c 8374 8367 82cc 8d58 9056
「表」と「ソ」の中にあった 0x5C が二重になり、文字列が壊れました。同じことをPHPでやっても結果は同じバイト列になります。これは言語の差ではなく、デコードする前に触ったかどうかの差です。
先に内部表現へ変換してから処理すれば問題は起きません。
Encode::decode('cp932', $line) してから処理し、出力時に encodemb_convert_encoding($line, 'UTF-8', 'CP932')PHPに移す場合、正規表現にも注意が要ります。/u を付けないとバイト単位で処理されます。
/./ の一致数: 27(バイト単位で分解される)
/./u の一致数: 9(文字単位)
■3. ハッシュの列挙順は、そもそも安定していない
移行検証では「同じ入力で同じ出力になるか」を確認します。ところがPerlのハッシュは、同じスクリプトを同じ入力で動かしても列挙順が毎回変わります。
1回目: 5,4,3,1,2
2回目: 5,3,4,1,2
Perl 5.18以降、ハッシュの順序はプロセスごとにランダム化されているためです。一方PHPの配列は挿入順を保持するので、何度動かしても同じです。
1回目: 1,2,3,4,5
2回目: 1,2,3,4,5
つまり keys %h の結果をそのまま出力している箇所があると、旧システムの出力順はもともと不定だったことになります。これを知らずに突き合わせると、差分が出るたびに移行のバグを疑って時間を溶かします。実際には旧側が毎回違うだけです。
逆に言うと、順序が意味を持つ処理なら旧システムの時点でバグだった可能性があります。移行はこういう問題を見つける機会でもあります。
■4. 比較演算子の結果が変わる
PerlとPHPで結果が変わるものがあります。特にPHP 8で文字列と数値の比較仕様が変わったため、単純に == を == へ置き換えると挙動が変わります。
| 式 | Perl 5.38 | PHP 8.4 |
|---|---|---|
0 == 'abc' | true | false |
'1' == '01' | true | true |
'10' == '1e1' | true | true |
Perlは数値として比較できない文字列を0扱いにするので 0 == 'abc' が真になります。PHP 8は文字列側に寄せて比較するため偽です。PHP 7以前なら真だったので、古い移行事例の情報を参考にする場合も注意が要ります。
なおPerlは数値比較 == と文字列比較 eq が分かれていますが、PHPは == と === で意味が違います。機械的な置き換えができない箇所です。
■5. ついでに気づいた点
配列の件数の取り方が違います。
my $n = @rows; # 3 が入る(スカラーコンテキスト)
print "@rows"; # 10 20 30 が出る(文字列補間)
同じ @rows が文脈によって件数にも中身にもなります。PHPには対応する概念がないので、count($rows) と明示する必要があります。読み替えを間違えても動いてしまうことがあるので、目視での移植は危険です。
■まとめ
移行で時間を使うのは、新しい言語で書き直す作業そのものではありませんでした。
このあたりを先に把握しておかないと、「移行したら数字が合わない」という状況になって原因調査に時間を取られます。逆に、ここさえ押さえれば見通しは立ちます。
当社ではPerlからPHP・Pythonへの移行について、現状調査(アセスメント)からのご相談を承っています。仕様書が残っていない、書いた人がもういない、という状態が前提です。