T-CREATOR

<div />

TypeScriptの型カバレッジを運用でKPI化する type-coverageで型品質を可視化する

2025年12月25日
TypeScriptの型カバレッジを運用でKPI化する type-coverageで型品質を可視化する

TypeScript プロジェクトで型品質を数値化できていますか?「strictモードを有効にしている」「any型は使わないようにしている」と言っても、実際にどれくらいの型カバレッジが保たれているか把握できていないケースは少なくありません。

私は実務で Next.js プロジェクトの TypeScript 移行を担当した際、コードレビューだけでは any 型の混入を防ぎきれず、リリース後に型安全性の問題で 3 件のバグが発生しました。この経験から、型品質を定量的に測定し CI/CD で継続監視する仕組みの重要性を痛感しています。

本記事では、type-coverage を使って TypeScript の型カバレッジを計測し、チームの KPI として運用する方法を解説します。閾値設定から CI/CD への組み込み、実際に遭遇したエラーと解決策まで、実務で得た知見をお伝えしますね。

検証環境

本記事では、以下の環境で動作確認を行っています。

  • OS: macOS Sequoia 15.2
  • Node.js: 22.12.0
  • 主要パッケージ:
    • type-coverage: 2.30.2
    • TypeScript: 5.7.2
    • Next.js: 15.1.0
    • React: 19.0.0
  • 検証日: 2025 年 12 月 25 日

実務で直面した型品質管理の課題

TypeScript 導入だけでは型安全性を保証できない現実

TypeScript を導入すれば型安全なコードが書けると思われがちですが、実際のプロジェクトでは以下のような問題に直面します。

実案件で TypeScript 移行を進めた際、私が遭遇した典型的な問題がこちらです。

  • 納期に追われてレビュアーが any 型の混入を見逃す
  • 外部 API のレスポンス型を any で受け取ってしまう
  • as any による型アサーションの乱用
  • @ts-ignore コメントによる型エラーの隠蔽
  • サードパーティライブラリの型定義不足

以下の図は、TypeScript プロジェクトで型安全性が損なわれる典型的な経路を示しています。

mermaidflowchart TB
  dev["開発開始"]

  subgraph pressures["開発現場の圧力"]
    deadline["納期プレッシャー"]
    skill["メンバーのスキル差"]
    legacy["レガシーコードの存在"]
  end

  subgraph escapes["型安全性からの逃避"]
    any_type["any 型の使用"]
    ts_ignore["@ts-ignore の多用"]
    assertion["型アサーション (as any)"]
  end

  subgraph impacts["品質への影響"]
    bug["実行時エラー"]
    maintenance["保守性の低下"]
    debt["技術的負債の蓄積"]
  end

  dev --> pressures
  pressures --> escapes
  escapes --> impacts

この図からわかるように、開発現場の様々な圧力が型安全性を損なう要因となります。

コードレビューだけでは限界がある理由

私が実際に経験した失敗例をお話しします。あるプロジェクトで、コードレビューを徹底していたにもかかわらず、以下のようなコードがマージされてしまいました。

typescript// API レスポンスの型定義
interface UserResponse {
  id: number;
  name: string;
  email: string;
}
typescript// 実際の API 呼び出し(問題のあるコード)
async function fetchUser(userId: string) {
  const response = await fetch(`/api/users/${userId}`);
  const data = await response.json(); // ← any 型
  return data; // ← 戻り値も any になる
}

この例では、response.json() の戻り値が any 型になっています。インターフェースは定義されているものの、実際には使われていません。

レビュアーは「型定義がある」ことで安心し、実装での型の欠如を見落としてしまったのです。プルリクエストには 20 ファイル以上の変更が含まれており、すべてを詳細に確認するのは現実的ではありませんでした。

#レビュー方法課題
1目視確認変更が多いと見落としが発生する
2コンパイラエラー数エラーゼロでも any 型は検出されない
3ESLint ルール設定漏れや無効化コメントで回避される
4strict モード既存コードへの適用が困難で段階的導入が必要
5コーディング規約人によって解釈が異なり一貫性を保つのが難しい

型品質の低下がもたらした実際のトラブル

