ハッシュ変数に存在しないキーを指定した場合の値は何?

今日、初めてPerlのコードを触りました。

僕は保守チームなので、もちろん既存バグの修正です。

しかもタチの悪い事に本番環境では上手くいっているのに、

開発環境で上手くいってない、という一番厄介もとい力の見せ所なパターンでした。

パッと見そんなにレベルの高そうなコードじゃなかったので、

とりあえず30分ぐらい、誰にも邪魔されない喫煙所で、基本構文のリファレンスをコピッたのを熟読して、いざ原因調査へ。

見事にハマった・・・orz

一機能がbashスクリプト、PL/SQL、Perlと複数言語に分散されてて実行時の動作がイミフだったのもありますけど・・・

ハマった原因の一つがコレ。

@array = {'aaa', 'bbb', 'ccc'};
%tmp;
for ( @array ) { $tmp{$_} = 1 }
if ($tmp($param) == 0){
   # 配列に存在しない場合の処理
}

$paramの文字列が配列に存在しない時に、コメントの部分の処理を動かしたい、っていう意図なのですが。

for文で、@arrayの値をキーとした$tmpの初期化をして、

$tmpに$paramをキーとした値が存在する場合はコメント部分の処理を行なわない、という風に実装者はしたかったのだと思います。

なんか変な事やってるなー、と初めは思ったけど、わざわざこんな事するなら意味あるんだろう、とスルーしてしまった・・・(言い訳)

他にも本番環境と開発環境の違いとか沢山怪しい所があったので、そっちに気を取られた・・・(言い訳)

このコードの場合、例えば$paramの値が'xxxx'だった場合、$tmp($param)の戻り値は「未定義」、だよなー?

だとすると、「$paramの値が@arrayに入っている値で無かったとしてもコメント部分の処理に絶対に行かない」んじゃないか・・・

あれ、でも未定義は数値として評価された場合0に変換されるんじゃ・・・

って今の今までモヤモヤしてましたが、解決しました!!

数値的に使用されたことのない値の真偽値をテストすると、予期しない結果が返されます。

論理演算では、どちらの方向の型変換も行なわれないからです。

Perlクックブック - Google ブックス

・・・だそうです。

Web上を走り回って明確な答えを得られなかったのに、その答えをクックブックで示すとは・・・さすがオライリー。

という訳で「undef == 0」の結果が偽となる為、絶対に条件式がfalseに評価される、という事でした。

(注:この解釈は間違いでした。下部、追記参照)

・・・なぜこんなコード書いたし><

修正したコードはこんな感じ。

@array = {'aaa', 'bbb', 'ccc'};
%tmp;
for ( @array ) { $tmp{$_} = 1 }
if (not exists ($tmp{$param}) ){
  # 配列に存在しない場合の処理
}

(2009-11-11 修正。wkbyshnbtkさん、ありがとうございました。)

うん、意図わかりやすくなった。

Perlプログラマの方にとっては当然の事かもしれないですが、今日始めたばっかの自分にはしんどかったです・・・

ちなみに本番環境はたまたま該当データの全てが、コメント部分の処理を必要としていなかったので異常が出てなかった。

開発環境ではテスト用に作ったデータがコメント部分の処理を必要としていたため、意図した処理が動かず異常となった、と。

ここら辺も、まだ僕の甘い所ですね・・・

客先常駐の場合、経験年数2年だろうが何だろうが、お客さんはプロ扱いしてきます。

初心者だから大目に見てやろう、とかそんなんありません。

「できないモンはできない!」ってハッキリ言うのも手ですが、僕が今置かれてる現状だと、僕しかやる人がいないので逃げようもありません。

初めて触る言語だろうが何だろうが、お客さんの要求に最大限答えるように努める、って結構面白いですよ。

客先常駐&保守も捨てたもんじゃない、と僕は思います。

話が脱線しました。

とりあえず、モヤモヤが取れて凄く気持ち良い・・・

これだからバグ修正はやめられねぇ!!!

追記 [2009-11-11]

えーっと。普通に間違えてました・・・

ハッシュ変数に存在しないキーを指定した場合の値はundefで間違いないだろうとは思うのですが、

比較演算子「==」で右辺と値を比較した場合、ちゃんと型変換が行なわれていました。

Perlクックブックからの引用部の真意は、

「ifとかwhileとかの条件式で、数値的に使用されたことのない文字列の値を返すと、"0"が返されてfalseに評価される」

という事だと思います。たぶん。

なので、本文に挙げたコードは、処理的には間違っていません。

原因は、全然別のところにありました。

$paramがちゃんとtrimされてなかった・・・orz

そりゃ、いくら条件式変えようが配列要素の存在チェックは常にfalseが返るわな・・・

うまくいったと思ったのは、どうやら勘違いだった模様・・・情け無い・・・

実際は、本文のコードの前処理でこのような正規表現を使ってtrimしていました。

(多分、実装者も同じページを見てたと思う)

$string =~ s/^\s+(.*?)\s+$/$1/;

PerlでTrimする! - 燈明日記

この正規表現だと文字列の両側に半角スペースがある場合のみ、ちゃんとtrimされて、

右側だけに半角スペースがある場合(左側に半角スペースが無い場合)はtrimできていませんでした。

同じページで紹介されている、もう一つの方法を試してみました。

$string =~ s/^\s+//;

$string =~ s/\s+$//;

PerlでTrimする! - 燈明日記

こちらの正規表現だと、片方にだけ半角スペースがある場合も、上手くtrimされました。

今までコードを読んだ事もない言語を触った為、無意識のうちに経験ある言語の仕様で考えてしまっていたのかもしれません・・・

・・・精進します。

このエントリーをはてなブックマークに追加
comments powered by Disqus