JavaScriptの「成功による失敗」と、Microsoftが出した答え
型を書く前に、なぜ型が必要なのかを知れ。それがTypeScriptを本当に使いこなす第一歩だ。
はじめに
「TypeScriptってJavaScriptに型をつけただけでしょ?」
そう思っているうちは、TypeScriptを半分しか使えていません。
TypeScriptは単なる「型付きJS」ではなく、JavaScriptが抱えた構造的な問題への、慎重に設計された答えです。
なぜ生まれたのか。何を解決しようとしたのか。どんな設計判断が行われたのか。
この背景を知ることで、型の書き方ではなく型の考え方が身につきます。
Chapter 1: JavaScriptの誕生と「想定外の成功」
10日間で作られた言語
1995年、Netscape社のエンジニアBrendan Eichは、わずか10日間でJavaScriptを設計しました。
当時の要件は明快でした:
- ブラウザ上でフォームのバリデーションをする
- 非エンジニアのデザイナーでも書ける
- Javaほど厳格でなくていい
つまり、**「ちょっとしたスクリプトを書くための言語」**として設計されたのです。
// 1995年に想定されていたJavaScriptの使われ方
function validateForm() {
if (document.getElementById("name").value === "") {
alert("名前を入力してください");
return false;
}
return true;
}
この規模なら動的型付けは合理的な選択でした。10行20行のスクリプトに型システムは過剰です。
誰も予想しなかったスケール
ところが、JavaScriptは想定をはるかに超えて普及しました。
1995年:フォームバリデーション(〜数十行)
↓
1999年:XMLHttpRequest登場。非同期通信が可能に
↓
2004年:Gmail公開。「Webアプリ」という概念が生まれる(数千〜数万行)
↓
2006年:jQuery登場。DOM操作が爆発的に広まる
↓
2009年:Node.js登場。サーバーサイドでもJSが使われ始める
↓
2010年代:SPA全盛期。フロントエンドのコードが数十万行規模に
↓
現在:フロント・バック・CLI・モバイル(React Native)・デスクトップ(Electron)
「10行用に設計された言語」が、数十万行のシステムを書くために使われるようになった。
これがJavaScriptの「成功による失敗」です。
Chapter 2: 動的型付けが大規模開発で引き起こす問題
問題1:実行するまでバグがわからない
動的型付けの最大の問題は、型エラーが実行時にしか発覚しないことです。
// これは全て「文法的に正しい」JavaScript
function calculateDiscount(price, rate) {
return price * rate;
}
calculateDiscount(1000, 0.1); // 100(意図通り)
calculateDiscount("1000", 0.1); // "10001000" (文字列の繰り返し!)
calculateDiscount(1000, "10%"); // NaN(意図と違う)
calculateDiscount(undefined, 0.1); // NaN(クラッシュ候補)
calculateDiscount(); // NaN(引数なしでも動く)
コードを書いた瞬間ではなく、ユーザーが本番環境で触った瞬間にエラーが起きる。
問題2:コードが「自己文書化」できない
// この関数、何を渡せばいいか読んでわかりますか?
function processOrder(order, options) {
// 200行の処理...
}
// 実際の呼び出しを見るまで、orderやoptionsの中身が分からない
processOrder(???, ???);
チームが5人になると「この関数に何を渡していいか」を全員が覚えられなくなります。
ドキュメントを書いても、実装との乖離が生まれます。コメントは嘘をつきますが、型は嘘をつきません。
問題3:リファクタリングへの恐怖
// User オブジェクトの "name" を "fullName" に変えたい
// でも... このプロパティはどこで使われている?
user.name // これは全部変える必要がある
response.name // これは変えなくていいかも?
data.name // これは別のオブジェクトかも?
大規模なJSコードベースでプロパティ名を変更するのは、地雷原を歩くような作業です。grep検索で全ての name を見つけて、文脈を読んで、1つ1つ判断していく。
これが積み重なると、エンジニアは「触ったら壊れるかもしれない」という恐怖からリファクタリングを避けるようになります。技術的負債の蓄積です。
問題4:IDEによるサポートの限界
動的型付けの言語では、IDEが型情報を持てません。
// IDEは user に何が入っているか分からない
function renderUser(user) {
user. // ← ここで補完候補が出ない
}
自動補完・定義ジャンプ・リファクタリング支援——現代の開発に欠かせないこれらのツールが、JavaScriptでは機能しません。
Chapter 3: Microsoftの問題意識と、TypeScriptの誕生
2010年頃のMicrosoftが抱えていた課題
当時のMicrosoftは、Visual Studioのような巨大なWebアプリケーションをJavaScriptで書こうとしていました。
社内で直面したのは、まさにChapter 2で述べた問題のすべてでした。
「JavaScriptのままでは、エンタープライズ規模の開発は無理だ」
この課題を解決するために動いたのが、Anders Hejlsbergでした。
Anders Hejlsberg という人物
HejlsbergはC#とDelphiの設計者です。静的型付け言語の設計に関して、世界最高峰の知見を持つエンジニアの一人です。
彼がTypeScriptの設計で下した判断は、単なる「JSに型を足す」というものではありませんでした。JavaScriptのエコシステム全体を壊さずに、型安全性を持ち込むという、非常に難しいバランスを取ることでした。
2012年10月1日、TypeScript 0.8がオープンソースとして公開されます。
TypeScript設計の3大原則
Hejlsbergたちが定めた設計原則は明快です。
原則1:JavaScriptの完全なスーパーセットであること
// 普通のJavaScriptはそのままTypeScriptとして動く
var x = 1;
function hello() { return "world"; }
const arr = [1, 2, 3];
これは当たり前に見えて、革命的な判断でした。
世の中にはすでに膨大なJavaScriptコードが存在していました。Microsoftが「新しい別の言語」を作っていたら、誰も移行しなかったでしょう。
「既存のJSコードを一切変えずに、.jsを.tsにリネームするだけで動く」
この互換性保証があったからこそ、TypeScriptは世界中に普及しました。
原則2:型はコンパイル後に「消える」
// TypeScriptのコード
function greet(name: string): string {
return `Hello, ${name}`;
}
const user: { id: number; name: string } = {
id: 1,
name: "Masaki"
};
// コンパイル後のJavaScript(型が完全に消える)
function greet(name) {
return `Hello, ${name}`;
}
const user = {
id: 1,
name: "Masaki"
};
TypeScriptの型情報はランタイムに存在しません。
これも深い設計判断です。型をランタイムに残すと、既存のJSエコシステムとの互換性が崩れます。ブラウザはJavaScriptしか実行できない。だから型は「開発時だけに存在する安全網」として設計されました。
原則3:型推論で「書く量を最小化」する
JavaやC#のような言語では、型を多く書く必要があります:
// Java:型を全部書く
HashMap<String, List<User>> usersByGroup = new HashMap<String, List<User>>();
TypeScriptはこの冗長さを避けるため、強力な型推論を内蔵しています:
// TypeScript:推論に任せる
const usersByGroup = new Map<string, User[]>(); // Map<string, User[]> と推論
const nums = [1, 2, 3]; // number[] と推論
const doubled = nums.map(n => n * 2); // number[] と推論(nもnumberと推論)
「型安全性」と「書きやすさ」のトレードオフを、型推論によって解決しています。
Chapter 4: 最も重要な設計判断——構造的型付けを選んだ理由
2種類の型システム
世界の静的型付け言語は、大きく2つの型システムに分かれます。
名前的型付け(Nominal Typing):JavaやC#が採用
// Java
class Point { int x; int y; }
class Vector { int x; int y; }
void plot(Point p) { ... }
Vector v = new Vector();
plot(v); // ❌ コンパイルエラー
// Point と Vector は「名前が違う」から別の型
名前的型付けでは、型の名前(またはクラス継承・インターフェース実装の宣言)が互換性の根拠になります。
構造的型付け(Structural Typing):TypeScriptが採用
// TypeScript
interface Point { x: number; y: number }
interface Vector { x: number; y: number }
function plot(p: Point) { ... }
const v: Vector = { x: 1, y: 2 };
plot(v); // ✅ OK!
// Point と Vector は「形が同じ」から互換性がある
構造的型付けでは、型の「形(プロパティと型)」が互換性の根拠になります。
なぜTypeScriptは構造的型付けを選んだのか
これはHejlsbergたちが直面した根本的な問いでした。
もしTypeScriptが名前的型付けを採用していたら、こうなります:
// もしTSが名前的型付けだったら(仮想)
// jQueryのコード(型宣言なし)
$.ajax({ url: "/api", method: "GET" });
// TypeScript側でjQueryを使うには...
// jQueryの全ての関数・オブジェクトに「implements」が必要になる
// → 既存のJSライブラリ全てに手を入れないとTSで使えない
// → 現実的に不可能
JavaScriptの世界には、互いにimplements宣言などしていない無数のライブラリがあります。React、jQuery、lodash、Express——これらは全て「形が合えば動く」JavaScriptの文化(ダックタイピング)で書かれています。
「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」 ——ダックタイピングの定義
この文化を型システムで表現するには、構造的型付けしかありえなかったわけです。
構造的型付けの実際の挙動
interface Printable {
print(): void;
}
class Document {
print() { console.log("Document printed"); }
save() { /* ... */ }
}
class Image {
print() { console.log("Image printed"); }
resize() { /* ... */ }
}
// Document も Image も Printable を implements していない
// でも「形が合う」から渡せる
function printAll(items: Printable[]) {
items.forEach(item => item.print());
}
printAll([new Document(), new Image()]); // ✅ 両方OK
これがJavaScriptのダックタイピング文化と完全に一致します。
構造的型付けの「罠」も知っておく
設計上のトレードオフとして、構造的型付けには直感に反する動作があります。
interface UserId { value: string }
interface PostId { value: string }
function getUser(id: UserId) { /* ... */ }
const postId: PostId = { value: "post_123" };
getUser(postId); // ✅ 構造が同じだから通ってしまう!
// 意味的には間違いなのに...
UserId と PostId は意味が違いますが、形が同じなので互換性があります。これを防ぐには、前回の記事で紹介したBranded Typesが必要になります。
Chapter 5: TypeScriptがあえて「完全な型安全」を諦めた部分
TypeScriptには、意図的に型安全性を犠牲にしている部分があります。これを知ることで、型エラーの本質が理解できます。
any型の存在
// any は型システムの「非常口」
const data: any = fetchSomething();
data.nonExistentMethod(); // ✅ TSはチェックしない(実行時エラーになる)
完全な型安全を目指すなら any は存在してはいけません。でもTypeScriptが any を提供したのは、「段階的な型付け(Gradual Typing)」を実現するためです。
既存のJSコードをTSに移行する際、全てを一度に型付けするのは現実的ではありません。any は「まだ型をつけていない部分」を一時的に許容するための脱出口です。
型アサーション(as)の存在
const input = document.getElementById("myInput");
// input は HTMLElement | null と推論される
const value = (input as HTMLInputElement).value;
// ✅ 開発者が「これはHTMLInputElementだ」と断言できる
// TSはこの断言を信頼する(間違っていても実行時エラー)
型アサーションは「私は型システムより正確な情報を持っている」と宣言する機能です。これも型安全性の一部を開発者の責任に委ねています。
なぜそれでいいのか
TypeScriptの設計目標は「Sound(完全に型安全)」ではなく「Productive(生産性が高い)」でした。
公式ドキュメントにはこう書かれています:
TypeScript does not try to be perfectly sound. Instead, it strikes a balance between correctness and productivity.
完全な型安全を追求すると、表現できないコードが増え、開発体験が損なわれます。TypeScriptは**「現実のJavaScript開発で使える型システム」**を目指したのです。
Chapter 6: 2012年から現在への進化
バージョンごとの主要な進化
TypeScriptは単なる「型のあるJS」から、年々表現力豊かな言語へと進化しています。
// TS 2.0(2016):Nullable Types
let name: string | null = null; // null を明示的に扱えるように
// TS 3.0(2018):Unknown型
let value: unknown = getData();
if (typeof value === "string") { /* 型が絞られる */ }
// TS 4.1(2020):Template Literal Types
type EventName = `on${Capitalize<string>}`; // 型レベルの文字列操作
// TS 4.9(2022):satisfies演算子
const config = { port: 3000 } satisfies Record<string, number>;
// TS 5.0(2023):const型パラメータ
function identity<const T>(val: T): T { return val; }
各バージョンが追加した機能は全て、**「JavaScriptの現実の使われ方をより正確に型で表現する」**という方向性で設計されています。
TypeScriptが世界を変えた証拠
2023年のStack Overflow Developer Surveyによると、TypeScriptは**「最も人気のある言語」**の上位に常にランクインしています。
GitHubの統計では、TypeScriptのリポジトリ数はPythonを超える勢いで増加しています。
React、Vue、Angular、Next.js——主要なフロントエンドフレームワークは全てTypeScriptを第一級サポートしています。
まとめ:TypeScriptの「なぜ」を理解すると何が変わるか
問題:JSが大規模開発に耐えられなくなった
↓
答え①:スーパーセット設計(既存JSを捨てない)
答え②:型はコンパイル後に消える(JSエコシステムと共存)
答え③:型推論(書く量を最小化)
答え④:構造的型付け(JSのダックタイピング文化を型で表現)
答え⑤:意図的な型安全の妥協(現実の開発で使えることを優先)
TypeScriptの設計判断は全て「JavaScriptの世界を壊さずに、大規模開発を可能にする」という一点に収束しています。
これを理解した上で型を書くと、any を使いたくなる気持ちへの向き合い方が変わります。構造的型付けの挙動に驚かなくなります。型エラーのメッセージが何を伝えようとしているかが分かるようになります。
「型を書く」から「型で設計する」へ。 その第一歩は、TypeScriptがなぜ生まれたかを知ることです。
参考リソース
| リソース | 内容 |
|---|---|
| TypeScript Design Goals | 公式が定めた設計目標(必読) |
| Anders Hejlsberg – Introducing TypeScript | 設計者本人による解説動画 |
| TypeScript Deep Dive | 型システムの詳細な解説 |
| TypeScript公式ブログ | バージョンごとの設計意図 |
この記事が参考になったら、シェアやコメントをいただけると励みになります。 次回は「構造的型付けの罠とBranded Typesで防ぐ方法」を予定しています。