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 型は検出されない |
| 3 | ESLint ルール | 設定漏れや無効化コメントで回避される |
| 4 | strict モード | 既存コードへの適用が困難で段階的導入が必要 |
| 5 | コーディング規約 | 人によって解釈が異なり一貫性を保つのが難しい |
型品質の低下がもたらした実際のトラブル
型カバレッジ測定を導入する前、私のチームでは以下のようなトラブルが発生していました。
トラブル 1: 本番環境での型エラー
typescript// 問題のあったコード
function calculateDiscount(user: any) {
// VIP ユーザーは 20% 割引
if (user.membershipLevel === 'VIP') {
return 0.2;
}
return 0;
}
このコードは開発環境では動作していましたが、本番環境で user が null の場合に実行時エラーが発生しました。any 型を使っていたため、TypeScript による型チェックが機能しなかったのです。
トラブル 2: リファクタリング時の影響範囲の見落とし
インターフェースを変更した際、any 型で受け取っている箇所が多数あったため、影響範囲を特定できませんでした。結果として、13 ファイルで型の不整合が発生し、修正に丸 2 日かかりました。
これらの経験から、定量的な指標による継続的な型品質管理の必要性を強く感じたのです。
type-coverage による型品質の可視化戦略
採用した解決アプローチと検討した代替手段
型カバレッジの測定方法として、私が検討した選択肢は以下の 3 つでした。
| # | 方法 | メリット | デメリット | 採用判断 |
|---|---|---|---|---|
| 1 | type-coverage | 導入が簡単で CI 連携が容易 | 詳細な型エラー情報は得られない | ★ 採用 |
| 2 | tsc --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.json の devDependencies に追加されます。
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
}
各設定項目の詳細を以下の表にまとめます。
| # | 設定項目 | 説明 | 推奨値 |
|---|---|---|---|
| 1 | atLeast | 最低限必要なカバレッジ率(パーセント) | 95 |
| 2 | strict | 暗黙的な any も検出対象にする | true |
| 3 | ignoreFiles | 計測から除外するファイルパターン(glob 形式) | テストファイルのみ |
| 4 | ignoreCatch | catch 句の error 変数を無視するか | false |
| 5 | ignoreUnread | 読み取られていない変数を無視するか | true |
| 6 | reportSemanticError | 型エラーを検出した際にプロセスを失敗させるか | 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.json の atLeast で設定した閾値を下回ると、このステップが失敗します。
実際に遭遇したエラーと解決方法
エラー 1: type-coverage not found
GitHub Actions で以下のエラーが発生しました。
bash/bin/sh: 1: type-coverage: not found
error Command failed with exit code 127.
発生条件
yarn installでdevDependenciesがインストールされていない場合
原因
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 型を含むコードが追加された場合
- 既存の型定義を削除した場合
原因
新規追加したコードで型定義が不十分だったため、カバレッジが低下しました。
解決方法
yarn type-coverage:detailで具体的な問題箇所を特定- 検出された箇所に適切な型を付与
- 再度 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();
このコメントがあることで、レビュアーが判断しやすくなり、将来的な改善の手がかりにもなります。
定期的なレビュー会の実施
月次で型カバレッジの推移をチーム全体で振り返る機会を設けています。
レビュー会のアジェンダ
- 先月のカバレッジ推移確認(5 分)
- グラフで可視化した推移を共有
- 改善された箇所の紹介(10 分)
- 型定義を追加したコードのビフォーアフター
- 課題の洗い出し(10 分)
- カバレッジが低下している箇所の原因分析
- 次月の目標設定(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 | イベントハンドラの引数 | 78 | 高 | React の型を適用 |
| 3 | サードパーティライブラリ | 32 | 中 | @types をインストール |
| 4 | レガシーコード | 89 | 低 | リファクタリング計画に含める |
| 5 | テストコード | 64 | 低 | ignoreFiles で除外 |
この分類により、効率的に改善を進められました。
具体的な修正例
修正例 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 ヶ月間で以下のような推移で改善が進みました。
| # | 週 | カバレッジ | 改善内容 |
|---|---|---|---|
| 1 | 1 週目 | 80.0% | 現状把握と分類 |
| 2 | 2 週目 | 82.3% | API レスポンスの型定義(20 件) |
| 3 | 3 週目 | 85.1% | イベントハンドラの型付け(35 件) |
| 4 | 4 週目 | 87.8% | カスタムフックの型定義(15 件) |
| 5 | 5 週目 | 90.2% | ユーティリティ関数の型付け |
| 6 | 6 週目 | 92.5% | コンポーネント Props の型修正 |
| 7 | 7 週目 | 94.3% | 残存 any の個別対応 |
| 8 | 8 週目 | 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 を削減する目標を設定します。
| # | 四半期 | 対象 | 削減目標 |
|---|---|---|---|
| 1 | Q1 2025 | user-management モジュール | 除外リストから削除 |
| 2 | Q2 2025 | payment モジュール | 除外リストから削除 |
| 3 | Q3 2025 | order-history モジュール | 除外リストから削除 |
| 4 | Q4 2025 | すべて | 除外ファイルゼロ達成 |
この計画により、1 年で全体の型カバレッジ 95% を達成できました。
運用で得られた知見と陥りやすい罠
型カバレッジ 100% を目指さなかった理由
当初はチーム内で「100% を目指すべきでは?」という議論がありました。しかし、実運用を通じて 95% 前後が最適だと判断しました。
100% が現実的でないケース
以下のような状況では、any 型の使用が許容されると判断しました。
-
サードパーティライブラリの型定義が不完全な場合
- 型定義を自前で作成するコストが高い
- ライブラリのアップデートで型定義が提供される可能性がある
-
動的な JSON 構造を扱う場合
- CMS から取得する自由形式のコンテンツ
- ユーザー定義のカスタムフィールド
-
パフォーマンス上の理由
- 極めて複雑な型定義が型チェックの速度を著しく低下させる場合
このような例外的なケースを考慮し、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 を導入して、型品質の可視化に取り組んでみてください。
関連リンク
著書
article2026年1月4日TypeScriptでi18nを設計する マルチリンガル対応を型安全に運用する戦略
article2026年1月4日TypeScriptでWebSocket双方向通信を作るユースケース 型安全なイベント設計と実装
article2026年1月3日TypeScriptとVitestでテストを運用する 導入から高速化まで活用手順
article2026年1月2日TypeScriptの型情報でドキュメントを自動生成する使い方 陳腐化を防ぐ運用も整理
article2026年1月2日NuxtとTypeScriptで型安全な開発をセットアップする手順 スタートガイド
article2026年1月2日TypeScriptで認証と認可をセキュアに設計する 使い方と実装手順を整理
article2026年1月4日TypeScriptでi18nを設計する マルチリンガル対応を型安全に運用する戦略
article2026年1月4日TypeScriptでWebSocket双方向通信を作るユースケース 型安全なイベント設計と実装
article2026年1月3日TypeScriptとVitestでテストを運用する 導入から高速化まで活用手順
article2026年1月2日TypeScriptの型情報でドキュメントを自動生成する使い方 陳腐化を防ぐ運用も整理
article2026年1月2日NuxtとTypeScriptで型安全な開発をセットアップする手順 スタートガイド
article2026年1月2日TypeScriptで認証と認可をセキュアに設計する 使い方と実装手順を整理
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
