Skip to content

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

選択理由

  1. 要件との適合性

    • コンパイル時の型チェックにより、早期バグ検出を実現
    • エディタのインテリセンス(VS Code)が強力で、開発効率が大幅に向上
    • API通信のリクエスト/レスポンスの型を定義することで、型安全性を確保
    • フロントエンド(Next.js)、バックエンド(NestJS)、Chrome拡張、デスクトップアプリ(Electron)すべてでTypeScriptが公式サポートされている
  2. 技術的妥当性

    • エコシステムの充実: React、Next.js、NestJS、Express等、主要フレームワークが公式サポート
    • DefinitelyTyped: npm の主要ライブラリの型定義(@types/*)が充実
    • JavaScriptとの互換性: 既存のJavaScriptコードと混在可能で、段階的な移行が可能
    • モダンJS機能: ES6+の機能をすべてサポート
    • コミュニティサポート: Stack Overflow、GitHub、公式ドキュメントが充実
  3. コスト対効果

    • 学習コストは若干高いが、JavaScriptの知識を活かせる
    • リファクタリングの安全性向上により、保守コストが大幅に削減
    • バグの早期発見により、デバッグ時間が削減
  4. 将来性

    • Microsoftが積極的に開発しており、将来性が高い
    • TypeScriptが業界標準になりつつある
    • 主要企業(Google、Microsoft、Facebook等)が採用

実装方針

  1. プロジェクト全体でTypeScriptを採用

    • フロントエンド(Next.js): TypeScript
    • バックエンド(NestJS): TypeScript
    • Chrome拡張: TypeScript
    • デスクトップアプリ(Electron): TypeScript
  2. 厳格な型チェック設定

    • tsconfig.jsonstrict: true を設定
    • noImplicitAny, strictNullChecks を有効化
    • ESLintとの統合(@typescript-eslint
  3. 型定義の統一

    • APIのリクエスト/レスポンスの型を共通ライブラリで定義
    • MongooseスキーマからTypeScript型を自動生成
  4. 段階的な移行

    • 新規コードはすべてTypeScriptで記述
    • 既存JavaScriptコードは必要に応じてTypeScriptに移行

受け入れたトレードオフ

トレードオフ1: 学習コスト

  • 影響: チームメンバーが型システムを学習する必要がある
  • 受け入れ理由:
    • JavaScriptの知識を活かせるため、学習コストは許容範囲内
    • 公式ドキュメントや学習リソースが充実している
    • 学習コストを上回る長期的なメリット(保守性向上、バグ削減)
  • 軽減策:
    • チーム内での勉強会・ペアプログラミング
    • TypeScript公式ドキュメントの共有
    • コードレビューでの型活用のベストプラクティス共有

トレードオフ2: ビルドステップの追加

  • 影響: tsc によるビルドステップが必要になり、開発フローが若干複雑化
  • 受け入れ理由:
    • Next.js、NestJSは標準でTypeScriptをサポートしており、ビルドステップが自動化されている
    • ビルドステップによる時間的オーバーヘッドは微小
  • 軽減策:
    • Watch モードの活用(tsc --watch
    • Next.js、NestJSのHot Module Replacement(HMR)活用

トレードオフ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での型共有: フロントエンド・バックエンド間で共通の型定義を共有する仕組みを構築
  • 高度なジェネリクスの学習: より高度なジェネリクスを活用することで、型安全性をさらに向上

参考リンク


関連ドキュメント