ADR-004: TypeScript採用
ステータス
採用済み
意思決定者
- 太田裕貴(CTO)
- 開発チーム全員
決定日
2021年頃(プロジェクト初期)
背景(Context)
Qudenは、フロントエンド(Next.js)、バックエンド(NestJS)、Chrome拡張、デスクトップアプリ(Electron)など、複数のJavaScript/TypeScript プロジェクトで構成されています。
現状の課題
- JavaScript(動的型付け)では、大規模プロジェクトでの型安全性が確保できない
- エディタのコード補完が不十分で、開発効率が低い
- リファクタリング時のバグ混入リスクが高い
- API通信のリクエスト/レスポンスの型が保証されない
- チーム開発でコードの可読性・保守性を確保したい
解決したいこと
- コンパイル時の型チェックによる早期のバグ検出
- エディタのインテリセンス(コード補完、型推論)による開発効率向上
- リファクタリングの安全性向上
- コードの可読性・保守性の向上
- チーム開発での一貫性確保
要件(Requirements)
機能要件
- 静的型付けによる型安全性の確保
- JavaScriptとの互換性(既存ライブラリの活用)
- フロントエンド・バックエンド両方で使用可能
- モダンなJavaScript機能(ES6+)のサポート
非機能要件
- 開発効率: エディタのコード補完・型推論による効率向上
- 保守性: リファクタリングが安全に行える
- 学習コスト: 既存のJavaScript知識を活かせる
- エコシステム: 主要なライブラリ・フレームワークがサポートしている
制約条件
- 技術的制約: Next.js、NestJS、React等のモダンフレームワークで使用可能
- ビジネス的制約: 無料で使用できるオープンソースツール
- 時間的制約: 学習コストが許容範囲内(JavaScriptから段階的に移行可能)
検討した選択肢
選択肢1: JavaScript(動的型付け)
概要
標準的なJavaScript(ES6+)をそのまま使用。
メリット
- 学習コストがゼロ(チームメンバー全員が既に習得)
- ビルドステップが不要(ただしBabelは使う)
- 柔軟な動的型付けで、プロトタイピングが高速
デメリット
- 型安全性がない(ランタイムエラーのリスク)
- エディタのコード補完が限定的
- リファクタリングが困難(変更の影響範囲が不明瞭)
- 大規模プロジェクトでの保守性が低い
- APIの型が保証されない
実装コスト
- 初期実装: 最低
- 学習コスト: ゼロ
- 保守コスト: 高(バグの早期発見が困難)
選択肢2: TypeScript(静的型付け)
概要
Microsoftが開発したJavaScriptのスーパーセット。静的型付けを導入し、コンパイル時に型チェックを行う。
メリット
- 型安全性: コンパイル時の型チェックで早期バグ検出
- エディタサポート: 強力なコード補完・型推論
- リファクタリング安全性: 変更の影響範囲が明確
- JavaScriptとの互換性: 既存ライブラリをそのまま使用可能
- エコシステム: 主要フレームワーク(Next.js、NestJS、React)が公式サポート
- DefinitelyTyped: 主要ライブラリの型定義が充実(@types/*)
- モダンJS機能: ES6+ の機能をすべてサポート
デメリット
- 学習コストが若干高い(型システムの理解が必要)
- ビルドステップが必要(tsc)
- 型定義ファイル(.d.ts)の作成が必要な場合がある
- 厳密な型チェックが面倒に感じる場合がある(
anyで回避可能)
実装コスト
- 初期実装: 低(JavaScriptからの段階的移行が可能)
- 学習コスト: 中(型システムの学習が必要)
- 保守コスト: 低(バグの早期発見、リファクタリングの安全性)
選択肢3: Flow(Facebook製の型チェッカー)
概要
Facebookが開発した、JavaScriptに型注釈を追加するツール。
メリット
- TypeScriptと同様の型チェック機能
- JavaScriptとの互換性
- Reactとの統合(Facebookが開発)
デメリット
- TypeScriptと比べてエコシステムが小さい
- 型定義ファイルの整備が不十分
- 主要フレームワークのサポートが限定的
- 学習リソースが少ない
- 将来性に不安(TypeScriptが主流)
実装コスト
- 初期実装: 中
- 学習コスト: 中
- 保守コスト: 中
決定(Decision)
選択した技術: TypeScript
選択理由
要件との適合性
- コンパイル時の型チェックにより、早期バグ検出を実現
- エディタのインテリセンス(VS Code)が強力で、開発効率が大幅に向上
- API通信のリクエスト/レスポンスの型を定義することで、型安全性を確保
- フロントエンド(Next.js)、バックエンド(NestJS)、Chrome拡張、デスクトップアプリ(Electron)すべてでTypeScriptが公式サポートされている
技術的妥当性
- エコシステムの充実: React、Next.js、NestJS、Express等、主要フレームワークが公式サポート
- DefinitelyTyped: npm の主要ライブラリの型定義(
@types/*)が充実 - JavaScriptとの互換性: 既存のJavaScriptコードと混在可能で、段階的な移行が可能
- モダンJS機能: ES6+の機能をすべてサポート
- コミュニティサポート: Stack Overflow、GitHub、公式ドキュメントが充実
コスト対効果
- 学習コストは若干高いが、JavaScriptの知識を活かせる
- リファクタリングの安全性向上により、保守コストが大幅に削減
- バグの早期発見により、デバッグ時間が削減
将来性
- Microsoftが積極的に開発しており、将来性が高い
- TypeScriptが業界標準になりつつある
- 主要企業(Google、Microsoft、Facebook等)が採用
実装方針
プロジェクト全体でTypeScriptを採用
- フロントエンド(Next.js): TypeScript
- バックエンド(NestJS): TypeScript
- Chrome拡張: TypeScript
- デスクトップアプリ(Electron): TypeScript
厳格な型チェック設定
tsconfig.jsonでstrict: trueを設定noImplicitAny,strictNullChecksを有効化- ESLintとの統合(
@typescript-eslint)
型定義の統一
- APIのリクエスト/レスポンスの型を共通ライブラリで定義
- MongooseスキーマからTypeScript型を自動生成
段階的な移行
- 新規コードはすべてTypeScriptで記述
- 既存JavaScriptコードは必要に応じてTypeScriptに移行
受け入れたトレードオフ
トレードオフ1: 学習コスト
- 影響: チームメンバーが型システムを学習する必要がある
- 受け入れ理由:
- JavaScriptの知識を活かせるため、学習コストは許容範囲内
- 公式ドキュメントや学習リソースが充実している
- 学習コストを上回る長期的なメリット(保守性向上、バグ削減)
- 軽減策:
- チーム内での勉強会・ペアプログラミング
- TypeScript公式ドキュメントの共有
- コードレビューでの型活用のベストプラクティス共有
トレードオフ2: ビルドステップの追加
- 影響:
tscによるビルドステップが必要になり、開発フローが若干複雑化 - 受け入れ理由:
- Next.js、NestJSは標準でTypeScriptをサポートしており、ビルドステップが自動化されている
- ビルドステップによる時間的オーバーヘッドは微小
- 軽減策:
- Watch モードの活用(
tsc --watch) - Next.js、NestJSのHot Module Replacement(HMR)活用
- Watch モードの活用(
トレードオフ3: 厳密な型チェックの面倒さ
- 影響: 一部のコードで型定義が面倒に感じる場合がある
- 受け入れ理由:
any型を使用することで、必要に応じて型チェックを回避可能- 厳密な型チェックによる長期的なメリット(バグ削減)が大きい
- 軽減策:
unknown型の活用(anyよりも安全)- ユーティリティ型の活用(
Partial<T>,Pick<T>,Omit<T>等)
結果(振り返り)
うまくいったこと(Pros)
- バグの早期発見: コンパイル時の型チェックにより、ランタイムエラーが大幅に削減された
- リファクタリングの安全性: VS Codeの「Rename Symbol」機能により、安全にリファクタリングができた
- 開発効率の向上: エディタのコード補完・型推論により、開発速度が大幅に向上
- コードの可読性向上: 型注釈により、関数の入出力が明確になり、可読性が向上
- APIの型安全性: フロントエンド・バックエンド間のAPI通信が型安全になり、統合時のバグが削減
うまくいかなかったこと(Cons)
- 学習曲線: 新規メンバーが型システムを理解するのに時間がかかる場合があった
- ジェネリクス の複雑さ: 高度なジェネリクスを使うと、型定義が複雑になることがあった
- サードパーティライブラリの型定義: 一部のライブラリで型定義が不完全な場合があった
学び
strict: trueの重要性: 厳格な型チェックを有効にすることで、バグの早期発見ができた- ユーティリティ型の活用:
Partial<T>,Pick<T>,Omit<T>等を活用することで、型定義が簡潔になる - 型ガードの活用: ユーザー定義型ガードにより、実行時の型安全性を確保できた
今後の改善点
- 型定義の自動生成: OpenAPI(Swagger)からTypeScript型を自動生成することで、型定義の手間を削減
- Monorepoでの型共有: フロントエンド・バックエンド間で共通の型定義を共有する仕組みを構築
- 高度なジェネリクスの学習: より高度なジェネリクスを活用することで、型安全性をさらに向上
参考リンク
- TypeScript公式ドキュメント
- TypeScript Deep Dive(日本語訳)
- DefinitelyTyped(型定義リポジトリ)
- Next.js公式:TypeScript
- NestJS公式:TypeScript