抽象度の違反 (Abstractness Violation)
ID: abstractness | 重要度: 中 (デフォルト)
このルールが検出するもの (TL;DR)
このルールは、以下のモジュールにフラグを立てます:
- 過度に具体的で過度に安定している — 多くのファイルが具象クラスに依存している(安全に変更するのが困難)。
- 過度に抽象的で過度に不安定 — 誰も依存していない抽象(過剰設計/YAGNI)。
要するに:
👉 安定したモジュール(基盤)は抽象的であるべきです。
👉 不安定なモジュール(末端)は具体的であるべきです。
「モジュール」とは何か?
archlint では、モジュールは少なくとも1つのシンボルをエクスポートする単一のソースファイル(.ts、.tsx など)です。
- 各ファイルは独立して分析されます。
- Barrel ファイル(
index.ts)は集約モジュールとして扱われます。 - 再エクスポートとインポートは、モジュール間の依存関係としてカウントされます。
メトリクスと直感
1. 不安定性 (I)
モジュールが変更されやすい度合いを測定します。
I = エフェレント結合 (Ce) / (アフェレント結合 (Ca) + エフェレント結合 (Ce))
直感:
- 安定 (I ≈ 0):多くのファイルがあなたをインポートしますが、あなたはほとんど誰もインポートしません。あなたは基盤コンポーネントです。
- 不安定 (I ≈ 1):あなたは多くのものをインポートしますが、誰もあなたをインポートしません。あなたはシステムの端にいます。
2. 抽象度 (A)
実際の使用に基づくセマンティック計算を使用します:
A = (インターフェース/型のみをインポートするクライアント) / (クライアント総数)
クラシックな A との重要な違い: ファイル内のキーワードやインターフェースを単にカウントするのではありません。代わりに、モジュールが実際にどのように使用されているかを測定します:
- 具象
classをインポート → 具象依存。 interfaceまたはtypeをインポート → 抽象依存。
これは実際のアーキテクチャ結合を反映し、単なる構文ではありません。import type を使用することは、抽象的な意図の強いシグナルです。
3. 距離 (D)
A + I = 1 となる理想的な「メインシーケンス」線からの距離。
D = |A + I - 1|
リスクゾーン (解釈)
A と I の値に基づいて、モジュールは特定のゾーンに分類されます:
🧱 苦痛の地帯
- メトリクス:I ≈ 0–0.3(安定)、A ≈ 0–0.3(具象)。
- 問題:誰もが具象実装に依存しています。変更することは危険です。なぜなら、それは硬直しており、高度に結合しているからです。
悪い例(具象依存):
typescript
// database.service.ts
export class DatabaseService {
save(data: any) {
/* 具象ロジック */
}
}
// client.ts (100+ ファイルがこれを行っている)
import { DatabaseService } from './database.service'; // 直接クラスインポート
const db = new DatabaseService();なぜフラグが立てられるか:
Ca= 100+(非常に安定、I ≈ 0)。A= 0(クライアントがクラスを直接インポート)。D≈ 1 → メインシーケンスからの最大距離。
💨 無用の地帯
- メトリクス:I ≈ 0.7–1.0(不安定)、A ≈ 0.7–1.0(抽象)。
- 問題:誰も使用しない過剰設計された抽象。
例:
typescript
// complex-plugin.interface.ts
export interface IComplexPlugin {
execute(context: any): Promise<void>;
}
// 0 個の実装と 0 個のクライアントがこのインターフェースを使用。なぜフラグが立てられるか:
I≈ 1(誰もそれに依存していない)。A= 1(純粋に抽象的)。D≈ 1 → 目的なく抽象が存在する。
偽陽性を減らすためのヒューリスティック
静的解析はノイズが多い場合があります。これらのヒューリスティックは、ルールをアーキテクチャの決定に焦点を当て、偶発的なコードではなく:
- 安定性のしきい値 (Fan-in):少なくとも
fan_in_threshold(デフォルト:10)の依存関係を持つモジュールのみが分析されます。少数のファイルのみがモジュールを使用する場合、そのアーキテクチャへの影響は低いです。 - DTO とエンティティ:メソッドのないクラス(データのみ)は無視されます。これらはデータキャリアであり、動作コンポーネントではありません。
- エラー:
Errorを拡張するクラスは無視されます。これらは設計上常に具象です。 - インフラストラクチャスクリプト:データベースマイグレーション(
up/down)は無視されます。これらは手続き型スクリプトであり、長期的なアーキテクチャの一部ではないためです。
修正方法 (意思決定ガイド)
モジュールは安定していますか(多くの依存関係がありますか)?
- はい:
interfaceを抽出します。クライアントがimport type { ... }を使用することを確認します。依存性注入を使用します。 - いいえ:抽象化は不要かもしれません。安定性が増すまで具体的に保ちます。
- はい:
複数の実装がありますか?
- いいえ:不安定な場合は、インターフェースの削除を検討してください(YAGNI)。
- はい:インターフェースは正当化されますが、クライアントがクラスではなくインターフェースに依存することを確認します。
設定
yaml
rules:
abstractness:
severity: medium
distance_threshold: 0.85 # 距離 D のトリガーしきい値
fan_in_threshold: 10 # 分析をトリガーする最小の受信依存関係(Fan-in)