- 技術 💻
TypeScriptの型とnull8選|any・unknown
型って、「書くのが面倒な決まりごと」に見えることがありますよね。
でも型は、バグを実行前に捕まえる仕組みであり、その歴史には設計者本人の謝罪まで残っています。今回は、「10億ドルの過ち」から、TypeScriptの型が実行時に消える話まで、型とnullをめぐる「ほぉー」を8つ集めました。1つでも知らなかったら儲けものです。
① nullは、発明者が「10億ドルの過ち」と謝っています
まず、有名すぎる話から。
nullを1965年に考案したトニー・ホーアは、2009年の講演でこう振り返りました。「あれは私の10億ドルの過ちだった」。
当時「実装が簡単だから」という理由で入れたnull参照が、その後世界中で無数のクラッシュとバグと脆弱性を生んだ——本人がそう認めているんです。
NullPointerException、ぬるぽ、セグフォ。私たちが日々戦っているあれは、半世紀前の一つの設計判断の子孫なんですよね。だから現代の言語は、この過ちを設計で正そうとしています。
② TypeScriptの型は、実行時には消えています
これは知らないと確実に事故ります。
TypeScriptの型注釈は、コンパイル(JavaScriptへの変換)の時点で、跡形もなく消えます。実行されるのは型情報のないただのJavaScriptです。だから、こう書いても守られません。
function f(x: number) { ... }
f(response.data); // dataが実は文字列でも、実行時は素通り
外部から来るデータ(APIレスポンス、フォーム入力)は、型注釈があっても実行時には検証されない。「型を書いたのに実行時エラー」の正体はこれです。
境界では、実行時に実際に検証するコードが別途必要なんです。
③ 型の一致は、「名前」で決まるとは限りません
「同じ型かどうか」の判定方法に、実は2つの流派があります。
| 流派 | 判定のしかた | 代表的な言語 |
|---|---|---|
| 公称型 | 名前で判定。「Dog型」と「Cat型」は、中身が同じでも別物 | Java、C# |
| 構造的型 | 形で判定。同じメンバーを持っていれば、同じ型とみなす | TypeScript、Goのインターフェース |
TypeScriptでは、名前の違うオブジェクトでも、必要なプロパティが揃っていれば代入できてしまう。「アヒルのように歩き、アヒルのように鳴くなら、それはアヒル」というダックタイピングの考え方です。
同じ「型」という言葉でも、言語によって判定基準が正反対。移る時に一番戸惑うポイントなんです。
④ nullとundefined、2種類ある言語があります
JavaScriptには、「値が無い」を表すものが2つあります。
null(意図的に「無い」を入れた)と、undefined(そもそも設定されていない)。似ていますが、由来が違います。そして罠が、比較の挙動です。
null == undefined // → true(緩い比較)
null === undefined // → false(厳密な比較)
「無い」が2種類あり、比較方法で結果が変わる。だからJavaScriptでは === を使うのが定石——この助言の背景には、こういう歴史的事情があるんです。
⑤ 現代の言語は、「nullかもしれない」を型で強制します
①のホーアの過ちへの、言語からの回答です。
KotlinやSwift、TypeScriptの厳格モード(strictNullChecks)などでは、「nullが入りうる」ことを型で明示しないと、nullを扱えません。
let a: string = null; // エラー:nullは入れられない
let b: string | null = null; // OK:「nullもある」と宣言して初めて許される
そして string | null 型は、nullチェックを通すまで、文字列として使わせてもらえません。コンパイラが強制するんです。
つまりNullPointerExceptionを、実行時ではなくコンパイル時に潰せる。「10億ドルの過ち」を、型で封じ込める仕組みなんです。
⑥ any は、型システムに空けた穴です
TypeScriptで型エラーに困ったとき、anyを付けると黙ります。でもこれ、その変数から先の型チェックを全部無効化する危険な操作です。
anyは「何でもあり」の型。一度anyにすると、そこを通ったデータは型の保護を失って伝播します。エラーが消えたのではなく、チェックを止めただけなんですよね。
「本当に型が分からない」ときの正解は、anyではなくunknown。unknownは「使う前に必ず種類を確認せよ」と強制するので、安全なんです。
anyは型システムに空けた穴。塞ぐ相手であって、頼る相手ではないんです。
⑦ 「型を書く」と「型を推論させる」は、両立します
「型付き言語は、いちいち型を書くから面倒」——これも半分は誤解です。現代の型システムは型推論が強力で、多くの場面で書かなくても型を言い当ててくれます。
const nums = [1, 2, 3]; // number[] と自動で分かる
const total = nums.reduce((a, b) => a + b); // numberと分かる
だから実務の勘所は、「境界には明示的に書き、内部は推論に任せる」。関数の引数と戻り値、外部との境界だけしっかり型を付ければ、中身は推論が面倒を見てくれる。
型は「全部書く」でも「全部省く」でもなく、効かせどころを選ぶものなんです。
⑧ 型を、状態そのものにできます
最後は、型の一番かっこいい使い方です。
「読み込み中」「成功(データあり)」「失敗(エラーあり)」——この3状態を、boolフラグの組み合わせで持つと、「成功なのにエラーもある」というあり得ない状態が表現できてしまいます。
型で状態を設計すると、これをそもそも作れなくできます(判別可能なユニオン型)。
type State =
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; error: Error };
成功のときだけdataがあり、失敗のときだけerrorがある、と型で縛る。
「あり得ない状態を、そもそも書けなくする」。これが型の到達点で、テストで防ぐより一段強い。バグを、実行時ではなく設計時に消す発想なんです。
番外編:知っておくと会話が深くなる話
「型が通れば、だいたい動く」
関数型言語の世界には、「型検査が通ったら、もう動いたも同然」という感覚があります。強い型システムは、テストの一部を肩代わりしてくれるんですよね。
ジェネリクスは「型の関数」です
List<T> の T は、型を受け取って型を返す仕組み。値を扱う関数があるように、型を扱う関数がある、と考えると腑に落ちます。
「?」の意味
多くの言語で、型やプロパティに付く ? は「無いかもしれない」の印です。user?.name は「userがあれば.nameを、無ければundefinedを」。①の過ちと向き合うための、日常的な道具なんです。
RJCでも、型と向き合っています
RJCが携わる保険や金融のシステムは、状態の取り違えが事故に直結する世界です。⑧の「あり得ない状態を作れなくする」設計は、品質をコードの構造で担保する考え方なんですよね。
研修でもReactやTypeScriptを扱いますし、こういう仕組みの話をTechチームで共有しながら開発しています。
よくいただくご質問
Q. まずどれから押さえるべきですか?
A. ②かなと思います。「型は実行時に消える」を知らないと、外部データの検証を丸ごと忘れる事故になるんですよね。
Q. anyは、絶対に使ってはいけませんか?
A. 最終手段としてはありますが、まずunknownを検討です(⑥)。「型が分からない」と「型チェックを止める」は別物なので、意図して使うのが大事です。
Q. 型を書くのが大変で、挫折しそうです…
A. ⑦のとおり、全部書く必要はありません。境界だけ書いて中は推論に任せる、から始めると、負担のわりに効果が大きいです。
Q. 未経験からでも、分かるようになりますか?
A. なります。型は「箱に貼るラベル」の比喩から入れて、⑧の「あり得ない状態を禁止する」まで、段階で理解できる分野なんです。
型は、未来のバグへの手紙です
今日の8つに共通していたのは、これでした。
型を書くことは、「このデータはこういうものだ」という約束を、コンパイラと未来の自分に残すことです。①の過ちを型で封じ、⑧であり得ない状態を消す。
型は制約ではなく、バグが生まれる前に捕まえる網。面倒がる対象ではなく、味方につける相手なんですよね。
みなさんが型に助けられた(あるいは泣かされた)話、よかったら教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。