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); // ✅ 構造が同じだから通ってしまう!
                 // 意味的には間違いなのに...

UserIdPostId は意味が違いますが、形が同じなので互換性があります。これを防ぐには、前回の記事で紹介した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で防ぐ方法」を予定しています。