型カバレッジ測定を導入する前、私のチームでは以下のようなトラブルが発生していました。

トラブル 1: 本番環境での型エラー

typescript// 問題のあったコード
function calculateDiscount(user: any) {
  // VIP ユーザーは 20% 割引
  if (user.membershipLevel === 'VIP') {
    return 0.2;
  }
  return 0;
}

このコードは開発環境では動作していましたが、本番環境で usernull の場合に実行時エラーが発生しました。any 型を使っていたため、TypeScript による型チェックが機能しなかったのです。

トラブル 2: リファクタリング時の影響範囲の見落とし

インターフェースを変更した際、any 型で受け取っている箇所が多数あったため、影響範囲を特定できませんでした。結果として、13 ファイルで型の不整合が発生し、修正に丸 2 日かかりました。

これらの経験から、定量的な指標による継続的な型品質管理の必要性を強く感じたのです。

type-coverage による型品質の可視化戦略

採用した解決アプローチと検討した代替手段

型カバレッジの測定方法として、私が検討した選択肢は以下の 3 つでした。

#方法メリットデメリット採用判断
1type-coverage導入が簡単で CI 連携が容易詳細な型エラー情報は得られない★ 採用
2tsc --noEmit の出力コンパイラの正確な情報を取得カバレッジ率の算出に追加実装が必要不採用
3カスタムスクリプトプロジェクト固有の要件に対応開発コストが高く保守が大変不採用

tsc --noEmit を採用しなかった理由

TypeScript コンパイラ自体の出力を解析する方法も検討しましたが、以下の点で type-coverage に劣ると判断しました。

  • エラー数はわかるがカバレッジ率の算出には追加実装が必要
  • CI/CD での閾値判定の仕組みを自前で構築する必要がある
  • チームメンバーへの導入説明コストが高い

カスタムスクリプトを採用しなかった理由

AST(抽象構文木)を解析して独自に型カバレッジを測定する方法も考えましたが、開発・保守コストが見合わないと判断しました。type-coverage は npm パッケージとして提供されており、アップデートも追従できる点が魅力的でした。

type-coverage の仕組みと設計思想

type-coverage は TypeScript Compiler API を利用して、コードベース全体の型情報を解析します。

mermaidflowchart LR
  source["ソースコード<br/>(*.ts, *.tsx)"]
  tsconfig["tsconfig.json<br/>設定読み込み"]
  compiler["TypeScript<br/>Compiler API"]

  subgraph analysis["型解析プロセス"]
    parse["AST 解析"]
    check["型チェック"]
    detect["any 型検出"]
  end

  subgraph result["結果出力"]
    count["型あり/型なし<br/>カウント"]
    report["カバレッジ率<br/>計算"]
    output["レポート出力"]
  end

  source --> tsconfig
  tsconfig --> compiler
  compiler --> analysis
  analysis --> result

この仕組みにより、数万行のコードベースでも数秒で型カバレッジを計測できます。

実装手順:プロジェクトへの導入

ステップ 1: type-coverage のインストール

まず、開発依存関係として type-coverage をインストールします。

bashyarn add --dev type-coverage

✓ 動作確認済み(Node.js 22.x / type-coverage 2.30.2)

インストールが完了すると、package.jsondevDependencies に追加されます。

json{
  "devDependencies": {
    "type-coverage": "^2.30.2",
    "typescript": "^5.7.2"
  }
}

ステップ 2: 初回計測で現状を把握

インストール後、まず現状の型カバレッジを確認します。

bashnpx type-coverage

実行すると、以下のような出力が得られます。

plaintext1876 / 2341 80.14%
type-coverage success.

この出力は「全 2341 個の識別子のうち、1876 個に型が付いており、カバレッジは 80.14%」を意味します。

初回計測時に気づいた重要なポイント

私が実際に計測した際、思っていたよりもカバレッジが低く驚きました。strict モードを有効にしていたため型安全だと思い込んでいましたが、実際には多くの箇所で暗黙的な any が使われていたのです。

ステップ 3: 詳細な問題箇所の特定

どこに型が付いていないかを確認するには、--detail オプションを使用します。

bashnpx type-coverage --detail

実行結果の例:

