はじめに
『プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発』(Boris Cherny 著、今村 謙士 監訳、原 隆文 訳、オライリー・ジャパン)を読みました。
TypeScriptの入門書は数多くありますが、本書は 「型システムそのものを設計の道具として使いこなす」 ことに重心を置いた一冊です。文法の解説にとどまらず、型チェッカーがなぜそう判断するのか、その理屈まで踏み込んでくれます。
この記事では、設計やアーキテクチャに関心のあるエンジニアの視点で、本書の内容と読みどころ、読む際の注意点をまとめます。
本記事は書評であり、書籍の内容を要約・再構成したものです。コード例は本書の趣旨を踏まえて筆者が書き起こしたもので、詳細な説明や正確な記述は書籍をご確認ください。
目次
- 本書の概要
- 本書の構成
- 各部の読みどころ
- 設計の観点で刺さったポイント
- 出版後に変わった点と補足
- 向いている読者・向かない読者
- まとめ
1. 本書の概要
| 項目 | 内容 |
|---|---|
| 書名 | プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発 |
| 著者 | Boris Cherny |
| 監訳 | 今村 謙士 |
| 訳 | 原 隆文 |
| 出版社 | オライリー・ジャパン |
| 原著 | Programming TypeScript(2019年) |
| 日本語版 | 2020年 |
著者が掲げる本書の目的は2つです。
- TypeScriptがどう動作するかを深く理解してもらうこと(理論)
- 本番で通用するコードの書き方を示すこと(実践)
序盤は理論寄り、終盤は実践寄りで、中盤で両者が混ざり合う構成になっています。「型とは何か」から始めて、最後はビルド・デプロイまでを一気通貫で扱うので、読み終える頃には言語機能の知識だけでなく、プロジェクト運用の見通しも得られます。
2. 本書の構成
全体は14章で、おおまかに次のように区切れます。
| 章 | テーマ | ひとことで言うと |
|---|---|---|
| 1 | イントロダクション | なぜ型安全性が必要か |
| 2 | TypeScript:全体像 | コンパイラーと型システムの仕組み |
| 3 | 型について | プリミティブ、オブジェクト、配列、タプル、列挙型 |
| 4 | 関数 | 呼び出しシグネチャ、オーバーロード、ジェネリクス |
| 5 | クラスとインターフェース | オブジェクト指向の型付け |
| 6 | 高度な型 | 変性、絞り込み、条件型など |
| 7 | エラー処理 | 例外に頼らない設計 |
| 8 | 非同期プログラミング・並行処理・並列処理 | Promise、async/await、Worker |
| 9 | フロントエンドとバックエンドのフレームワーク | 実アプリへの適用 |
| 10 | 名前空間とモジュール | コードの分割と公開 |
| 11 | JavaScriptとの相互運用 | 型宣言と段階的移行 |
| 12 | TypeScriptのビルドと実行 | tsconfigとプロジェクト構成 |
| 13 | デプロイ | 本番稼働に向けて |
| 14 | おわりに | 総括 |
多くの章末に練習問題があり、解答はオンラインで公開されています。手を動かして確かめる前提の作りです。
3. 各部の読みどころ
3.1 序盤(1〜2章):型安全性とコンパイラーのメンタルモデル
本書は「型安全性とは、型を使ってプログラムが不正なことをするのを防ぐこと」という定義から始まります。
JavaScriptは、数値と配列を足すような明らかな誤りでも、できる限り動かそうとします。その結果、ミスに気づくタイミングが 実行時 になります。TypeScriptはこれを エディターで入力している最中 に前倒しします。この「フィードバックが得られる時点」の違いが、本書全体を貫くテーマです。
2章では、コンパイルの流れが整理されます。
- ソースコードをASTに解析する
- 型チェックを行う
- JavaScriptコードを出力する
ここで押さえるべき点は、型は出力されるJavaScriptに影響しない ということです。型は型チェックのためだけに使われます。この理解があると、「型を書き換えても実行時の挙動は変わらない」「ただし実行時に値を検査してくれるわけではない」という両面が腑に落ちます。
またTypeScriptは、すべてに型注釈を要求する言語と、すべてを推論する言語の中間にあります。本書は一貫して「必要な場所にだけ注釈を書き、残りは推論に任せる」スタイルを勧めています。
3.2 中盤前半(3〜4章):型を「値の集合と操作」として捉える
3章では、型を 値と、その値に対してできる操作の集まり と定義します。この定義は、後の合併型・交差型を集合の和・積として理解するための土台になります。
印象に残ったのは、any と unknown の対比です。
// any:何でも許してしまい、型チェックが働かない
let a: any = 666
let b: any = ['danger']
let c = a + b // エラーにならない
// unknown:絞り込むまで使えない
let x: unknown = 30
// let y = x + 10 // エラー
if (typeof x === 'number') {
let z = x + 10 // OK
}
型が分からない値には any ではなく unknown を使い、絞り込んでから使う ことを強制する。小さな選択ですが、コードベース全体の安全性に効いてきます。
4章の関数では、次のような内容が扱われます。
- オプション・デフォルト・レストパラメーター
-
thisの型付け - ジェネレーターとイテレーター
- 呼び出しシグネチャと文脈的型付け
- オーバーロードとジェネリクス
特に 呼び出しシグネチャ は、関数を値としてではなく「型」として設計する視点を与えてくれます。インターフェースの契約を先に型で決め、実装は後から埋める、という設計の流れがそのまま書けます。
type Log = (message: string, userId?: string) => void
const log: Log = (message, userId = 'anonymous') => {
console.log(new Date().toISOString(), message, userId)
}
型を先に決めると、実装側の引数や戻り値の型は文脈から推論されます。注釈の重複が減り、変更にも強くなります。
3.3 中盤後半(5〜6章):クラス設計と高度な型
5章はクラスとインターフェースです。TypeScriptの型システムが 構造的型付け である点が、設計に大きく影響します。名前ではなく形状が合えば代入できるので、継承階層に縛られない柔軟な設計ができます。
そして本書の山場が 6章「高度な型」 です。扱われるテーマは広範です。
- 型の関係とサブタイプ、変性(共変・反変)
- 割り当て可能性
- 型の拡大と
constアサーション - 過剰プロパティチェック
- 型の絞り込みとタグ付き合併型
- 総合性チェック
- 高度なオブジェクト型とマップ型
- 条件型と
infer - 型アサーションと名目型のエミュレーション
特に変性の説明が丁寧です。「なぜ関数の戻り値は共変で、パラメーターは反変なのか」を、鳥の継承関係を使った具体例で導き出します。暗記するのではなく、型エラーが出たときに理由を思い出せる ようにするという姿勢が、本書らしいところです。
3.4 終盤(7〜9章):エラー処理・非同期・フレームワーク
7章では、例外に頼らずエラーを型で表現する方向性が示されます。null を返す、例外を投げる、結果を表す型を返す、といった選択肢のトレードオフを比較できます。
8章は非同期処理です。コールバックからPromise、async/await までの流れを型の観点で整理し、並行処理・並列処理の考え方にも触れます。
9章では、フロントエンドとバックエンドのフレームワークとの連携が扱われます。型安全なAPIの設計例もあり、フロントとサーバーで型を共有する発想は、スケールするアプリケーションを考えるうえで参考になります。
3.5 終盤(10〜13章):モジュール、移行、ビルド、デプロイ
実プロジェクトで差がつくのは、むしろこの部分かもしれません。
- 10章:名前空間とモジュール、宣言のマージ
- 11章:型宣言ファイルの扱い、JavaScriptからの 段階的な移行
-
12章:
tsconfig.jsonの設計、ビルドと実行 - 13章:本番環境へのデプロイ
11章の段階的移行は、既存のJavaScript資産を抱える現場で特に役立ちます。「全部を一度に書き換えない」ための現実的な手順が示されており、strict を最初から有効にできないプロジェクトでも、少しずつ安全性を引き上げる道筋が見えます。
4. 設計の観点で刺さったポイント
ここからは、設計やアーキテクチャに関心のある立場で、特に刺さった点を挙げます。
4.1 「不正な状態を表現できなくする」発想
タグ付き合併型は、設計上とても強力です。状態ごとに必要なデータが異なるとき、フラグの組み合わせで表現すると不正な組み合わせが生まれます。合併型で表現すれば、そもそも 不正な状態を型として書けなくなります。
type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string }
| { status: 'error'; error: Error }
function render(state: RequestState): string {
switch (state.status) {
case 'idle':
return '待機中'
case 'loading':
return '読み込み中'
case 'success':
return state.data
case 'error':
return state.error.message
}
}
switch の分岐を網羅していなければコンパイラーが指摘してくれる、いわゆる 総合性チェック も、リファクタリングや仕様追加の際の安全網になります。
4.2 推論を信頼し、注釈は境界に置く
本書は、明示的な型の乱用を戒めています。誤った型を覆い隠すのではなく、表面化させるほうがコードは明瞭で安全になる、という考え方です。
設計に落とし込むと、次のような方針になります。
- 関数やモジュールの 境界(公開API) には型を明示する
- 内部の実装は推論に任せる
- 型が変わったら、影響箇所がエラーとして浮かび上がる
境界を型で固め、内側は軽く保つ。アーキテクチャの責務分離と、考え方がよく似ています。
4.3 イミュータブルなデータ構造を型で支える
本書は、スプレッド構文を使ってデータ構造をイミュータブルに保つことを勧めています。さらに readonly や as const を使えば、その意図を型に刻めます。
const config = {
env: 'production',
retries: 3,
} as const
// config.retries = 5 // エラー:読み取り専用
変更可能な状態を減らすことは、並行処理や状態管理の複雑さを下げる基本戦略です。型システムがそれを後押ししてくれる点は心強いです。
4.4 構造的型付けと名目型のエミュレーション
構造的型付けは柔軟ですが、UserId と OrderId がどちらも string だと、取り違えても気づけません。本書はこの問題に対して、名目型をエミュレートする手法を紹介しています。
type Brand<T, B> = T & { readonly __brand: B }
type UserId = Brand<string, 'UserId'>
type OrderId = Brand<string, 'OrderId'>
const toUserId = (s: string) => s as UserId
const toOrderId = (s: string) => s as OrderId
function findUser(id: UserId) { /* ... */ }
const orderId = toOrderId('o-1')
// findUser(orderId) // エラー:OrderIdはUserIdに代入できない
ドメインの概念を型で区別できると、ドメイン駆動設計のように「意味の違いを守る」設計とも相性がよくなります。
4.5 any との付き合い方
本書は any を、あくまで最後の手段として扱います。ただし現実の開発では、レガシーコードや型の不正確なライブラリとの接点で、理想的でないコードを書く場面もあります。本書はその現実も認めたうえで、「妥協しない努力をする理由」を説明しています。
完璧主義を押し付けるのではなく、どこまで型で守るかをチームで決めるための判断材料をくれる、という印象を受けました。
5. 出版後に変わった点と補足
日本語版の出版は2020年です。その後のエコシステムの変化もあるため、読む際には次の点を意識すると、より実務に結びつけやすくなります。
以下は、本書の記述と現在の一般的な運用との間で差が出やすいポイントです。細かな仕様は、最新の公式ドキュメントで確認してください。
| 項目 | 本書での扱い | 補足 |
|---|---|---|
| リンター | TSLintを使用 | 本書の訳注にもあるとおり、TSLintは非推奨です。現在はESLintとTypeScript用のプラグインの組み合わせが一般的です |
| 列挙型 | 利用を控えるよう推奨 | 文字列リテラルの合併型で代替する設計が広く好まれます |
| 新しい型機能 | 本書の時点までの機能 | 本書の後に追加された型機能も多いため、公式のリリースノートで補完すると理解が深まります |
bigint |
一部エンジンで未対応と注意 | 実行環境の対応状況は時期によって変わるため、現在の対象環境で確認が必要です |
本書の価値は、個別の機能名よりも 型システムの考え方 にあります。変性、絞り込み、構造的型付けといった原理は、言語が進化しても通用します。新機能は、その原理の延長として学べば、無理なく吸収できます。
6. 向いている読者・向かない読者
向いている読者
- 他の言語(C#、Java、Python、Kotlinなど)の経験があり、TypeScriptの型システムを 体系的に 理解したい方
- JavaScriptの経験はあるが、型を「なんとなく」書いている方
- 型エラーの意味が分からず、
anyやasで逃げがちな方 - 設計の段階で型を活用し、大規模化に耐えるコードを書きたい方
- 既存のJavaScriptプロジェクトを、段階的にTypeScriptへ移行したい方
向かない読者
- プログラミング自体が初めてで、関数や変数、クラスの基礎から学びたい方
- 特定のフレームワークの使い方だけを、最短で知りたい方
- 最新の機能だけを網羅したリファレンスを求めている方
本書は、関数、変数、クラス、エラーの基礎を理解している人を対象としています。ある程度のプログラミング経験がある前提で読むと、ちょうどよい難易度です。
7. 読み進め方のコツ
最後に、読み進めるうえで役立ったコツを共有します。
- エディターを開いて読む:型の推論結果は、変数にマウスを載せると確認できます。本書の例を写して試すと理解が段違いです
-
strictを有効にする:本書の例は厳格なチェックが前提です。設定を揃えて読み進めると、挙動の差に戸惑いません - 6章は何度も往復する:他の章から6章への参照が多くあります。1周目は概要をつかみ、2周目で細部を詰める読み方が合います
- 練習問題を解く:答えを読むより、自分で試した結果から得られる洞察のほうが深くなります
- 自分のコードに当てはめる:読んだ内容を、手元のプロジェクトの型定義に適用してみると定着します
まとめ
『プログラミングTypeScript』は、TypeScriptの型システムを 理論と実践の両面 から理解するための、骨太な一冊です。
- 型を「値と操作の集まり」として捉える視点が、全体を貫いている
- 変性や絞り込みなど、つまずきやすい概念を具体例で導いてくれる
- 型を設計の道具として使う発想(不正な状態を作らせない、境界に型を置く)が学べる
- 移行・ビルド・デプロイまで扱うため、実務の見通しが立つ
- 出版後に変わった点はあるが、原理の理解は今でも通用する
「TypeScriptを書けるようになった」の次の段階、つまり 型で設計できるようになる ためのステップとして、おすすめできる書籍です。
最後までお読みいただき、ありがとうございました。
参考
- 書名:プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発
- 著者:Boris Cherny/監訳:今村 謙士/訳:原 隆文
- 出版社:オライリー・ジャパン
- 本書の書籍ページ:http://www.oreilly.co.jp/books/9784873119045
- 練習問題の解答:https://github.com/oreilly-japan/programming-typescript-ja
- TypeScript公式ドキュメント:https://www.typescriptlang.org/docs/