2017年04月07日の日記です


プラスとピリオドの取り違え  2017-04-07 11:57:37  コンピュータ

恥ずかしながらケアレスミスによるバグを出した。

恥ずかしい話だし、黙っていればいいのだけど、思うところがあったので書いておこう。




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 の場合は、「組み込めない」場所には、ブロックが入れられないようになっている。

子供向けだからこその配慮だけど、ケアレスミスは起こせない。


いつまでもテキストだけで書こうとするのではなく、これからはそういう方向の言語だってありだと思う。





同じテーマの日記(最近の一覧)

コンピュータ

別年同日の日記

05年 工事が遅くなるそうだ

10年 ポメラニアン

14年 オーレ・キアク・クリスチャンセンの誕生日

15年 プログラムは必要か

15年 女性向けのゲーム


申し訳ありませんが、現在意見投稿をできない状態にしています


戻る
トップページへ

-- share --

11000

-- follow --




- Reverse Link -