Typescript の型安全
型安全にコードをかけるというのが Typescript のいちばんの強みと言っても過言ではないかと思います。
例外として、 any
や as
を使うと型安全が失われてしまうというのもよく聞く話ではないでしょうか。
例外はまだある
ただし、例外はそれだけではなく Overload の機構も型安全を破壊することがあります。(他にもまだあるのかもしれない)
知っていれば簡単な話だと思うのですが、知らなければ思わぬところで転んでしまうことがあるかもしれないなと感じたので、記事として残しておきます。
Typescript の Overload
Typescript にも Overload の仕組みが存在しており、通常の関数やコンストラクタ等で使用することができます。
// 引数なしだと string 型
function safeFunc(): string;
// number 型の引数を渡すと number 型
function safeFunc(n: number): number;
// 二つの型情報を満たす実装
function safeFunc(n?: number): number | string {
return n ?? 'string';
}
it('n', () => {
const number = safeFunc(10.1);
expect(typeof number === 'number');
expect(number).toBe(10.1);
expect(number.toFixed()).toBe('10');
});
it('undefined', () => {
const string = safeFunc();
expect(typeof string === 'string');
expect(string).toBe('string');
expect(string.length).toBe(6);
});
型安全が破壊される場合
Overload が見ているのはあくまでも型情報だけで、実装から型が推論されるわけではありません。
Overload を行う場合、型の整合性は実装側で担保する必要があります。
最終的に実装を含んだ関数と返り値の型情報が異なっている場合は通常通り検証されますが、Overload で使われた型情報まではカバーされないのです。
結果、「Typescript は型安全〜」と思って、気を抜いてOverload を使うと、簡単に型安全が破壊されます。
Overload に用いる型情報は先ほどと同じですが、実装を少し変えて、型安全が破壊されるコードを書いてみます。
// 引数なしだと string 型
function unsafeFunc(): string;
// number 型の引数を渡すと number 型
function unsafeFunc(n: number): number;
// 二つの型情報を満たす実装
function unsafeFunc(n?: number): number | string {
// 実際には、number 型の引数を渡すと string 型 が返る
if (n) return n.toString();
// 実際には、引数なしだと number 型 が返る
return 0;
}
it('n', () => {
const number = unsafeFunc(10.1);
expect(typeof number === 'string');
expect(number).toBe('10.1');
expect(() => number.toFixed()).toThrowError(TypeError);
});
it('n', () => {
const string = unsafeFunc();
expect(typeof string === 'number');
expect(string).toBe(0);
expect(string.length).toBeUndefined();
expect(() => string.trim()).toThrowError(TypeError);
});
型情報は Overload で指定したものが返るため、型情報とテストの内容はちぐはぐですが、テストはこのまま通過します。
Overload は使わない or 必要最低限に
any
は危険だって話はよく聞くんですが、 Overload のことは、検索しても言及しているところが特に見つからなかったため、「これも危ないのでは?」と書いてみました。(as
もそこそこ危ない)
少し強めの言葉を使っていると思うのですが、この記事になにか変なところや間違いなどがあればコメントお願いしたいです。
アロー関数を使う理由がまた一つ増えた。(アロー関数の方が何かと厳密な気がする)