Нарушение Абстрактности (Abstractness Violation)
ID: abstractness | Уровень серьёзности: Средний (по умолчанию)
Что находит это правило (TL;DR)
Это правило помечает модули, которые:
- Слишком «жесткие» (привязанные к реализации) и при этом слишком стабильные — когда от конкретного класса зависит слишком много файлов (это трудно и опасно менять).
- Слишком абстрактные и слишком нестабильные — абстракции, от которых никто не зависит (избыточное проектирование / YAGNI).
Если кратко:
👉 Стабильные модули (фундамент) должны быть абстрактными.
👉 Нестабильные модули (листья системы) должны быть конкретными.
Что такое «Модуль»?
В archlint под модулем понимается один файл исходного кода (.ts, .tsx, и т.д.), который экспортирует хотя бы один символ.
- Каждый файл анализируется независимо.
- Barrel-файлы (
index.ts) рассматриваются как агрегирующие модули. - Реэкспорты и импорты учитываются как зависимости между модулями.
Метрики и интуиция
1. Нестабильность (Instability, I)
Показывает, насколько модуль склонен к изменениям.
I = Исходящие зависимости (Ce) / (Входящие зависимости (Ca) + Исходящие зависимости (Ce))
Интуиция:
- Стабильный (I ≈ 0): Многие импортируют вас, но вы почти никого не импортируете. Вы — фундамент системы.
- Нестабильный (I ≈ 1): Вы импортируете много всего, но вас никто не импортирует. Вы находитесь на «краю» системы.
2. Абстрактность (Abstractness, A)
Мы используем семантический расчет, основанный на реальном использовании:
A = (Клиенты, импортирующие только Интерфейсы/Типы) / (Всего клиентов)
Важное отличие от классического расчета A: Мы НЕ просто считаем ключевые слова или интерфейсы внутри файла. Вместо этого мы измеряем, как модуль используется на самом деле:
- Импорт конкретного
class→ конкретная зависимость. - Импорт
interfaceилиtype→ абстрактная зависимость.
Это отражает реальное архитектурное зацепление, а не просто синтаксис. Использование import type — это сильный сигнал об абстрактном намерении.
3. Дистанция (Distance, D)
Расстояние от идеальной линии «Главной последовательности», где A + I = 1.
D = |A + I - 1|
Зоны риска (Интерпретация)
В зависимости от значений A и I модули попадают в определенные зоны:
🧱 Zone of Pain (Зона боли)
- Метрики: I ≈ 0–0.3 (стабильный), A ≈ 0–0.3 (конкретный).
- Проблема: Все зависят от конкретной реализации. Менять её опасно, так как модуль одновременно «железобетонный» (не предоставляет интерфейсов для расширения) и при этом имеет слишком много внешних связей.
Плохой пример (Зависимость от класса):
// 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 → Максимальное расстояние от главной последовательности.
💨 Zone of Uselessness (Зона бесполезности)
- Метрики: I ≈ 0.7–1.0 (нестабильный), A ≈ 0.7–1.0 (абстрактный).
- Проблема: Переусложненные абстракции, которые никто не использует.
Пример:
// 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) зависимых файлов. Если модуль используют 2-3 файла, его архитектурное влияние невелико. - DTO и сущности: Классы без методов (только свойства с данными) игнорируются. Это переносчики данных, а не компоненты с поведением.
- Ошибки (Errors): Классы, расширяющие
Error, игнорируются. Они всегда конкретны по определению. - Скрипты инфраструктуры: Миграции БД (методы
up/down) игнорируются, так как это процедурные скрипты, а не часть долгосрочной архитектуры.
Как исправить (Руководство к действию)
Модуль стабилен (много зависимых)?
- Да: Выделите
interface. Убедитесь, что клиенты используютimport type { ... }. Используйте Dependency Injection. - Нет: Абстракции могут быть излишни. Оставьте код конкретным, пока количество зависимых не вырастет.
- Да: Выделите
Существует более одной реализации?
- Нет: Если модуль нестабилен, рассмотрите возможность удаления интерфейса (YAGNI).
- Да: Интерфейс оправдан, но убедитесь, что клиенты зависят от интерфейса, а не от классов.
Конфигурация
rules:
abstractness:
severity: medium
distance_threshold: 0.85 # Порог срабатывания по дистанции D
fan_in_threshold: 10 # Минимум входящих зависимостей для анализа