継承という名の「時限爆弾」を解体せよ:ESLintカスタムルールによるアーキテクチャ統治
大規模なTypeScriptプロジェクトにおいて、最も深刻な技術的負債は「不適切なクラス継承」から生じる。特に、かつて設計された巨大な基底クラス(`BaseService`, `LegacyController` 等)を安易に拡張し続けることは、疎結合を破壊し、テスタビリティを奪い、最終的にはコードベースを「触るのが怖いブラックボックス」へと変貌させる。
本稿では、単なるLintingの域を超え、静的解析を「アーキテクチャの強制力」へと昇華させるためのカスタムルール実装と、それをCI/CDパイプラインへ統合する戦術を解説する。
—
1. なぜ「静的解析」で継承を制御するのか
現代のIDEのナビゲーション機能を使えば、継承関係は追える。しかし、それは「開発者の善意」に依存している。我々アーキテクトが求めているのは、「設計思想をコードベースの物理法則として埋め込むこと」だ。
ESLintのAST(抽象構文木)を操作することで、特定の基底クラスを継承した瞬間にコンパイルエラー(あるいは警告)を発生させ、コードレビュー以前の段階でアーキテクチャの逸脱を検知する。
—
2. ASTの深淵:継承を検知するカスタムルールの実装
`@typescript-eslint/parser` を利用し、`ClassDeclaration` の `superClass` をトラッキングするカスタムルールを記述する。
// eslint-rules/no-legacy-inheritance.js
module.exports = {
create(context) {
// 警告対象とする基底クラスのリストを定義
const FORBIDDEN_BASE_CLASSES = [‘LegacyBaseService’, ‘OldWorldController’];
return {
ClassDeclaration(node) {
if (!node.superClass) return;
// ASTのノード名を取得(Identifier型であることを想定)
const superClassName = node.superClass.name;
if (FORBIDDEN_BASE_CLASSES.includes(superClassName)) {
context.report({
node,
message: `[Architecture Violation] ${superClassName} の継承は禁止されています。コンポジションへの移行を検討してください。`,
});
}
}
};
}
};
なぜこれが「最強」なのか
このロジックは、型システムに依存しない。`tsconfig.json` の設定ミスや、ビルドが通らない状態でも、ASTさえ生成できれば継承関係を検知できるため、CIのパイプラインの極めて初期段階(Lintフェーズ)で強制排除が可能だ。
—
3. CI/CDパイプラインとの高度な連携と最適化
このルールを適用する際、単に「エラーが出る」だけでは現場は疲弊する。段階的に負債を返済するための「警告レベルの動的制御」が不可欠だ。
プロセス分離による高速化ハック
大規模プロジェクトでは、ESLintの解析時間が数分に達することがある。これを回避するため、CI上では `eslint –cache` を有効化し、かつ `–parallel` (または CI ツール側の並列化) を活用する。
CI環境での実行例
–cache: 前回の結果と比較し、変更差分のみを解析(必須)
–max-warnings 0: 警告もエラー扱いとし、品質の劣化を許さない
npx eslint . –ext .ts –cache –cache-location .eslintcache –max-warnings 0
さらに、メモリ制限を回避するために `NODE_OPTIONS` を調整する。
メモリ消費が激しい大規模リポジトリでは必須のチューニング
export NODE_OPTIONS=”–max-old-space-size=4096″
—
4. 現場で使える「段階的リファクタリング」の仕組み
いきなり「継承禁止」にすると、既存コードがすべてエラーになりチームが崩壊する。ここで活用すべきは、ESLintの `reportUnusedDisableDirectives` と、特定のディレクトリのみを対象とするオーバーライド設定だ。
// .eslintrc.json
{
“overrides”: [
{
“files”: [“src/legacy//.ts”],
“rules”: {
// レガシー領域は「警告」にとどめ、修正の意志を示すためのマーカーを置く
“local-rules/no-legacy-inheritance”: “warn”
}
},
{
“files”: [“src/new-feature//.ts”],
“rules”: {
// 新規開発領域は「エラー」で厳格にブロックする
“local-rules/no-legacy-inheritance”: “error”
}
}
]
}
この設定により、「新規に書くコードは正しい設計を強制し、古いコードは徐々に解消していく」という現実的な負債返済プランをCI/CD上で自動遂行できる。
—
5. アーキテクトへの提言:ツールを「文化」に昇華させる
ツールは、ただの「警察官」であってはならない。警告が出た際に、IDE上で「なぜ継承がダメなのか」「どう書き換えるべきか」のドキュメントへ飛べるようなランブックを `context.report` のメッセージに含めるべきだ。
// context.report のメッセージをリッチ化する
context.report({
node,
message: “LegacyBaseServiceを継承しています。詳細は https://wiki.internal/refactoring-guide を参照してください。”
});
結論
静的解析によるルール化は、単なるコードチェックではない。それは、チームの意識を「動けばいいコード」から「保守可能な資産としてのコード」へとシフトさせるための組織的アプローチである。
ESLintのASTを掌握し、CIパイプラインの深いレイヤーにこのロジックを刻み込むこと。それが、数年後も陳腐化しない、強固なプロダクトを維持する唯一の道だ。さあ、今すぐレガシーの継承関係を可視化し、アーキテクチャの主導権を君たちの手に取り戻せ。