恥ずかしながらケアレスミスによるバグを出した。
恥ずかしい話だし、黙っていればいいのだけど、思うところがあったので書いておこう。
PHP でサーバー側を、Javascript でクライアント側を動かすプログラムを組んでいた。
まぁ、良くある話だ。
両言語とも、いろいろと似たところのある言語だ。
データの型は比較的緩く、必要に応じて自動変換される。
たとえば、
32 + "1"
と書くと、33 になる。前の 32 は数値、後ろの 1 は文字列なのだけど、計算しようとしているのだから両辺を数値にしないといけない、と気を回してくれるのだ。
"32" - 1
と書くと、31 になる。これも、同じように文字列の 32 を数値に変換してくれるんだ。
では、次の式はどうなるだろう?
"32" + 1
これは、PHP では 33 で、Javascript では "321" だ。
PHP では + は計算に使われるものなので、左右にあるものを数値にする。
でも、Javascript では + は計算と文字列の連結の両方に使われる。
左側が優先されるので、左側にあるものが数値なら計算になるし、文字列なら連結になる。
PHP では、文字列の連結は . (ピリオド)で示す。
"32" . 1
これで、PHP では "321" になる。
同じことを Javascript でやると、エラーになる。
さて、PHP と Javascript では、文字列の連結方法が違う。
でも、同時に使っていたので、うっかりして Javascript で次のようなプログラムを書いてしまった。
var foo = "bar" . str;
ここで str は文字列の入った変数だ。
文字列を連結しようと思ったのだけど、間違えて PHP のやり方で書いている。
先に書いたように、Javascript では文字列の連結は + なのでエラーに…ならない。なってくれれば気づいたのだけど。
Javascript では、 . (ピリオド)はオブジェクトのプロパティへのアクセスを意味する。
オブジェクトっていうのは、まぁざっくりと言えば、名前を付けた複数のデータを保持する仕組みだ。
AWK や perl 、またはそれらの影響を受けた言語では「連想配列」や「ハッシュ」と呼ばれるものと同じだと思っていい。
var obj = {a:1,b:"Hi!"};
ここで obj.a は 1 になるし、obj.b は "Hi!" になる。
C言語系の言語では、配列のアクセスは [ ] で行われる。
Javascript では、こちらの方法も使えて、obj["a"] は 1 で、 obj["b"] は "Hi!" だ。
[ ] と . (ピリオド)は、少し違うけど「ほぼ」同じものなんだな。
「ほぼ」と括弧書きで書いたのは、完全に同じではないから。
ピリオドで書く場合、後ろに書かれるものが「名前」である必要がある。
[ ] で書く場合は、名前ではなく「リテラル」である必要がある。
多くの言語では、単語は「名前」と「直値(リテラル)」に分けられる。
リテラルには大きく分けて数値リテラルと文字列リテラルがある。
それ以外は、大体「名前」だと思っていいだろう。
(細かく見れば他にもいろいろあるのだけど)
さきに "32" . 1 という例を出したけど、"32" は文字列リテラルで、1 は数値リテラルだ。
だから、ピリオドの後ろには名前が来る、というルールに違反していて、エラーになる。
でも、ほぼ同じ書き方である "32"[1] ならエラーにはならない。"2" が返ってくる。
#最後の例はC言語と同じ動作を提供しているのだけど、案外知られていない。
文字列を配列アクセスすると、0 から始まる「位置」に応じた文字を、文字列として取り出せる。
先に書いた間違い、
"bar" . str
の場合、 str は文字列リテラルでも数値リテラルでもない。
これは変数 str の指定…正確にいえば、変数の内容を参照するために、 str という「名前」を書いたのだ。
意図としては、文字列リテラル "bar" に、変数 str に入っている内容の文字列を連結、だった。
ただ、連結の演算子を間違えて、PHP 風に表記してしまった。
すると、オブジェクトのプロパティアクセス演算子となる。
str は変数ではなく、"bar" オブジェクトのプロパティとなってしまう。
Javascript では、すべてのものは「オブジェクト」だ。文字列リテラルであっても例外ではない。
先に書いたように、オブジェクトの正体は連想配列に過ぎない。
そして、文字列オブジェクトの場合、関連情報が連想配列として保持されている。
例えば、「文字列の長さ」という関連情報は、length という名前で保持されている。
だから "bar".length とすれば、 3 が返される。
"bar".str は、定義されていない情報を参照した、というだけで、文法エラーではない。
この場合、定義されていないことを意味する、 undefined という値を返す。
恥ずかしいバグ、というのはこれなのだけど、長いコードの中にこれが混ざっていた。
ここで作りだそうとしていたのは、別サーバーにアクセスする際に渡すパラメーターだった。
さらに、このパラメーターはアクセス自体には影響がなく、アクセス理由などをログに残して解析するためのものだった。
結果として、正しい値を渡せていないにも関わらず、プログラムは動作した。
そして、別サーバーは僕の管轄ではなかったため、ログに正しく記録できているかを調べる術はなかった。
つまり、僕は自分で作ったこのバグに気付かなかったんだ。
ログがうまく取れてない、という報告があって付近を見回し、str の内容などが正しく来ていることなどを確認しても、+ と . を間違えていることに気付かなかった。
僕としては「文字列を連結したい」ところに、PHP で文字の連結を行う . を正しく書いているので、おかしくないと思っていたんだ。
でも、ログがうまく取れていないのはどうやら事実らしいし、バグの可能性範囲を絞り込んでいって…30分くらいたって、やっと自分の過ちに気が付いた。
恥ずかしい、と思うけど、プログラマなら誰でも同じような経験を持っていると思う。
自分が「正しい」と思っていることは、間違えていてもなかなか気づくことができない。
で、思ったんだ。
FORTRAN のことを「古い言語」と笑えないぞ、と。
FORTRAN の有名な「おかしな言語仕様」として、, (カンマ)と . (ピリオド)を取り違えてしまってもエラーにならない、というものがある。
見た目も似ているので、見直していても気づきにくい。
そして、金星探査機を失うわけだ。…いや、これは都市伝説に過ぎないのだけど。
そもそも PHP と Javascript の記法を間違えたからいけない、という話ではある。
でも、それをエラーにできず、構文の間違いに気づく機会を失うのは、FORTRAN と同じような言語の不備だ。
Javascript の場合、アクセスできないとなんでも undefined にする。
ここで、アクセスできないならエラーになるのであれば、構文の間違いに気づいただろう。
もっとも、アクセス違反があってもプログラムが動き続けてくれる気軽さは Javascript のよさでもある。
strict モードをつかったらどうだろう…と思ったけど、これもエラーにはできないようだ。
"32"-1 は計算になるのに "32"+1 は文字列の連結になる、という対称性の悪さも気になる部分ではある。
ここは、連結に . (ピリオド)を割り当てた PHP のほうが良いように思うが、そのせいで PHP は、オブジェクトのプロパティアクセスに -> という面倒な記号を割り当てることになった。
FORTRAN の時代に比べて、言語は便利になったと思うのだけど、こうした「単語の解釈」で足元をすくわれる、というのはあまり変わっていないのかもしれない。
"bar" . str の例でいえば、後ろが「プロパティ名」ではなく「変数名」だと判れば、文法違反でエラーにできる。
でも、文字で書くとどちらも名前であり、情報が足りないために類推するしかない。
例えば、PHP であれば変数名は $ で始まり、プロパティ名には $ がつかないため、こうした文法違反を検出しやすい。
まぁ、変数が必ず $ で始まる、という古臭い仕様が良いとは言わないのだけど。
結局、文字を並べてプログラムする、というやり方の限界だとも思う。
Scratch が良いとは言わないけど、「ブロック」を選んで組み込む方式なら、選んだ時点でプロパティか変数かは確定する。
表示上は「名前」だけを示してすっきりとさせつつ、内部的に情報を保持しておけばよいだけだ。
情報の違いによって、表示の色を変えるなどすれば、ユーザーもミスした際に気付きやすいだろう。
語句解析で情報を「類推」するのではなく、語句の中に見えない情報を埋め込んだ言語だな。
その情報によって、プログラマーのケアレスミスを未然に検出する。
そもそも、Scratch の場合は、「組み込めない」場所には、ブロックが入れられないようになっている。
子供向けだからこその配慮だけど、ケアレスミスは起こせない。
いつまでもテキストだけで書こうとするのではなく、これからはそういう方向の言語だってありだと思う。
同じテーマの日記(最近の一覧)
別年同日の日記
申し訳ありませんが、現在意見投稿をできない状態にしています。 |