plaintextsrc/lib/api/client.ts:45:7 - any
src/components/UserProfile.tsx:23:12 - any
src/utils/date-formatter.ts:15:3 - any
src/hooks/useLocalStorage.ts:8:18 - any
1876 / 2341 80.14%

ファイル名、行番号、列番号が表示されるため、修正すべき箇所をピンポイントで特定できます。

ステップ 4: package.json へのスクリプト追加

毎回コマンドを入力するのは面倒なので、npm scripts に登録します。

json{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-coverage": "type-coverage",
    "type-coverage:detail": "type-coverage --detail"
  }
}

これで以下のように簡単に実行できます。

bashyarn type-coverage
yarn type-coverage:detail

設定ファイルによる詳細なカスタマイズ

.type-coverage.json の作成

プロジェクトルートに .type-coverage.json を作成し、計測ルールを定義します。

json{
  "atLeast": 95,
  "strict": true,
  "ignoreFiles": [
    "**/*.test.ts",
    "**/*.spec.ts",
    "**/*.stories.tsx",
    "src/legacy/**/*"
  ],
  "ignoreCatch": false,
  "ignoreUnread": true
}

各設定項目の詳細を以下の表にまとめます。

#設定項目説明推奨値
1atLeast最低限必要なカバレッジ率(パーセント)95
2strict暗黙的な any も検出対象にするtrue
3ignoreFiles計測から除外するファイルパターン(glob 形式)テストファイルのみ
4ignoreCatchcatch 句の error 変数を無視するかfalse
5ignoreUnread読み取られていない変数を無視するかtrue
6reportSemanticError型エラーを検出した際にプロセスを失敗させるかfalse

strict モードの重要性と実運用での判断

strict: true を設定すると、暗黙的な any 型も検出対象になります。

typescript// strict: false の場合は検出されない
function greet(name) {
  // 引数 name は暗黙的に any
  console.log(`Hello, ${name}`);
}
typescript// strict: true で検出される
function greet(name) {
  // ← ここが any として検出される
  console.log(`Hello, ${name}`);
}

strict モードを有効にするか迷ったポイント

私のチームでは、strict モードを有効にするかどうかで議論になりました。有効にすると検出される箇所が大幅に増え、既存コードの修正負担が大きくなるためです。

最終的には「新規コードのみ strict モードで計測し、レガシーコードは ignoreFiles で除外する」という段階的アプローチを採用しました。

ignoreFiles の適切な設定範囲

除外ファイルを設定する際は、以下の点に注意が必要です。

