---
title: TSKaigi 2026 「AI時代に考える、Branded Typesで実現する堅牢な型付け」
tags: 
author: [yuta-ike](https://www.docswell.com/user/4136989)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/YE9PL2LDJ3.jpg?width=480
description: https://2026.tskaigi.org/talks/62
published: May 23, 26
canonical: https://www.docswell.com/s/4136989/Z6NJ78-tskaigi2026
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/YE9PL2LDJ3.jpg)

AI時代に考える、
Branded Typesで実現する
堅牢な型付け
2026.05.23 TSKaigi 2026
@yuta-ike
エムスリー / エンジニアリンググループ


# Page. 2

![Page Image](https://bcdn.docswell.com/page/GE8DX5X5ED.jpg)

型エラーが出ると
嬉しいですよね
😉


# Page. 3

![Page Image](https://bcdn.docswell.com/page/LELM8Y837R.jpg)

Q. わたしたちは何のために型を書くのか


# Page. 4

![Page Image](https://bcdn.docswell.com/page/4JMY6N6MJW.jpg)

Q. わたしたちは何のために型を書くのか
A. 型エラーを出すため


# Page. 5

![Page Image](https://bcdn.docswell.com/page/PJR9PDPL79.jpg)

Q. どんなときに型エラーが出てほしいだろう


# Page. 6

![Page Image](https://bcdn.docswell.com/page/PEXQ31N6JX.jpg)

Q. どんなときに型エラーが出てほしいだろう
A. 実装が間違っているとき


# Page. 7

![Page Image](https://bcdn.docswell.com/page/3EK9Y2NGED.jpg)

実装があっていて型チェックも通る
嬉しい


# Page. 8

![Page Image](https://bcdn.docswell.com/page/L73W94V575.jpg)

実装が間違っていて型チェックが通らない
正常


# Page. 9

![Page Image](https://bcdn.docswell.com/page/87DKGQ8YJG.jpg)

実装があっていて、型チェックが通らない
複雑な実装で型推論が追いつかない
止むを得ずimmutableな処理をしている
（letの利用、再代入、Record&lt;string, any&gt;）


# Page. 10

![Page Image](https://bcdn.docswell.com/page/VJPK3L82E8.jpg)

実装が間違っていて、型チェックが通る
一番避けたい


# Page. 11

![Page Image](https://bcdn.docswell.com/page/2EVV4QNXEQ.jpg)

実装が間違っていて、型チェックが通る
型の表現力には制限があり、
完全に避けることはできない
function add(a: number, b: number): number {
retrun 0
}
※ 明らかに実装が間違っているが、型レベルで検知はできない（型チェックが通る）


# Page. 12

![Page Image](https://bcdn.docswell.com/page/57GL1WKREL.jpg)

実装が間違っていて、型チェックが通る
型の表現力には制限があり、
完全に避けることはできない
それでも、
このパターンは可能な限り小さくしたい


# Page. 13

![Page Image](https://bcdn.docswell.com/page/4EQYD3NYJP.jpg)

そのためには、
より厳格な型を適用する必要がある。
そのための手段の1つが Branded Types。


# Page. 14

![Page Image](https://bcdn.docswell.com/page/KJ4WZ1GZ71.jpg)

目標
Branded Typesを実際のアプリケーションで活用することで、
値の種類や属性・状態を型にエンコードすることができ、
堅牢なプログラミングにつながる
....という感覚を共有したい！


# Page. 15

![Page Image](https://bcdn.docswell.com/page/LE1YRGDD7G.jpg)

Branded Typesとは
type User = {
id: string // ユーザーID
name: string // 氏名
date: Date // 生年月日
}
type Book = {
id: string // 本のID
name: string // 本のタイトル
date: Date // 出版年
}
同じ構造の型がある場合、TypeScriptはこれらを同一の型と
みなしてしまう（構造的部分型）


# Page. 16

![Page Image](https://bcdn.docswell.com/page/GEWG1KY8J2.jpg)

Branded Typesとは
type User = {
id: string // ユーザーID
name: string // 氏名
date: Date // 生年月日
__type: &quot;User&quot;
}
type Book = {
id: string // 本のID
name: string // 本のタイトル
date: Date // 出版年
__type: &quot;Book&quot;
}
そこで、型の名前に対応する架空のプロパティを追加すること
で、2つの構造を区別することができる（Branded Types）


# Page. 17

![Page Image](https://bcdn.docswell.com/page/47ZLPZX9J3.jpg)

Branded Typesとは
function createUser(user: User) { ... }
const book: Book = {
id: getUUID()
name: &quot;吾輩はエンジニアである&quot;,
date: new Date(2000, 0, 1)
} as Book
createUser(book) // TypeError: Book型の変数をUser型には代入できない
Branded Typesを使うと２つの型は完全に別の型として扱われるの
で、当然代入しようとすると型エラーになる（嬉しい）


# Page. 18

![Page Image](https://bcdn.docswell.com/page/YJ6WMZ4DJV.jpg)

プロダクト開発における Branded Types の
使いどころ
例1: ドメインモデルを区別するための利用
UserIDとBookID、UserとBookなどのドメインモデルの区別
例2: 値の状態や属性を表現する
Unvalidated, Validatedなど、値の状態や属性を表現する


# Page. 19

![Page Image](https://bcdn.docswell.com/page/GJ5MZWQ8J4.jpg)

例1: ドメインモデルを区別する
export type User = {
userId: string;
userName: string;
}
UserIDとUserNameは本来的には全く別の概念
偶々同じデータ構造（文字列）を持っているだけで、同じ型（string）が割り当
てられる
では、我々は何を持ってその値がUserIDであること理解するのか？


# Page. 20

![Page Image](https://bcdn.docswell.com/page/LE3W94VGE5.jpg)

ドメインモデルを区別する
await getUserById(myUserId); // myUserId はユーザーIDであろう  
const id = user.id;
// ... 
console.log(id); // この id は、文脈的にユーザーIDっぽい  
const name = user.id; // 変数名は UserName っぽいが、実体は UserID
変数名から推測する（myUserIdはユーザーIDを表しているに違いない）
周辺の処理・コンテキストから推測する（const id = user.id という処理が
あれば、idはユーザーIDに違いない）
どちらも人間の認知能力を前提とした曖昧なアプローチ。


# Page. 21

![Page Image](https://bcdn.docswell.com/page/8EDKGQ8N7G.jpg)

ドメインモデルを型レベルで表現する
export type UserID = string &amp; { __type: &quot;userID&quot; }  
const id = user.id; 
// ^? const id: UserID 型レベルでUserID型であることがわかる（代入しても） 
const hoge = id;
// ^? const hoge: UserID 代入しても情報が失われない
 
updateProfileName(userId);
// TypeError: UserID is not UserName UserNameを期待しているところにUserIDを指定すると型エ
ラーで落とせる
UserIDやUserNameをBranded Typesで定義することで、型システムレベルで
ドメインモデルを表現できる。


# Page. 22

![Page Image](https://bcdn.docswell.com/page/V7PK3L8NJ8.jpg)

Branded Typesを利用した
ドメインモデルの定義
ドメインの識別を型システムに担保させる
→ 言い換えると、ビジネスドメインを型に埋め込む行為
変数名やコメントなどの曖昧な表現に依存せずにすむ
代入しても型が失われない


# Page. 23

![Page Image](https://bcdn.docswell.com/page/2JVV4QNYJQ.jpg)

例2: 値の状態や属性を表現する


# Page. 24

![Page Image](https://bcdn.docswell.com/page/5EGL1WKWJL.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
要件: ユーザー名は英数字のみ
① 入力値が string かチェック
② ユーザー名の要件を満たすか
正規表現をチェック
③ DBにユーザー情報を保存
function isValidUserName(input: string) {
return/[^0-9a-zA-Z]/.test(input);
}
app.post(async (c) =&gt; {
const input = c.json().userId; // unknown
// inputがstring型であることをチェック
if (typeof input !== &quot;string&quot;) {
throw new ValidationError(&quot;文字列以外が指定されました&quot;);
}
// inputがユーザー名の要件を満たすかチェック
if (isValidUserName(input)) {
throw new ValidationError(&quot;利用できない文字が含まれています&quot;);
}
// DBに保存
await userRepo.createUser({ id: getUUID(), name: input });
});


# Page. 25

![Page Image](https://bcdn.docswell.com/page/4JQYD3NQ7P.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
要件: ユーザー名は英数字のみ
① 入力値が string かチェック
② ユーザー名の要件を満たすか
正規表現をチェック
③ DBにユーザー情報を保存
// input: unknown
① の処理実行
// input: string
app.post(async (c) =&gt; {
const input = c.json().userId; // unknown or any
// inputがstring型であることをチェック
if (typeof input !== &quot;string&quot;) {
throw new ValidationError(&quot;文字列以外が指定されました&quot;);
}


# Page. 26

![Page Image](https://bcdn.docswell.com/page/K74WZ1GYE1.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
要件: ユーザー名は英数字のみ
① 入力値が string かチェック
② ユーザー名の要件を満たすか
正規表現をチェック
③ DBにユーザー情報を保存
// input: unknown
① の処理実行
// input: string
② の処理実行
// input: string
型に変化がない
= 型レベルでは何もやって
いないと同じ?
function isValidUserName(input: string) {
return/[^0-9a-zA-Z]/.test(input);
}
if (isValidUserName(input)) {
throw new ValidationError(&quot;利用できない文字が含まれています&quot;);
}


# Page. 27

![Page Image](https://bcdn.docswell.com/page/LJ1YRGDNEG.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
要件: ユーザー名は英数字のみ
① 入力値が string かチェック
② ユーザー名の要件を満たすか
正規表現をチェック
③ DBにユーザー情報を保存
function isValidUserName(input: string) {
return/[^0-9a-zA-Z]/.test(input);
}
if (isValidUserName(input)) {
throw new ValidationError(&quot;利用できない文字が含まれています&quot;);
}
実装を見るとちゃんと仕事をしている。
でも仕事内容が関数のインタフェースに表出していない
（なぜ？）


# Page. 28

![Page Image](https://bcdn.docswell.com/page/GJWG1K8M72.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
unknown型
①の
バリデーション
unknown型
string型


# Page. 29

![Page Image](https://bcdn.docswell.com/page/4EZLPZ8M73.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
string型
②の
バリデーション
string型
アルファベット+数字型


# Page. 30

![Page Image](https://bcdn.docswell.com/page/Y76WMZP57V.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
string型
②の
バリデーション
string型
アルファベット+数字型
これに対応する型が存在しない


# Page. 31

![Page Image](https://bcdn.docswell.com/page/G75MZWKG74.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
string型
②の
バリデーション
string型
アルファベット+数字型
代わりにstring型で代用している


# Page. 32

![Page Image](https://bcdn.docswell.com/page/9J29RQWDER.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
string型
②の
バリデーション
string型
アルファベット+数字型
この小さい長方形の型を作ってあげる
→ Branded Types


# Page. 33

![Page Image](https://bcdn.docswell.com/page/DEY4DWLMJM.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
今回はValidAsUserNameという名
前で型を定義。
あわせてこの型の値を得るための
ファクトリ関数も定義。
ValidAsUserName型の値は 、ファ
クトリ関数の戻り値からしか得られ
ない
ファクトリ関数の中では必ず英数字
のみの文字列かを検証する（バリ
デーション）
→ ValidAsUserName型は英数字のみ
から成り立つ文字列を表す型である
// Branded Typesの定義
export const ValidAsUserName = string &amp; { __type:
&quot;ValidAsUserName&quot; }
// ValidAsUserNameを得るための関数を定義
export const ValidAsUserName = (input: string) =&gt; {
// 関数内でバリデーションを定義
if (/[^0-9a-zA-Z]/.test(input)) {
throw new Error(&quot;Invalid&quot;)
}
return input as ValidAsUserName
}


# Page. 34

![Page Image](https://bcdn.docswell.com/page/VJNY694378.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
さらに、userRepoは
ValidAsUserNameを受け取るように
する。
// 元の実装
function createUser(
userId: string,
userName: string
) { ... }
createUser関数の事前条件（引数として与え
られる値の範囲）を制限することができ、関数
実装時に考えることが1つ減る。
（= 記号等が含まれるケースを考慮しなくて良
い）
// Branded Typesを利用して、受け入れ可能な値をより厳密に表明
function createUser(
userId: string,
userName: ValidAsUserName
) { ... }
そのため、ユニットテストなどを削減できる可
能性がある。


# Page. 35

![Page Image](https://bcdn.docswell.com/page/YE9PL2QPJ3.jpg)

お題 Githubのようなユーザー名を指定して登録できるサービス
要件: ユーザー名はアルファベットと
数字のみ
① 入力値が string かチェック
② ユーザー名の要件を満たすか
正規表現をチェック
③ DBにユーザー情報を保存
// input: unknown
① の処理実行
// input: string
② の処理実行
// input: ValidAsUserName
③の関数はValidAsUserNameのみを
受け入れる
→ ②の処理を忘れた場合は③の呼び出しで型エラー（嬉しい）
→ バリデーションをしないと型エラーが出るため、値の検証を強制できる


# Page. 36

![Page Image](https://bcdn.docswell.com/page/GE8DX5GXED.jpg)

ValidAsUserName型とは何なのか
string型の中でも「英数字のみからなる」という性質を持つ値
言い換えると、「英数字のみからなる」という性質を型で表現している
ほかにも、様々な意味を持つ型を定義可能
UserID型は「ユーザーID」という意味を持つ（= 値の意味を型にエン
コードする）
UnvalidatedUserName型は「検証前のユーザー名」という意味を
持つ（= 値の状態を型にエンコードする）


# Page. 37

![Page Image](https://bcdn.docswell.com/page/LELM8YGN7R.jpg)

Branded Typesで型に意味をエンコードする
Branded Typesを値に意味を持たせる手段として使う。
変数名やコメントと違い、代入しても追跡が途絶えない。
TypeScriptの型システムのメリットを享受でき、堅牢。
データ構造（string）ではなく、ビジネス上の概念を型にマッピング
する。
一部のドメインロジックの検証を型システムに載せるイメージ


# Page. 38

![Page Image](https://bcdn.docswell.com/page/4JMY6NQQJW.jpg)

実際の開発チームでの活用
ほとんどのEntity + ValueObject、一部のプロパティはBranded Typesで定義
IDやEmailなどPrimary KeyになりうるものはBrand化
一方でそのほかの属性は通常通りプリミティブなtypeで定義
ユーティリティを取っている
Branded Typesはzod等の外部ライブラリを使わず自前で定義。データの詰め替え
はスプレッド構文を使うとそこまで発生しない。
コードベース全体は、型を全面に押し出したFunctionベースの構成（classはあまり使っ
ていない）


# Page. 39

![Page Image](https://bcdn.docswell.com/page/PJR9PD8K79.jpg)

現時点での所感
型チェックが通っているならとりあえず安心という感覚がある
もちろん全てではないので、テストもちゃんと書く
人間が実装している場合も、型エラーによって処理の漏れや考慮漏れに気づくことが
多く便利
一方で、あくまでランタイムに影響を与えないことを意識することは必要（as等で簡
単に嘘をつける）


# Page. 40

![Page Image](https://bcdn.docswell.com/page/PEXQ3185JX.jpg)

まとめ
Branded Typesを実際のアプリケーションで活用することで、
値の種類や属性・状態を型にエンコードすることができ、
堅牢なプログラミングにつながる
→ この堅牢性がAI時代の武器になる（はず）


# Page. 41

![Page Image](https://bcdn.docswell.com/page/3EK9Y2KRED.jpg)

ちょっとだけ蛇足


# Page. 42

![Page Image](https://bcdn.docswell.com/page/L73W94ZG75.jpg)

なぜTypeScriptを利用するか
（私は）ランタイムエラーを防ぐ目的が大きいと思っている
ex. オブジェクトだと思ってたらnullだった
user.greet()
// Uncaught TypeError: Cannot read properties of null
TypeScriptはデータ構造を型として扱う
ランタイムエラーは防げるが、あくまでデータ構造ベース。
ドメインロジックを検証することはできない


# Page. 43

![Page Image](https://bcdn.docswell.com/page/87DKGQRNJG.jpg)

Branded Typesを使う世界線
userIdとuserNameは、データ構造的には同じstring
でも実際、我々がそこに見出している意味は異なる。
この意味をそのまま型の世界にエンコードしてあげることで、より堅牢なプロ
グラミングが可能になる（はず）


# Page. 44

![Page Image](https://bcdn.docswell.com/page/VJPK3LWNE8.jpg)

Ask the Speaker
or 懇親会でお話しましょう
@yuta-ike
エムスリー / エンジニアリンググループ


