21.11.22 【プログラミングの罠】空文字列、null、undefined

今回から「テクニカルコラム」として、Buddyでのアプリ開発にまつわる技術的な話題について書いていきます。内容に順序はなく、ユーザーから寄せられた質問などにもとづいて、開発上で引っかかりやすいポイントや、Buddyの標準的な機能では足らない部分を補うテクニックなどを取り上げます。よろしくお願いいたします。

今回は「【プログラミングの罠】空文字列、null、undefined」です。

「プログラミングの罠」とは、誤りに陥りやすいポイント、というような意味です。Buddyでは基本的にJavascript言語を用い、データベース操作では一部SQL言語の機能を用いますので、JavascriptとSQLでのプログラミングでの注意点ということですね。取り上げるのは、空文字列、null、undefinedというちょっと不思議な存在です。

JavascriptとSQLではちょっと様子が違うので、まずJavascriptの場合を考えます。

まずは空文字列です。Javascriptでは、”” とか ” と書くと空文字列です。”abc” と書けば3文字からなる文字列ですね。でも文字が一つもないのに「文字列」とはこれいかに?日常的な感覚からするとちょっと不思議ですが、プログラミングの世界では空っぽ(長さが0)でも文字列として扱うことにすると便利なので、そうなっています。

次にnullです。nullは「何もない」という意味で、Javascriptでは「値を持たない特殊な状態」のことです。ドイツ語のNullは数値の0(ゼロ)の意味だったりするのでややこしいですが、0とnullは違います。空文字列とも違います。空文字列は「長さ0の文字列という値」、nullは「値を持たない特殊な状態」。うーん…という感じですが、そういうことになっています。プログラミングの中では、0や空文字列と区別してnullがあることは結構重要です。

さて、Javascriptではさらにundefinedというモノがあります。文字通り「未定義」です。変数を定義したが初期値をセットしなかったら、その変数の内容はundefinedです。オブジェクトの存在しないプロパティを読み出すとこれもundefinedです。undefinedがあるのにnullが必要なのか?どう使い分けるのか?という疑問が浮かびますが、とにかくそうなっています。
ちなみに、null + 0 は 0 になりますが、undefined + 0 は NaN というこれまた特殊なモノになります。さらにややこしいですね。undefinedについてはいろいろと突っ込みどころがあるのですが、今回はスルーします。

次にデータベースを操作する言語であるSQLではどうなっているかを見てみましょう。

空文字列はSQLにもあります。SQLでは文字列はシングルクォーテーションで囲むので、” ですね。

nullもあります。しかし、SQLにはundefinedはありません。テーブルのカラムに何も値をセットしていない状態は null です。
ちなみに、SQLでは null + 0 は 0 ではなく null です。「値のない状態」に対して演算を行うことはできず結果もまた「値のない状態」になる、という考え方ですね。比較するときも「… = null」ではダメで「… is null」という特殊な書き方をする必要があります。うっかり「… = null」と書いてもエラーにはならず、比較結果がnullになってしまうというところが罠ですね。

以上はJavascriptとSQLについての一般的なことでした。次にBuddyでのプログラミング、特にデータベース操作で注意すべき点を書いておきます。

例えば「Item」というテーブルに「ID」「name」「price」のカラムがあるとします。IDとpriceは数値型、nameは文字列型です。IDが1のレコードの内容を更新するには、例えば次のように書きます。

	this.tables.Item.updateRecord({
		where: {ID: 1},
		data: {name: '商品A', price: 10000}
	}, function(error, result) {
		if(error) return console.log(error.response.text);
	});

何も問題はありません。nameに’商品A’、priceに10000がセットされます。

では、

		data: {price: 20000}

としたらどうなるでしょうか?
結果は、nameは変わらずそのままで、priceに20000がセットされます。updateRecord()の場合、dataで指定しなかったカラムは更新対象にならないということですね。

次にこんなことをやってみましょう。

		data: {name: undefined, price: 30000}

としたらどうなるでしょうか?
この場合の結果も、nameは変わらずそのままで、priceに30000がセットされます。undefinedだと、そもそも指定しなかった場合と同様の動作になります。

nullをセットしてみましょう。

		data: {name: null, price: null}

としたらどうなるでしょうか?
結果はもちろん、nameもpriceもnullになります。(「もちろん」と書きましたが、これはJavascriptのnullはSQLでのnullとしてセットするように内部で処理されているからです。)

では、空文字列をセットしてみましょう。

		data: {name: '', price: ''}

としたらどうなるでしょうか?
結果はエラーとなり、コンソールに「invalid input syntax for integer: “”」と表示されました。nameもpriceもそのままです。
実は

		data: {name: '商品A', price: '10000'}

のようにpriceに文字列(中味は数値であるような)を与えると、ちゃんとnameに’商品A’、priceに10000がセットされます。これは、Buddyで使用しているPostgreSQLでは、数値型のカラムに文字列をセットしようとした場合、暗黙の型変換が行われて数値に変換できれば成功します。しかし、空文字列は数値に変換できないので、エラーになったわけです。

さて、上記の例ではdataの中に値を直書きしていますが、実際のアプリではnameやpriceを入力するテキストボックスがあって、その値を取ってきてセットするような場合が多いでしょう。例えば、次のようになります。

	var name = this.items.nameTEXT.getValue();
	var price = this.items.priceTEXT.getValue();
	this.tables.Item.updateRecord({
		where: {ID: 1},
		data: {name: name, price: price}
	}, function(error, result) {
		if(error) return console.log(error.response.text);
	});

テキストボックスからgetValue()で得られるのは文字列です。入力内容を削除して空にすると空文字列になります。それがpriceの入力欄であれば、updateRecord()に与えるときにそのままでは上記のエラーになります。空文字列はnullに置き換えて与えるのが、おそらくユーザーの入力意図にも沿った処理となるでしょう。(将来のBuddyで、この置き換えを自動でおこなうオプション機能を提供するかもしれませんが、現時点ではプログラムで書く必要があります。)
また、テキストボックスをクリアするために、setValue(null) や setValue(undefined) とすれば、getValue()で得られるのも null や undefined になります。null であれば意図通り null がセットされますが、間違えて undefined にすると、セットされずに元の値のままになってしまいます。

以上のように、空文字列、null、undefinedについては、いろいろな罠が潜んでいます。注意しましょう。

(2021/11/22 中島)

Posted in テクニカルコラム.