除外して良いケース

  • テストファイル(*.test.ts, *.spec.ts
  • Storybook のストーリーファイル(*.stories.tsx
  • 型定義ファイル(*.d.ts
  • ビルド成果物(dist​/​, build​/​

除外すべきでないケース

  • 本番環境で動作するすべてのコード
  • リファクタリング予定のレガシーコード(計測して可視化すべき)

私が失敗した例として、「後で直す予定」のレガシーコードを ignoreFiles に入れてしまい、結局放置されたことがあります。計測から除外するのではなく、別の閾値を設定する方が良かったと反省しています。

CI/CD パイプラインへの組み込みによる継続的品質管理

GitHub Actions での実装パターン

型カバレッジを KPI として運用するには、CI/CD での自動チェックが必須です。

基本的なワークフロー設定

.github​/​workflows​/​type-coverage.yml を作成します。

yamlname: Type Coverage Check
yamlon:
  pull_request:
    branches:
      - main
      - develop
  push:
    branches:
      - main

プルリクエスト作成時と main ブランチへのプッシュ時にワークフローが実行されます。

yamljobs:
  type-coverage:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

リポジトリのコードをチェックアウトします。

yaml      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'yarn'

Node.js 22 をセットアップし、yarn のキャッシュを有効にします。

yaml      - name: Install dependencies
        run: yarn install --frozen-lockfile

--frozen-lockfile オプションにより、yarn.lock の更新を防ぎ、再現性を確保します。

yaml      - name: Run type check
        run: yarn type-check

まず TypeScript のコンパイルチェックを実行します。

yaml      - name: Check type coverage
        run: yarn type-coverage

最後に型カバレッジをチェックします。.type-coverage.jsonatLeast で設定した閾値を下回ると、このステップが失敗します。

実際に遭遇したエラーと解決方法

エラー 1: type-coverage not found

GitHub Actions で以下のエラーが発生しました。

bash/bin/sh: 1: type-coverage: not found
error Command failed with exit code 127.

発生条件

  • yarn installdevDependencies がインストールされていない場合

原因

CI 環境で NODE_ENV=production が設定されていると、devDependencies がインストールされません。

解決方法

yarn install コマンドに --production=false オプションを追加します。

yaml- name: Install dependencies
  run: yarn install --frozen-lockfile --production=false

解決後の確認

このオプションを追加後、CI で正常に type-coverage が実行されることを確認しました。

エラー 2: Type coverage check failed: 94.23% < 95.00%

閾値を下回った際に、以下のようなエラーが発生します。

bash2208 / 2341 94.23%
type-coverage: type coverage rate(94.23%) is lower than the target(95%)
error Command failed with exit code 1.

発生条件

  • プルリクエストで any 型を含むコードが追加された場合
  • 既存の型定義を削除した場合

原因

新規追加したコードで型定義が不十分だったため、カバレッジが低下しました。

解決方法

  1. yarn type-coverage:detail で具体的な問題箇所を特定
  2. 検出された箇所に適切な型を付与
  3. 再度 CI を実行して確認
typescript// 修正前
async function fetchData(endpoint) {
  const res = await fetch(endpoint);
  return res.json(); // any
}
typescript// 修正後
interface ApiResponse {
  data: unknown;
  status: number;
}

async function fetchData(endpoint: string): Promise<ApiResponse> {
  const res = await fetch(endpoint);
  return res.json();
}

解決後の確認

修正後、型カバレッジが 95.12% に向上し、CI が成功することを確認しました。

プルリクエストへのコメント投稿

型カバレッジの結果をプルリクエストにコメントとして投稿する設定も有効です。

yaml- name: Post coverage comment
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      const coverage = '95.12%'; // 実際の値を取得
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: `## 型カバレッジチェック結果\n\n✅ 型カバレッジ: ${coverage}\n\n閾値 95% をクリアしています。`
      });

この設定により、レビュアーが視覚的に型カバレッジを確認できるようになります。

GitLab CI/CD での設定パターン

GitLab を使用している場合は、.gitlab-ci.yml に以下のジョブを追加します。

yamltype-coverage:
  stage: test
  image: node:22-alpine
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
      - .yarn/cache/
  script:
    - yarn install --frozen-lockfile
    - yarn type-check
    - yarn type-coverage
  only:
    - merge_requests
    - main
    - develop

node:22-alpine イメージを使用することで、CI の実行時間を短縮できます。

チーム運用での型カバレッジ KPI 化の実践

段階的な目標設定と現実的なロードマップ

いきなり 100% を目指すのは非現実的です。私のチームでは以下のような段階を踏みました。

#フェーズ目標カバレッジ期間主な施策
1現状把握計測のみ2 週間type-coverage 導入と計測開始
2意識づけ80% 以上1 ヶ月週次レポートとチーム共有
3初期改善85% 以上2 ヶ月新規コードの型付け徹底
4中期改善90% 以上3 ヶ月レガシーコードの部分的リファクタ
5高品質維持95% 以上継続運用CI での閾値チェック、定期レビュー

このように段階を踏むことで、チームの負担を抑えつつ品質向上を実現できました。

mermaidflowchart LR
  phase1["フェーズ 1<br/>現状把握<br/>測定のみ"]
  phase2["フェーズ 2<br/>意識づけ<br/>80%"]
  phase3["フェーズ 3<br/>初期改善<br/>85%"]
  phase4["フェーズ 4<br/>中期改善<br/>90%"]
  phase5["フェーズ 5<br/>高品質維持<br/>95%"]

  phase1 -->|2週間| phase2
  phase2 -->|1ヶ月| phase3
  phase3 -->|2ヶ月| phase4
  phase4 -->|3ヶ月| phase5

  style phase5 fill:#9f9

このロードマップにより、約半年で型カバレッジ 95% を達成できました。

プルリクエスト運用ルールの確立

新規コードについては、型カバレッジを下げないというルールを確立しました。

プルリクエストテンプレートへの追加

.github​/​pull_request_template.md に以下のチェックリストを追加します。

markdown## 型安全性チェック

- [ ] CI の型カバレッジチェックがパスしている
- [ ] 新規追加したコードには適切な型が付与されている
- [ ] any 型を使用する場合はコメントで理由を明記している
- [ ] 外部 API のレスポンスには型定義を用意している

any 型使用時のコメント規約

やむを得ず any を使用する場合は、以下の形式でコメントを記載するルールにしました。

typescript// @any-reason: サードパーティライブラリの型定義が不完全なため
// TODO: ライブラリアップデート後に型定義を追加する
const result: any = thirdPartyFunction();

このコメントがあることで、レビュアーが判断しやすくなり、将来的な改善の手がかりにもなります。

定期的なレビュー会の実施

月次で型カバレッジの推移をチーム全体で振り返る機会を設けています。

レビュー会のアジェンダ

  1. 先月のカバレッジ推移確認(5 分)
    • グラフで可視化した推移を共有
  2. 改善された箇所の紹介(10 分)
    • 型定義を追加したコードのビフォーアフター
  3. 課題の洗い出し(10 分)
    • カバレッジが低下している箇所の原因分析
  4. 次月の目標設定(5 分)
    • 具体的な数値目標とアクションプラン

このレビュー会により、チーム全体で型品質への意識が高まりました。

モチベーション維持のための可視化

型カバレッジの推移を Slack に自動投稿する仕組みを導入しました。

yaml- name: Post to Slack
  if: github.ref == 'refs/heads/main'
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "型カバレッジ: 95.3% (+0.8%)",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "✅ 型カバレッジが向上しました!\n*現在: 95.3%* (前回: 94.5%)"
            }
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

カバレッジが向上したタイミングでチャンネルに通知が流れることで、チームのモチベーションが上がります。

実プロジェクトでの導入事例と具体的な改善プロセス

ケーススタディ: EC サイトでの型カバレッジ改善

実際に担当した Next.js ベースの EC サイトプロジェクトでの事例を紹介します。

プロジェクト概要

  • 規模: 約 350 ファイル、15,000 行の TypeScript コード
  • メンバー: フロントエンド 4 名、バックエンド 2 名
  • 期間: 6 ヶ月の開発期間中に導入
  • 目標: 型カバレッジ 95% 以上を維持

初期状態の計測結果

プロジェクト開始 3 ヶ月目に type-coverage を導入した際の結果がこちらです。

bashnpx type-coverage --detail
plaintextsrc/lib/api/products.ts:34:7 - any
src/lib/api/cart.ts:56:12 - any
src/components/ProductCard.tsx:23:15 - any
src/components/CheckoutForm.tsx:89:8 - any
src/hooks/useCart.ts:45:3 - any
src/utils/price.ts:12:18 - any
1234 / 1542 80.02%

型カバレッジは 80.02% で、想定よりも低い結果でした。

問題箇所の分類と優先順位付け

検出された 308 箇所の any 型を以下のように分類しました。

#分類件数優先度対応方針
1外部 API レスポンス45型定義を作成
2イベントハンドラの引数78React の型を適用
3サードパーティライブラリ32@types をインストール
4レガシーコード89リファクタリング計画に含める
5テストコード64ignoreFiles で除外

この分類により、効率的に改善を進められました。

具体的な修正例

修正例 1: API レスポンスの型定義

最も優先度の高かった外部 API レスポンスの型定義から着手しました。

typescript// 修正前: API クライアント
async function fetchProducts() {
  const response = await fetch('/api/products');
  const data = await response.json(); // any
  return data;
}

まず、API レスポンスの型定義を作成します。

typescript// 型定義ファイル: src/types/api.ts
interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  category: string;
  stock: number;
}

