TypeScriptの「構造的部分型」という名の甘美な罠を、Brandパターンでねじ伏せる
フロントエンドの現場で、こんな苦い経験はないだろうか?
「ユーザーIDと注文ID、どっちもただの `string` だから、引数に間違えて渡してもコンパイラは何も文句を言わない」
TypeScriptは「構造的部分型(Structural Subtyping)」を採用している。要は「中身が同じなら同じ型とみなす」という合理的な思想だ。しかし、この合理性が大規模なアプリケーションでは時に牙を剥く。IDの取り違えによるバグは、往々にしてデバッグが困難な「沈黙の不具合」を招く。
今日は、TypeScriptの標準仕様の隙間を縫って、Nominal Typing(名前による型判定)を擬似的に実現する「Brandパターン」について、実務の現場目線で深掘りしていこう。
—
なぜ「構造」だけでは不十分なのか
ブラウザのJavaScriptエンジンから見れば、TypeScriptの型などコンパイルの瞬間に消え去る蜃気楼に過ぎない。実行時に `UserId` か `OrderId` かを判別するメタデータは存在しない。
しかし、開発体験(DX)と堅牢性を両立させたい我々にとって、「型システム上で意味的に区別すること」は必須要件だ。ここで登場するのが、特定のプロパティを付与して「これはこの型である」と刻印を押すBrandパターンである。
実践:Brandパターンの実装
まずは、現場で即座に使えるボイラープレートを見てほしい。
// 独自の「ブランド」を付与するためのユーティリティ型
type Brand
// 型定義:ID系はこれでラップするだけで別物になる
type UserId = Brand
type OrderId = Brand
// ユーザーIDを作成する関数(キャストが必要だが、ここだけ我慢)
const createUserId = (id: string): UserId => id as UserId;
const createOrderId = (id: string): OrderId => id as OrderId;
function processOrder(userId: UserId, orderId: OrderId) {
console.log(`Processing ${orderId} for ${userId}`);
}
const uid = createUserId(‘user_123’);
const oid = createOrderId(‘order_999’);
// ✅ 正常:型が一致している
processOrder(uid, oid);
// ❌ コンパイルエラー:
// ‘OrderId’ 型の引数は ‘UserId’ 型の引数に割り当てられません。
// processOrder(oid, uid);
この手法の「現場的な」強み
1. ゼロコストの抽象化:
`__brand` プロパティはコンパイル後のJSには残りうるが、実務上のオーバーヘッドは皆無に等しい。ランタイムでクラスのインスタンスを生成して `instanceof` でチェックするような、大げさなことをする必要はない。
2. サニタイズの強制力:
`createUserId` のようなファクトリー関数を用意することで、IDのバリデーション(桁数や正規表現チェック)を型変換と同時に行わせるという「強制的な設計」に持ち込める。
3. 型互換性の遮断:
TypeScriptの構造的部分型を逆手に取り、あえて存在しない(あるいは特定の)プロパティを混ぜることで、意図しない型混入を物理的に弾くことができる。
知っておくべき「落とし穴」
ただし、このパターンには一点だけ注意が必要だ。`as` によるキャストを多用することになるため、「型定義が正しい」という前提条件を崩すと、コンパイラは無力になる。
また、複雑な型演算を行う際に、`__brand` が消えてしまうことがある。その場合はユーティリティ型を駆使してブランドを再付与する必要があるが、それは「型が正しく定義されているか」を見直す良い機会でもある。
—
シニアからのアドバイス:やりすぎには注意
このBrandパターン、非常に強力だが、「なんでもかんでもBrandにする」のは悪手だ。
例えば、単なる設定値や、アプリケーション内で一度も混同されるリスクのない単純なデータまでBrand化すると、コードベースが `as` だらけになり、可読性が著しく低下する。「本当にIDが混ざるリスクがある境界線」を見極めて、そこだけに適用するのが、枯れたエンジニアの流儀だ。
TypeScriptは、ツールだ。公式マニュアルを暗記するのではなく、その裏にある思想を理解し、現場の泥臭い課題を解決するために「型を捻じ曲げる」勇気を持ってほしい。
さあ、今日のデプロイから、あなたのアプリケーションをもう少しだけ堅牢にしてみないか?

コメント