Оценки здоровья¶
Qualimetrix вычисляет 6 оценок здоровья для каждого класса, пространства имён и проекта — от 0 (худшее) до 100 (лучшее). Оценки здоровья сводят десятки метрик в быструю картину качества, позволяя моментально определить, какие участки кодовой базы требуют внимания.
Определения разрешаются отдельно для каждого запуска анализа и вычисляются после агрегации исходных метрик. При повторном запуске в одном процессе набор определений заменяется атомарно, поэтому конфигурация предыдущего запуска не может протечь в следующий.
Rule ID: computed — пользовательские вычисляемые метрики.
Каждое встроенное измерение — самостоятельный производитель и публикует свои находки под собственным rule ID:
- Rule ID:
health.complexity - Rule ID:
health.cohesion - Rule ID:
health.coupling - Rule ID:
health.typing - Rule ID:
health.maintainability - Rule ID:
health.overall
Измерения¶
| Измерение | Что измеряет | Ключевые метрики | Пороги (warning / error) |
|---|---|---|---|
health.complexity |
Сложность методов и классов | CCN (avg, max, p95), Cognitive Complexity | 50 / 25 |
health.cohesion |
Связность методов внутри класса | TCC, LCOM4, количество методов | 50 / 25 |
health.coupling |
Зависимости между классами и пространствами имён | Efferent coupling (Ce, Ce packages), Distance from Main Sequence, CBO (на уровне проекта) | 50 / 25 |
health.typing |
Покрытие типами | Типы параметров, возвращаемых значений, свойств | 80 / 50 |
health.maintainability |
Лёгкость безопасной модификации | Maintainability Index (avg, p5, min) | 50 / 25 |
health.overall |
Взвешенное среднее всех измерений | Все вышеперечисленные | 50 / 30 |
Уровни оценки¶
Каждой оценке присваивается текстовый уровень на основе значения относительно порогов warning (W) и error (E):
- Excellent: score > W + (100 − W) × 0.6
- Good: score > W + (100 − W) × 0.3
- Fair: score > W
- Poor: score > E
- Critical: score ≤ E
Для стандартных измерений (W=50, E=25):
| Уровень | Диапазон |
|---|---|
| Excellent | > 80 |
| Good | 65 – 80 |
| Fair | 50 – 65 |
| Poor | 25 – 50 |
| Critical | ≤ 25 |
Пороги health.typing отличаются
У health.typing пороги по умолчанию W=80, E=50, поэтому границы уровней сдвинуты: Excellent > 92, Good > 86, Fair > 80, Poor > 50, Critical ≤ 50.
Как работают оценки¶
Все оценки здоровья используют штрафной подход: оценка начинается со 100 и уменьшается при обнаружении проблем. Результат ограничивается диапазоном 0–100 функцией clamp. Такой подход обеспечивает хорошую дифференциацию — проекты с умеренными проблемами не скатываются сразу в ноль, а различия между «хорошим» и «отличным» кодом остаются видны.
health.complexity¶
Штрафует за высокую цикломатическую и когнитивную сложность. На уровне класса учитывается средняя и максимальная сложность методов. На уровне пространства имён дополнительно анализируется p95 (95-й перцентиль), что позволяет обнаруживать выбросы — отдельные аномально сложные методы. Максимальная сложность масштабируется через квадратный корень, чтобы один метод-монстр не обрушивал оценку всего пространства имён.
Методы интерфейсов включены в агрегацию
Методы интерфейсов имеют минимальную сложность (CCN=1, cognitive=0, NPath=1) и включены в расчёт .avg и .p95 на уровне пространства имён. В проектах с большим количеством интерфейсов средняя сложность может оказаться ниже ожидаемой. Это сделано намеренно — интерфейсы являются частью кодовой базы — но добавление интерфейсов может немного улучшить оценку сложности без реальных изменений логики.
health.cohesion¶
Оценивает, насколько методы класса работают с общими данными. Основана на TCC (Tight Class Cohesion) и LCOM4. Формула корректирует «чистые» методы (без обращения к свойствам) — такие методы завышают LCOM и занижают TCC, не являясь реальной проблемой. Для классов с менее чем 6 методами применяется смягчённая оценка.
health.coupling¶
Измеряет зависимости на уровне класса, пространства имён и проекта. Везде применяется гиперболическое затухание: зависимости за пределами порога снижают оценку, но каждая следующая зависимость влияет слабее предыдущей.
- На уровне класса смешиваются
coupling.ce-packages(количество внешних пакетов) и сглаженный rawcoupling.ce(efferent coupling). - На уровне пространства имён используются только efferent-сигналы: средняя
coupling.ce.avgиcoupling.ce-packages.avgпо классам, выброс отдельного класса (coupling.ce.max), а также общая исходящая ширина пространства имён (coupling.ce), плюс Distance from Main Sequence. Двунаправленный CBO здесь намеренно не используется — он смешивает Ca с Ce и несправедливо штрафует «контрактные» пространства имён со стабильно высоким Ca и низким Ce. - На уровне проекта оставлены агрегаты двунаправленного CBO (
coupling.cbo.avg,coupling.cbo.p95,coupling.cbo.max): на уровне проекта Σ Ca = Σ Ce, потому что каждое внутреннее ребро вносит вклад в обе стороны, поэтому CBO симметричен и пропорционален Ce.
health.typing¶
Непосредственно отражает процент покрытия типами (Type Coverage): параметры, возвращаемые значения и свойства. На уровне пространства имён агрегирует суммы по всем классам.
health.maintainability¶
Основана на Maintainability Index. На уровне класса штрафует за низкий средний MI и за отдельные методы с экстремально низким MI (масштабирование через квадратный корень). На уровне пространства имён основные дифференциаторы — p5 (5-й перцентиль) и минимальное значение. Границы штрафов взяты из опубликованных значений Coleman: 85 — «highly maintainable», 65 — «difficult to maintain». На калибровочном корпусе из семнадцати проектов измерение принимает значения от 33.7 до 100.0 на уровне проекта.
health.overall¶
Взвешенное среднее всех измерений. Веса различаются по уровням:
- Класс: complexity 35%, cohesion 25%, coupling 25%, typing 15% (maintainability исключена — MI является метрикой уровня метода и её сигнал уже учтён через complexity и cohesion)
- Пространство имён / проект: complexity 30%, cohesion 20%, coupling 20%, typing 10%, maintainability 20%
Чтение оценок здоровья¶
Оценки здоровья доступны в нескольких форматах вывода:
summary (по умолчанию) — прогресс-бары в терминале:
Qualimetrix — 45 files analyzed, 1.23s
Complexity ████████████████░░░░ 78 Excellent
Cohesion ██████████████░░░░░░ 68 Good
Coupling ████████████░░░░░░░░ 59 Fair
Typing ██████████████████░░ 88 Excellent
Maintainability████████████████░░░░ 80 Good
Overall ██████████████░░░░░░ 72 Good
json — структурированные данные для CI/CD:
health — текстовая таблица оценок здоровья в терминале:
html — интерактивный отчёт с drill-down по пространствам имён и классам:
Подробнее о форматах вывода — в разделе Форматы вывода.
Что именно покрывает оценка¶
Оценка — утверждение лишь о той части кода, на которой её входы вообще удалось измерить. Связность (cohesion) не определена для класса менее чем с двумя методами, поэтому оценка cohesion обычно описывает от четверти до половины классов проекта — и до сих пор об этом не сообщала.
Поэтому каждое измерение здоровья публикует рядом с оценкой поле coverage:
.count самого узкого входного агрегата, размер популяции, долей которой этот
счёт является, и имя самого .count (basis). Оценка при этом не
приглушается покрытием: число публикуется, чтобы читатель мог судить сам, а не
подмешивается в оценку (см. ADR 0062).
Там, где покрытие не определено, поле говорит об этом явно и с причиной, а не
показывает ноль: health.overall складывается из остальных измерений,
health.typing считается из сумм typed/total, для которых .count не
публикуется, а оценка уровня класса или отфильтрованного пространства имён
вообще не является агрегатом по символам.
Покрытие выводится в --format=json (объект coverage у каждого измерения) и в
--format=health (строка на измерение). В компактных форматах — summary и
HTML — его нет из-за нехватки места.
Настройка¶
Допустимые ключи¶
Каждая запись computed_metrics: принимает ровно девять ключей: formula,
formulas, levels, description, inverted, threshold, warning,
error и enabled. threshold задаёт warning и error одним и тем же
значением и не сочетается ни с одним из них. Внутри formulas: допустимы
только три слова уровня: class, namespace и project.
Неизвестный ключ, значение неверного типа или имя health.* вне шести
встроенных измерений (health.complexity, health.cohesion,
health.coupling, health.typing, health.maintainability,
health.overall) отклоняются с кодом возврата 3 и сообщением, называющим
написанное и допустимое — ни один из этих случаев не игнорируется молча.
Настройка порогов¶
# qmx.yaml
computed_metrics:
health.complexity:
warning: 60 # Stricter than default 50
error: 30 # Stricter than default 25
Отключение измерения¶
Или через CLI:
Оба пути дают одинаковый результат: измерение убирается из пайплайна И веса health.overall ренормализуются по оставшимся измерениям (отключённое измерение не учитывается как нейтральный вклад в 75 баллов). Если вы переопределили health.overall нестандартной формулой (например, min(...) или условным выражением), исключение измерений выбросит явную ошибку — обработайте отключённое измерение через ??-фолбэки в собственной формуле.
Два похожих переключателя, которые делают разное
Каждое встроенное измерение — сам себе производитель, поэтому его можно выключить двумя способами, читающимися почти одинаково:
rules: { health.cohesion: { enabled: false } }останавливает публикацию находок производителемhealth.cohesion. Измерение всё равно вычисляется и продолжает участвовать вhealth.overall.computed_metrics: { health.cohesion: { enabled: false } }убирает само измерение — это переключатель из раздела «Отключение измерения» выше. Весаhealth.overallренормализуются по оставшимся измерениям.
У измерения, убранного вторым способом, у производителя не остаётся ни одного канала. Ключ suppress_namespace_channels, который раньше адресовал health.cohesion, после этого отвергается: ключ обязан называть канал, который правило под ним действительно эмитит, а после удаления оно не эмитит ни одного.
Переопределение формул¶
computed_metrics:
health.maintainability:
# Same formula for all levels
formula: "clamp(m['maintainability.mi.avg'], 0, 100)"
Формула — это выражение, записанное строкой, а константа — тоже выражение:
formula: "80" задаёт метрику, равную 80 везде. Кавычки обязательны: 80 без
них — число, а число формулой не является.
computed_metrics:
health.maintainability:
# Different formulas per level
formulas:
class: "clamp(m['maintainability.mi.avg'], 0, 100)"
namespace: "clamp(m['maintainability.mi.avg'] * 0.7 + m['maintainability.mi.p5'] * 0.3, 0, 100)"
project: "clamp(m['maintainability.mi.avg'] * 0.7 + m['maintainability.mi.p5'] * 0.3, 0, 100)"
Пользовательские вычисляемые метрики¶
computed_metrics:
computed.code-density:
formula: "clamp((m['size.lloc'] ?? 0) / max(m['size.loc'] ?? 1, 1) * 100, 0, 100)"
description: "Ratio of logical to physical lines (higher = denser code)"
levels: [namespace] # size.lloc / size.loc — сырые ключи только на уровне namespace
warning: 80
error: 90
inverted: false # Higher values trigger violations
Именование метрик
Имя пользовательской метрики обязано начинаться с health. или computed. — другие префиксы не принимаются. Рекомендуемое соглашение для собственных метрик — computed.*; health.* зарезервирован за шестью встроенными измерениями. Оба префикса требуют строчных kebab-case сегментов после точки (например, computed.code-density); подчёркивания и заглавные буквы отвергаются, а последний сегмент не может совпадать с именем стратегии агрегации (sum, avg, max, min, count, p95, p5 — например, computed.sum отклоняется).
Доступные переменные¶
Формулы обращаются к метрикам через единственный массив m, индексированный настоящим ключом метрики: m["complexity.ccn.avg"]. Отдельного «имени переменной» запоминать не нужно — ключ, который вы видите в выводе --format=metrics/--format=json, и есть индекс.
| Ключ метрики | Доступен на уровне |
|---|---|
complexity.ccn.avg |
class, namespace, project |
complexity.ccn.max |
class, namespace, project |
complexity.ccn.sum |
namespace, project |
complexity.ccn.p95 |
namespace, project |
complexity.cognitive.avg |
class, namespace, project |
complexity.cognitive.max |
class, namespace, project |
complexity.cognitive.sum |
namespace, project |
complexity.cognitive.p95 |
namespace, project |
cohesion.tcc |
class |
cohesion.tcc.avg |
namespace, project |
cohesion.lcom |
class |
cohesion.lcom.avg |
namespace, project |
coupling.cbo.avg |
namespace, project |
coupling.cbo.max |
namespace, project |
coupling.cbo.p95 |
namespace, project |
coupling.ce |
class, namespace |
coupling.ce.avg |
namespace, project |
coupling.ce.max |
namespace, project |
coupling.ce-packages |
class |
coupling.ce-packages.avg |
namespace, project |
coupling.distance |
namespace |
coupling.distance.avg |
project |
maintainability.mi.avg |
class, namespace, project |
maintainability.mi.min |
class, namespace, project |
maintainability.mi.p5 |
namespace, project |
design.type-coverage.all |
class |
design.type-coverage.param.total.sum |
namespace, project |
design.type-coverage.param.typed.sum |
namespace, project |
design.type-coverage.return.total.sum |
namespace, project |
design.type-coverage.return.typed.sum |
namespace, project |
design.type-coverage.property.total.sum |
namespace, project |
design.type-coverage.property.typed.sum |
namespace, project |
size.method-count |
class |
size.symbol-method-count |
class, namespace, project |
cohesion.pure-method-count |
class |
size.loc |
namespace |
size.lloc |
namespace |
health.complexity |
class, namespace, project |
health.cohesion |
class, namespace, project |
health.coupling |
class, namespace, project |
health.typing |
class, namespace, project |
health.maintainability |
class, namespace, project |
Частые агрегатные суффиксы у ключа: .avg, .min, .max, .sum, .p5, .p95.
Это не исчерпывающий список — в формулах можно использовать любую метрику, собираемую Qualimetrix, по её ключу. Команда bin/qmx check src/ --format=metrics покажет все доступные метрики и их точные ключи для вашего проекта.
Неизвестные ссылки на метрики
Если формула ссылается на несуществующий ключ метрики (например, опечатка m["complexity.ccn.abg"] вместо m["complexity.ccn.avg"]), Qualimetrix выдаст явную ошибку вместо молчаливого возврата нуля. Всегда используйте оператор ?? для метрик, которые могут обоснованно отсутствовать: (m["complexity.ccn.avg"] ?? 0).
Доступные функции¶
| Функция | Описание |
|---|---|
min(a, b) |
Минимум из двух значений |
max(a, b) |
Максимум из двух значений |
abs(x) |
Модуль числа |
sqrt(x) |
Квадратный корень |
log(x) |
Натуральный логарифм |
log10(x) |
Десятичный логарифм |
clamp(value, min, max) |
Ограничение значения диапазоном [min, max] |
?? |
Null coalescing (значение по умолчанию, если метрики нет) |
** |
Возведение в степень |
Всегда используйте оператор ??
Метрики могут отсутствовать для некоторых символов (например, у класса без методов нет complexity.ccn). Всегда задавайте значения по умолчанию через ??: (m["complexity.ccn.avg"] ?? 1) вместо m["complexity.ccn.avg"].