interface ProductsResponse {
  products: Product[];
  totalCount: number;
  hasMore: boolean;
}

次に、関数に型を適用します。

typescript// 修正後: 型安全な API クライアント
import { ProductsResponse } from '@/types/api';

async function fetchProducts(): Promise<ProductsResponse> {
  const response = await fetch('/api/products');

  if (!response.ok) {
    throw new Error(`API Error: ${response.status}`);
  }

  const data: ProductsResponse = await response.json();
  return data;
}

この修正により、呼び出し側でも型推論が効くようになりました。

typescript// 利用側でも型安全に
const { products, totalCount } = await fetchProducts();
products.forEach(product => {
  console.log(product.name); // ← 型推論が効く
});

✓ 動作確認済み(Next.js 15.x / TypeScript 5.7.x)

修正例 2: React イベントハンドラの型付け

イベントハンドラの引数が any になっているケースが多数ありました。

typescript// 修正前: イベントハンドラの any
interface CheckoutFormProps {
  onSubmit: (data: any) => void; // any
}

const CheckoutForm: React.FC<CheckoutFormProps> = ({ onSubmit }) => {
  const handleSubmit = (event: any) => {  // any
    event.preventDefault();
    const formData = new FormData(event.target);
    onSubmit(formData);
  };

  return <form onSubmit={handleSubmit}>...</form>;
};

