null、null安全とは何か
nullはプログラミングをする上で欠かせない仕組みです。
一方で、nullが存在することによる問題もあり、近年ではそのような問題を防ぐためnull安全と呼ばれる仕組みが取り入れられている言語も多くなっています。
この記事では、nullとは何か?null安全とは何か?なぜ必要なのか?を超わかりやすく解説します。
ぜひ最後まで読んでみてください!
nullとは
まずはnullとは何かを理解しておきましょう。
プログラミングではさまざまなデータを扱いますが、「データがない」という状態を示すことができると便利な場面があります。
例えば、ageという数値型の変数を扱いたいとしましょう。
この変数にはユーザーの年齢を代入します。
Integer age;
この変数を宣言した時点では、まだ代入する値が取れないとしましょう。
しかし通常、変数は適当な値で初期化することが推奨されています。
そうでないと値が未定義のためバグの原因になり得るからです。
ではこの変数をどんな値で初期化すればいいでしょうか?
一つの手段として、数値型なので0で初期化する方法が考えられます。
Integer age = 0;
しかし、場合によってはこの方法だと不都合が生じることがあります。
例えば、ユーザーの年齢が0歳だったらどうでしょうか。
age変数を使おうとしたとき、「初期値の0」なのか「ユーザーが入力した0」なのか判別できません。
そのため、例えば次のように変数が初期値なら何らかの処理をしたいという条件分岐が書けなくなってしまいます。
// 初期値なら何らかの処理をしたい
if (age == 0) {
// これだと0歳が入力された場合も真になってしまう
}
このように「値がない状態」を表現するため適当な値を使おうとすると、その値自体も何らかの意味を持ってしまうため注意が必要です。
そこで、このような場面でnullが役立ちます。
nullとは「値がない状態」を示す特別な値です。
先ほどの初期化をnullを使って行えば、値がないということを明示的に表現できます。
// 初期値にnullを代入
Integer age = null;
// 初期値なら何らかの処理をしたい
if (age == null) {
// これなら初期値の場合のみ真になる
}
nullの目的は「データの不在や欠如を表現すること」です。
上記では変数の初期化を例に出しましたが、これ以外でも値がないことを表現したい様々な場面で役に立ちます。
nullによって発生する問題
このように便利なnullですが、nullがあることによって発生する問題があります。
それはnullに対してメンバーを参照したり、メソッドを呼び出すとエラーになるということです。
nullはデータがないので、当然、メンバーもメソッドも持っていません。
そのため、存在しないメンバーやメソッドを使おうとするとエラーが発生します。
下記の例では、nullを代入した文字列型の変数の長さを取得しようとしていますが、これはエラーになります。
String型のデータがnullにも関わらず、String クラスのlengthというメソッドを呼び出しているからです。
String name = null;
System.out.println(name.length()); // Error
このようなnull参照によるエラーの何が問題かというと、コンパイル時では発生せず、実行してみて初めて発生するエラーであるということです。
nullにアクセスするかどうかはプログラムの流れを追わないと分からないため、実行してみるまでエラーになるか分からないのです。
実行するまで分からないということは、それだけエラーの発見が遅れてしまいます。
また、コンパイルのように機械的にチェックできるわけではないので、エラーを取りこぼしてしまう可能性もあります。
この問題に対して昔から存在する一般的な解決法はnullチェックを行うことです。
以下の例では、name変数がnullでないか判定しています。
if (name != null) {
// nullではない場合の処理
}
しかしnullチェックの実施は実装者に任されるため、つい忘れてしまう可能性もあります。
プログラムの中でnull参照が起こり得る箇所をすべて把握して漏れなくチェックすることを想像してください。難しいと思います。
nullは便利ですが、このように根本的な問題を抱えています。
nullを発明したアンソニーさんはnullの存在は「10億ドル規模の失敗」と表現するほどです。
Null References: The Billion Dollar Mistake
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/
null安全とは
近年ではこのnull参照問題を回避するための仕組みを持つ言語が多くなっています。
このような取り組みをnull安全と言い、例えば「Kotlinはnull安全な言語」などと言ったりします。
null安全に対応している言語はKotlin、TypeScript、Rust、C#、Dartなどがあります。
また、Javaのように言語全体ではなく一部をnull安全にする仕組みを導入した言語もあります。
このようにnull安全への対応状況は言語によって異なります。
null安全を支える仕組みは言語により異なりますが、核となるのはコンパイラにより以下の制約を設けることです。
・通常、変数にnullを代入できない
・nullを代入するには、nullになり得る変数であることを明示的に書かなければいけない
・nullになり得る変数を使う場合はnullチェックを強要する
null安全な言語であるTypeScriptで例を見てみましょう。
TypeScriptでは下記のように変数にnullを入れようとすると、静的型チェック※でエラーになります。
※エディタのTypeScriptの拡張によりコードを書いている途中でリアルタイムに検知されます。
このように、まずそもそも簡単にnullを使えないようになっています。
const userName: string = null; // エラー Type 'null' is not assignable to type 'string'.(2322)
しかしnullを入れたい場合もあるでしょうから、その場合は下記のように書きます。
これはuserNameの型がstringまたはnullであると明示的に書くことで、コンパイラに「この変数はnullになり得る」ということを伝えます。
const userName: string | null = null; // OK
このようにnullになり得る値のことをnullable(読み方:ヌラブル)と言います。
※nullになれる(able:~になれる、できるの意味)でnullable
ではこのnullableな変数を使ってみます。
例として50%の確率で文字列かnullを返すgetStringOrNull関数を用意しました。
この関数の戻り値を先ほどのuserName変数で受け取ります。
// 文字列かnullを返す関数
function getStringOrNull() {
// 乱数を使って50%の確立で文字列を返す
if (Math.random() < 0.5) {
return "Tanaka";
}
// それ以外の場合はnullを返す
return null
}
// 結果をnullableな変数に代入
const userName: string | null = getStringOrNull();
その後、下記のようにuserName変数を使用してみます。
今回は.lengthで文字列の長さを参照します。
// 文字列かnullを返す関数
function getStringOrNull() {
// 乱数を使って50%の確立で文字列を返す
if (Math.random() < 0.5) {
return "Tanaka";
}
// それ以外の場合はnullを返す
return null
}
// 結果をnullableな変数に代入
const userName: string | null = getStringOrNull();
// nullableな変数のメンバを参照
console.log(userName.length); // エラー 'userName' is possibly 'null'.(18047)
ご覧の通り、変数を使用する箇所でエラーになります。
これは「nullになり得るのであればちゃんとnullチェックしなさい!」とコンパイラから言われています。
エラーを消すためには、下記のようにnullチェックを行います。
// 文字列かnullを返す関数
function getStringOrNull() {
// 乱数を使って50%の確立で文字列を返す
if (Math.random() < 0.5) {
return "Tanaka";
}
// それ以外の場合はnullを返す
return null
}
// 結果をnullableな変数に代入
const userName: string | null = getStringOrNull();
if (userName != null) {
// nullではない場合のみ参照する
console.log(userName.length);
}
このように、null参照となり得る箇所があるとエラーが出るのであれば、nullチェックを忘れることはありません。
nullチェック以外にそもそもロジックを変更してnull参照が起こらないようにしてこのエラーを解消するのももちろんありです。
堅牢なコードを書くのにとても役立つ機能なので、扱う言語がnull安全の機能を持っているならぜひ使ってみてください。
おまけ:オプショナルチェーン
null安全とは直接関係があるわけではないですが、知っておくと便利な機能を合わせて紹介します。
オプショナルチェーン
nullを扱う時にいちいちnullチェックを書くのが煩わしく感じることがあるかもしれません。
そんな時は、nullになり得る値に?.を付けることで、nullではない場合にのみメンバやメソッドにアクセスできます。
// 通常のnullチェック
if(userName != null) {
console.log(userName.length);
}
// オプショナルチェーンを使ったnull参照
console.log(userName?.length);
この場合、userName変数がnullでない場合のみlenghtプロパティを参照します。
もしuserName変数がnull(またはundefined)だった場合はundefinedが返され、エラーにはなりません。