Skip to content

抽象度违规 (Abstractness Violation)

ID: abstractness | 严重程度: 中 (默认)

此规则检测的内容 (TL;DR)

此规则标记以下模块:

  • 过于具体且过于稳定 — 许多文件依赖于具体类(难以安全更改)。
  • 过于抽象且过于不稳定 — 无人依赖的抽象(过度设计/YAGNI)。

简而言之:
👉 稳定模块(基础)应该是抽象的
👉 不稳定模块(叶子)应该是具体的


什么是"模块"?

archlint 中,模块是导出至少一个符号的单个源文件(.ts.tsx 等)。

  • 每个文件独立分析。
  • Barrel 文件(index.ts)被视为聚合模块。
  • 重新导出和导入被计为模块之间的依赖关系。

指标与直觉

1. 不稳定性 (I)

衡量模块变化的倾向性。

I = 传出耦合 (Ce) / (传入耦合 (Ca) + 传出耦合 (Ce))

直觉

  • 稳定 (I ≈ 0):许多文件导入你,但你几乎不导入任何内容。你是基础组件。
  • 不稳定 (I ≈ 1):你导入许多内容,但没有人导入你。你位于系统边缘。

2. 抽象度 (A)

我们使用基于实际使用的语义计算

A = (仅导入接口/类型的客户端) / (客户端总数)

与经典 A 的重要区别: 我们仅仅计算文件内的关键字或接口。相反,我们测量模块的实际使用方式

  • 导入具体 class具体依赖
  • 导入 interfacetype抽象依赖

这反映了真实的架构耦合,而不仅仅是语法。使用 import type 是抽象意图的强烈信号。

3. 距离 (D)

距离理想"主序列"线的距离,其中 A + I = 1

D = |A + I - 1|


风险区域 (解释)

根据 AI 的值,模块落入特定区域:

🧱 痛苦地带

  • 指标: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 → 抽象存在但没有目的。

减少误报的启发式方法

静态分析可能很嘈杂。这些启发式方法将规则聚焦于架构决策,而不是偶然代码:

  1. 稳定性阈值 (Fan-in):仅分析至少有 fan_in_threshold(默认:10)个依赖项的模块。如果只有少数文件使用模块,其架构影响较低。
  2. DTO 和实体:没有方法的类(仅数据)被忽略。它们是数据载体,而不是行为组件。
  3. 错误:扩展 Error 的类被忽略。它们按设计始终是具体的
  4. 基础设施脚本:数据库迁移(up/down)被忽略,因为它们是过程脚本,不是长期架构的一部分。

如何修复 (决策指南)

  1. 模块是否稳定(有许多依赖项)?

    • :提取一个 interface。确保客户端使用 import type { ... }。使用依赖注入。
    • :抽象可能是不必要的。保持具体,直到稳定性增加。
  2. 是否有多个实现?

    • :如果不稳定,考虑删除接口(YAGNI)。
    • :接口是合理的,但确保客户端依赖于接口,而不是类。

配置

yaml
rules:
  abstractness:
    severity: medium
    distance_threshold: 0.85 # 距离 D 的触发阈值
    fan_in_threshold: 10 # 触发分析的最小传入依赖项(Fan-in)

Released under the MIT License.