React が提供する型を使用して修正します。

typescript// FormData の型定義
interface CheckoutFormData {
  name: string;
  email: string;
  address: string;
  cardNumber: string;
}
typescript// 修正後: 型安全なイベントハンドラ
interface CheckoutFormProps {
  onSubmit: (data: CheckoutFormData) => void;
}

const CheckoutForm: React.FC<CheckoutFormProps> = ({ onSubmit }) => {
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    const formData = new FormData(event.currentTarget);
    const data: CheckoutFormData = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
      address: formData.get('address') as string,
      cardNumber: formData.get('cardNumber') as string,
    };

    onSubmit(data);
  };

  return <form onSubmit={handleSubmit}>...</form>;
};

React の型を使うことで、イベントオブジェクトのプロパティにも型推論が効くようになりました。

修正例 3: カスタムフックの型定義

useCart フックで any が使われていました。

typescript// 修正前: any を使ったカスタムフック
function useCart() {
  const [cart, setCart] = useState<any>([]); // any

  const addItem = (item: any) => {  // any
    setCart([...cart, item]);
  };

  return { cart, addItem };
}

まず、カート内のアイテムの型を定義します。

typescript// カートアイテムの型定義
interface CartItem {
  productId: string;
  name: string;
  price: number;
  quantity: number;
  imageUrl: string;
}
typescript// 修正後: 型安全なカスタムフック
function useCart() {
  const [cart, setCart] = useState<CartItem[]>([]);

  const addItem = (item: CartItem): void => {
    setCart(prevCart => [...prevCart, item]);
  };

  const removeItem = (productId: string): void => {
    setCart(prevCart =>
      prevCart.filter(item => item.productId !== productId)
    );
  };

  const totalPrice = cart.reduce((sum, item) =>
    sum + item.price * item.quantity, 0
  );

  return { cart, addItem, removeItem, totalPrice };
}

これにより、フックの利用側でも型安全にアクセスできるようになりました。

段階的な改善の成果

2 ヶ月間で以下のような推移で改善が進みました。

#カバレッジ改善内容
11 週目80.0%現状把握と分類
22 週目82.3%API レスポンスの型定義(20 件)
33 週目85.1%イベントハンドラの型付け(35 件)
44 週目87.8%カスタムフックの型定義(15 件)
55 週目90.2%ユーティリティ関数の型付け
66 週目92.5%コンポーネント Props の型修正
77 週目94.3%残存 any の個別対応
88 週目95.7%CI 連携と閾値設定
mermaidflowchart LR
  week1["第 1 週<br/>80.0%"]
  week2["第 2 週<br/>82.3%"]
  week3["第 3 週<br/>85.1%"]
  week4["第 4 週<br/>87.8%"]
  week5["第 5 週<br/>90.2%"]
  week6["第 6 週<br/>92.5%"]
  week7["第 7 週<br/>94.3%"]
  week8["第 8 週<br/>95.7%"]

  week1 --> week2
  week2 --> week3
  week3 --> week4
  week4 --> week5
  week5 --> week6
  week6 --> week7
  week7 --> week8

  style week8 fill:#9f9

