ここ一ヶ月ほどTypeScriptのNullチェック機能をONにした状態でコーディングを行っていて、気づいた点をまとめました。
strictNullChecksの抜け道
strictNullChecksの機能を有効化することで、undefined、null、それ以外がコンパイラによって区別され、
Null参照エラーによるバグやNull判定文を減らすことが出来ます。
ただし、全てのエラーが静的チェックできるわけではなく、以下のケースではundefinedが入力出来てしまいます。
初期化されていないインスタンス変数
class Hoge {
a: string;
}
const b: string = new Hoge().a; // コンパイルOK
const len = b.length // 実行時エラー
初期化されていないエンクロージャスコープ内の変数
let c: string; // コンパイルOK
let hoge = () => {
return c.length; // 実行時エラー
};
hoge();
配列(Array)の範囲外
const a: number[] = [];
const b: number = a[1]; // コンパイルOK
b.toFixed(0); // 実行時エラー
Objectの定義されてないキー
const a: {[key: string]: string}} = {};
const b: string = a["test1"]; // コンパイルOK
b.length; // 実行時エラー
importする型定義ファイルがstrictNullChecksに対応していない場合
importするライブラリの定義ファイルがstrictNullChecksに対応していない場合は、当然すり抜けてしまいます。
ちなみに、DefinitelyTypedに登録してある定義ファイルは、私の知る限り現時点では対応されていないようです。
抜け道対策
上記の様なケースを考慮する為に、Null判定のif文を増やしてしまってはstrictNullChecksの効果が半減するので、
ある程度のコーディング規約を決めて置く必要があります。
ローカル変数は定義時に初期化
特別な理由がない限り、ローカル変数は定義時に初期化出来るはずです。
let c: string = "hoge";
インスタンス変数はコンストラクタで全て初期化
コンストラクタでの変数初期化は性能にも寄与します。
class Hoge {
a: string;
b: number;
constructor(){
this.a = "hoge";
this.b = 0;
}
}
Map,Setを利用する。
Array、Objectはそれぞれ専用のラッパーを通してアクセスすることで、undefinedチェックの責務を
集中させることが効果的です。(性能を考慮すると、全ての場面で有効ではないですが。)
また、ES6の Map、Setのメソッドを通したアクセスは静的チェックが効くので利用可能な場合は積極的に利用するのが良いと思います。
const map = new Map<string, string>();
map.get("hoge").length; // コンパイルエラー
まとめ
DefinitelyTypedなどの公開定義ファイルは、後方互換を考えると定義ファイルを2重で用意する必要があるので、
strictNullChecksへのハードは高く結合部分はどうしてもNull判定文を入れる必要が出てきます。
それでも、疎結合を意識してコーディングすればstrictNullChecks現状でも十分強力に働くので、今後も積極的に利用していきたいと思います。