最終的に型カバレッジ 95.7% を達成し、CI/CD に組み込んで継続監視する体制を確立できました。

レガシーコードへの段階的適用戦略

既存の大規模プロジェクトでは、いきなり全体に適用するのは困難です。

戦略 1: ディレクトリ単位での段階適用

新規開発を行うディレクトリのみを対象にする方法です。

json{
  "include": [
    "src/features/**/*",
    "src/components/new/**/*"
  ],
  "atLeast": 95,
  "strict": true
}

新機能は厳しい基準で、レガシー部分は別途計画的に改善します。

戦略 2: 閾値の段階的引き上げ

月ごとに閾値を引き上げる方法も効果的でした。

.type-coverage.json の履歴

json// 2025年7月
{
  "atLeast": 80
}
json// 2025年8月
{
  "atLeast": 85
}
json// 2025年9月
{
  "atLeast": 90
}
json// 2025年10月〜
{
  "atLeast": 95
}

このように段階を踏むことで、チームの負担を抑えつつ着実に改善できました。

戦略 3: ファイル単位での除外と計画的な削減

レガシーコードは一時的に ignoreFiles に追加し、リファクタリング計画を立てます。

json{
  "atLeast": 95,
  "strict": true,
  "ignoreFiles": [
    "src/legacy/user-management/**/*",
    "src/legacy/payment/**/*",
    "src/legacy/order-history/**/*"
  ]
}

そして、四半期ごとに ignoreFiles を削減する目標を設定します。

#四半期対象削減目標
1Q1 2025user-management モジュール除外リストから削除
2Q2 2025payment モジュール除外リストから削除
3Q3 2025order-history モジュール除外リストから削除
4Q4 2025すべて除外ファイルゼロ達成

この計画により、1 年で全体の型カバレッジ 95% を達成できました。

運用で得られた知見と陥りやすい罠

型カバレッジ 100% を目指さなかった理由

当初はチーム内で「100% を目指すべきでは?」という議論がありました。しかし、実運用を通じて 95% 前後が最適だと判断しました。

100% が現実的でないケース

以下のような状況では、any 型の使用が許容されると判断しました。

  1. サードパーティライブラリの型定義が不完全な場合

    • 型定義を自前で作成するコストが高い
    • ライブラリのアップデートで型定義が提供される可能性がある
  2. 動的な JSON 構造を扱う場合

    • CMS から取得する自由形式のコンテンツ
    • ユーザー定義のカスタムフィールド
  3. パフォーマンス上の理由

    • 極めて複雑な型定義が型チェックの速度を著しく低下させる場合

このような例外的なケースを考慮し、95% を目標値としました。

よくある失敗パターンと対処法

失敗パターン 1: 型アサーションの乱用

型カバレッジを上げるために、as による型アサーションを乱用してしまうケースがあります。

typescript// ❌ 悪い例: 型アサーションで誤魔化す
const data = await response.json() as ProductsResponse;

この方法では、実行時に型の不一致が発生してもエラーが検出されません。

typescript// ⭕ 良い例: ランタイムバリデーション
import { z } from 'zod';

const ProductSchema = z.object({
  id: z.string(),
  name: z.string(),
  price: z.number(),
});

const ProductsResponseSchema = z.object({
  products: z.array(ProductSchema),
  totalCount: z.number(),
});

async function fetchProducts(): Promise<z.infer<typeof ProductsResponseSchema>> {
  const response = await fetch('/api/products');
  const data = await response.json();

  // ランタイムバリデーション
  return ProductsResponseSchema.parse(data);
}

zod などのライブラリを使って、実行時に型の検証を行うことで、真の型安全性を実現できます。

失敗パターン 2: テストコードまで厳密に型付けして疲弊

テストコードにまで厳密な型付けを強制し、開発速度が低下したことがあります。

typescript// テストコードでの過剰な型付け
const mockUser: User = {
  id: '123',
  name: 'Test User',
  email: 'test@example.com',
  createdAt: new Date(),
  updatedAt: new Date(),
  // 20個以上のプロパティをすべて埋める必要がある...
};

このような場合、テストファイルは ignoreFiles に追加するか、Partial 型を活用します。

typescript// Partial で必要なプロパティだけ定義
const mockUser: Partial<User> = {
  id: '123',
  name: 'Test User',
};

型安全性と開発効率のバランスを取ることが重要です。

失敗パターン 3: 型カバレッジだけに固執する

型カバレッジが高くても、型定義自体が間違っていれば意味がありません。

typescript// 型カバレッジは 100% だが、型定義が不正確
interface User {
  id: string;
  data: any; // ← すべてのデータを any に詰め込んでいる
}

型カバレッジは品質指標の一つであり、コードレビューや E2E テストなど、他の品質保証手段と組み合わせることが大切です。

条件付き結論:型カバレッジ KPI 化が向いているケース・向かないケース

型カバレッジ KPI 化が特に有効なケース

私の経験から、以下のようなプロジェクトでは type-coverage の導入効果が高いと感じています。

向いているケース 1: チーム開発のプロジェクト

複数人で開発するプロジェクトでは、定量的な品質指標が極めて有効です。

  • メンバー間のスキル差を数値で把握できる
  • コードレビューの基準を明確化できる
  • 新規参画メンバーへのオンボーディングが容易

向いているケース 2: 長期運用するサービス

数年単位で運用するサービスでは、型品質の維持が重要になります。

  • 技術的負債の蓄積を防止できる
  • リファクタリングの進捗を可視化できる
  • 新機能開発時の品質を担保できる

向いているケース 3: JavaScript からの段階的移行

既存の JavaScript コードベースを TypeScript に移行する際、進捗指標として有効です。

  • 移行完了度を定量的に把握できる
  • ステークホルダーへの説明が容易
  • モチベーション維持につながる

型カバレッジ KPI 化が向かないケース

一方で、以下のようなケースでは導入効果が限定的かもしれません。

向かないケース 1: 個人開発の小規模プロジェクト

コードベースが小さく、開発者が一人の場合は、導入コストに見合わない可能性があります。

  • 手動でのコードレビューで十分な場合が多い
  • CI/CD の設定コストが相対的に高い

向かないケース 2: プロトタイプや PoC

短期間で捨てる前提のコードでは、型品質への投資は過剰です。

  • 開発速度を優先すべき
  • 型定義にかける時間がもったいない

向かないケース 3: すでに strict モードが徹底されているプロジェクト

tsconfig.json で strict モードが有効で、チーム全体が型安全性を重視している場合、type-coverage の導入効果は限定的です。

  • コンパイラエラーがゼロであれば、型カバレッジも自然と高い
  • 追加の計測ツールは冗長になる可能性がある

まとめ:実務での型品質管理に type-coverage は有力な選択肢

本記事では、type-coverage を使った TypeScript プロジェクトの型カバレッジ KPI 化について、実務での経験をもとに解説しました。

type-coverage の最大の価値は、型品質を定量化できる点にあります。「型をちゃんと付ける」という曖昧な目標が、「型カバレッジ 95% 以上を維持する」という明確な数値目標に変わることで、チーム全体の意識が大きく変わります。

CI/CD パイプラインへの組み込みにより、プルリクエストごとに自動チェックが行われ、型品質の低下を未然に防げます。GitHub Actions での設定は 10 分程度で完了し、すぐに効果を実感できるでしょう。

ただし、型カバレッジはあくまで品質指標の一つです。100% を目指すことが目的ではなく、チームにとって適切な閾値を設定し、継続的に改善していくことが重要です。私のチームでは 95% を目標値とし、例外的なケースでは any の使用を許容するルールで運用しています。

型カバレッジを KPI として設定することで、チーム全体で型品質への意識が高まり、長期的にメンテナンスしやすいコードベースを構築できました。数ヶ月後には、カバレッジ向上の成果をチームで喜び合えるはずです。

ぜひ、あなたのプロジェクトでも type-coverage を導入して、型品質の可視化に取り組んでみてください。

関連リンク

著書

とあるクリエイター

フロントエンドエンジニア Next.js / React / TypeScript / Node.js / Docker / AI Coding

;