Перейти к содержанию

Оценки здоровья

Qualimetrix определяет шесть измерений здоровья классов, пространств имён и проекта. Оценка от 0 (худшее) до 100 (лучшее) появляется при применимых измеренных входах выбранной формулы. Без этих входов числовой оценки нет; числовой 0 остаётся измерением.

Определения разрешаются отдельно для каждого запуска анализа и вычисляются после агрегации исходных метрик. При повторном запуске в одном процессе набор определений заменяется атомарно, поэтому конфигурация предыдущего запуска не может протечь в следующий.

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 Зависимости между классами и пространствами имён Исходящая связность (Ce, Ce packages), применимая Distance from Main Sequence 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.


Как работают оценки

Встроенные измерения используют измеренные входы своего уровня, а overall — их взвешенное среднее. Отсутствующие или неприменимые входы не превращаются в выдуманные значения штрафов. Формулы классов читают значения классов, а формулы пространств имён и проекта — соответствующие агрегаты.

health.complexity

Штрафует за высокую цикломатическую и когнитивную сложность. На уровне класса учитывается средняя и максимальная сложность методов. На уровне пространства имён дополнительно анализируется p95 (95-й перцентиль), что позволяет обнаруживать выбросы — отдельные аномально сложные методы. Максимальная сложность масштабируется через квадратный корень, чтобы один метод-монстр не обрушивал оценку всего пространства имён.

Методы интерфейсов включены в агрегацию

Методы интерфейсов имеют минимальную сложность (CCN=1, cognitive=0, NPath=1) и включены в расчёт .avg и .p95 на уровне пространства имён. В проектах с большим количеством интерфейсов средняя сложность может оказаться ниже ожидаемой. Это сделано намеренно — интерфейсы являются частью кодовой базы — но добавление интерфейсов может немного улучшить оценку сложности без реальных изменений логики.

health.cohesion

Объединяет доступные вклады TCC (Tight Class Cohesion) и LCOM4 и делит на сумму участвующих весов. Отсутствующая половина не заменяется значением по умолчанию, в том числе у классов менее чем с шестью методами. Масштабирование TCC через квадратный корень сохраняется; поправка на чистые методы делится на cohesion.tcc-method-count, а не на size.method-count. Если нет ни измеренного TCC, ни LCOM, оценки cohesion у класса нет.

Cohesion пространства имён и проекта применяет ту же поправку на чистые методы через cohesion.pure-method-count.avg и cohesion.tcc-method-count.avg: пространство имён с одним классом получает его оценку. Health не наследует фильтр минимального числа методов правила LCOM. LCOM-вклад непустого графа из одной вершины по построению равен 100 (вся оценка равна 100 при отсутствующем TCC); две несвязанные вершины без поправки на чистые методы дают 40. Это форма графа, а не достаточное свидетельство разделения обязанностей. В проверенном корпусе одновершинных классов было 209/877 у Qualimetrix, 164/242 у PHP-Parser, 359/826 у PHPUnit и 153/856 у Laravel.

Вклады LCOM класса и пространства имён используют размах пять. Проверка объявленной сетки из 27 кандидатов сохранила прежнюю шкалу пространства имён. Cohesion проекта наследует эту формулу и вычисляется по агрегированным входам проекта, а не усредняет оценки health пространств имён. Корректное усреднение взаимно дополняющих входов TCC/LCOM может дать оценку выше всех дочерних даже при одинаковой формуле и популяции участников. ADR 0116 сохраняет нативный пример (50/50 → 60.355339) и обязательные проверки реальной потери входов. Сравнения родителя и детей остаются диагностикой.

Измеренная калибровка

Выбраны размах LCOM пять, вес дистанции проекта шесть и вес ширины четыре. ADR 0116 сохраняет результаты всех 27 кандидатов и полные таблицы «до/после» для 17 проектов. Пересмотри зависимые ограничения с учётом новых измеренных популяций.

health.coupling

Измеряет зависимости на уровне класса, пространства имён и проекта. Везде применяется гиперболическое затухание: зависимости за пределами порога снижают оценку, но каждая следующая зависимость влияет слабее предыдущей.

  • На уровне класса смешиваются coupling.ce-packages (количество внешних пакетов) и сглаженный raw coupling.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.
  • На уровне проекта применяется Form B только по исходящим связям: средняя ширина исходящих пакетов (coupling.ce-packages.avg), coupling.ce.avg с масштабированием через квадратный корень, исходящие выбросы (coupling.ce.max) и применимая coupling.distance-own.avg. Дистанция взвешена подходящими типами измеренных собственных областей. Входящая популярность не штрафует проект; двунаправленный CBO не входит в формулу.

На калибровочном корпусе из 17 проектов штраф ширины был нулевым во всех проектах: составной вход не достигал порога четыре (максимум 2.996722 у Guzzle). Вес ширины четыре выбран порядком сетки при ничьей, а не измеренным эффектом на корпусе. Поэтому на этом корпусе штраф определяется собственной дистанцией и худшим исходящим классом; различать ширину ниже порога формула не обещает.

Project Form B использует вес дистанции шесть и вес ширины четыре:

clamp(100 * 18 / (18 + (m["coupling.distance-own.avg"] ?? 0) * 6 + max((m["coupling.ce-packages.avg"] ?? 0) * 3.0 + (m["coupling.ce.avg"] ?? 0) ** 0.5 * 0.5 - 4, 0) * 4 + max((m["coupling.ce.max"] ?? 0) - 30, 0) ** 0.5 * 0.8), 0, 100)

Для применимости нужен хотя бы один конечный измеренный вход из coupling.distance-own.avg, coupling.ce-packages.avg, coupling.ce.avg и coupling.ce.max. Декомпозиция использует те же четыре входа. Отсутствующая дистанция остаётся отсутствующей в метриках и покрытии; подстановка для пропущенного слагаемого в формуле не публикует измеренный ноль.

Coupling класса остаётся применимым при измеренном 0. Для coupling пространства имён нужно хотя бы одно конечное измеренное значение среди coupling.distance, coupling.ce-packages.avg, coupling.ce.avg, coupling.ce.max и coupling.ce. Пространство имён только с функциями и без всех пяти входов не получает встроенную оценку coupling. Когда применимое distance не опубликовано, формула использует только остальные измеренные входы; пространству имён без рёбер не назначается distance 1.

health.typing

Использует процент класса, когда есть что типизировать. Оценки пространства имён и проекта делят суммы типизированных объявлений на фактическую сумму параметров, возвращаемых значений и свойств. При нулевом знаменателе оценки нет; при положительном знаменателе и отсутствии типов оценка равна 0.

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%

Overall усредняет только доступные измерения, сохраняя указанный порядок и исходные веса и деля на сумму участвующих весов. Отсутствующим измерениям не подставляется нейтральное значение 75; измеренный 0 участвует. Без участвующих измерений значения overall нет.


Чтение оценок здоровья

Оценки здоровья доступны в нескольких форматах вывода:

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:

bin/qmx check src/ --format=json

health — текстовая таблица оценок здоровья в терминале:

bin/qmx check src/ --format=health

html — интерактивный отчёт с drill-down по пространствам имён и классам:

bin/qmx check src/ --format=html -o report.html

Подробнее о форматах вывода — в разделе Форматы вывода.

Что именно покрывает оценка

Оценка описывает ту часть кода, которую покрывают участвующие входы. Если TCC отсутствует, а cohesion даёт измеренный LCOM, покрытие оценки следует LCOM, а не отсутствующей половине TCC. Покрытие и декомпозиция следуют выбранной формуле, включая входы, которые авторская формула действительно читает; одного имени встроенной метрики недостаточно для подтверждения её данных.

Поэтому каждое измерение здоровья публикует рядом с оценкой поле coverage: .count самого узкого участвующего входного агрегата, размер популяции, долей которой этот счёт является, и имя самого .count (basis). Оценка при этом не приглушается покрытием: число публикуется, чтобы читатель мог судить сам, а не подмешивается в оценку (см. ADR 0062).

Состояния — measured, not-measured и not-applicable. При положительной подходящей популяции и отсутствии измеренного входа not-measured показывает 0/N; пустая популяция даёт not-applicable с причиной. Числовой 0 остаётся измеренным значением. Включённые встроенные измерения проекта сохраняются с null-оценкой, когда недоступны; выключенные опускаются. Отсутствующие значения декомпозиции равны null и сохраняют своё покрытие вместо числового 0. При выборе класса или пространства имён отсутствующая оценка не заменяется оценкой проекта.

Там, где покрытие не определено, поле говорит об этом явно и с причиной, а не показывает ноль: health.overall складывается из остальных измерений, health.typing считается из сумм typed/total, для которых .count не публикуется, а оценка уровня класса или отфильтрованного пространства имён вообще не является агрегатом по символам.

Входы классов и size.symbol-class-count считают объявления, включая отдельные объявления одного имени. Размеры выборок используют ту же популяцию; покрытие не ограничивается сверху ради сокрытия расхождения.

Отклонение от оригинальной спецификации

Метрики графа связей определены на логических именах. Если у имени несколько объявлений, его графовые значения входят в агрегаты namespace и project по одному разу на объявление. Это расширение метрик графа с весами по числу объявлений.

Покрытие выводится в --format=json (объект coverage у каждого измерения), в --format=health (колонка Coverage плюс строка на измерение в декомпозиции), в --format=summary (строка под каждой оценкой) и в --format=html (объект summary.healthCoverage и подготовленная summary.healthDecomposition рядом с summary.healthScores). Viewer отображает эти данные PHP без вычисления формул в JavaScript.

Участники cohesion ранжируются по TCC для всей выбранной области, если она измерена хотя бы у одного кандидата, включая числовой ноль; иначе — по LCOM, от большего значения к меньшему. Кандидаты без выбранной метрики опускаются. Прогон без агрегированных метрик проекта не публикует health: его популяции неизвестны, а не равны нулю.

Покрытие меньше 100% — не обязательно дефект прогона. Часть разрывов постоянна по построению: у маленьких классов TCC может отсутствовать, а у пространства имён, объявляющего одни только голые enum, нет собственной абстрактности — голый enum намеренно вне этого знаменателя (см. Distance from Main Sequence), — поэтому собственная distance для него не публикуется, хотя популяция его считает: тип он объявляет. Знаменатель намеренно не сужен до тех пространств имён, до которых агрегат дошёл: знаменатель, равный собственному обходу агрегата, печатает 100% по построению и не способен показать, куда обход не добрался, — а ради этого строка и существует.


Настройка

Допустимые ключи

Каждая запись 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 и сообщением, называющим написанное и допустимое — ни один из этих случаев не игнорируется молча. Ключ распознаётся и при значении ~, поэтому warnign: ~ тоже отклоняется.

Между пресетами и вашим файлом

computed_metrics сливается метрика за метрикой, а внутри одной метрики — по ключам: метрика пресета и ключи вашего файла для того же имени объединяются, а метрика, которую другой слой не называет, остаётся как есть.

  • threshold раскрывается в warning и error в написавшем его слое, поэтому файл, написавший поверх threshold пресета только warning, сохраняет значение пресета как error.
  • formula задаёт все уровни, а formulas.<level> уточняет один уровень — какими бы слоями они ни были написаны: formulas: {class: …} файла поверх formula пресета меняет только уровень class.
  • Метрика убирается только через enabled: false. computed_metrics: {} и health.complexity: ~ оставляют значения нижнего слоя — ни то, ни другое не сбрасывает ничего к встроенным умолчаниям.
  • exclude_health накапливается между слоями.

Правила, общие для всех ключей конфигурации, — в разделе Как сливаются слои.

Настройка порогов

# qmx.yaml
computed_metrics:
  health.complexity:
    warning: 60    # Stricter than default 50
    error: 30      # Stricter than default 25

Отключение измерения

computed_metrics:
  health.typing:
    enabled: false

Или через CLI:

bin/qmx check src/ --exclude-health=typing

Оба варианта убирают измерение и его каноническое слагаемое overall. Исходные веса и порядок сохраняются; среднее делится на сумму участвующих весов. Неподдерживаемая авторская форма overall отказывает с источником выбранной формулы. Перепиши её в поддерживаемую каноническую форму взвешенного среднего или явно обработай доступность входов в авторской формуле.

Два похожих переключателя, которые делают разное

Каждое встроенное измерение — сам себе производитель, поэтому его можно выключить двумя способами, читающимися почти одинаково:

  • 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 без них — число, а число формулой не является.

Авторская формула остаётся авторской даже при дословном копировании встроенной. Она использует Always для выбранной формулы, и константа 80 не требует встроенных входов. Изменение только метаданных сохраняет встроенную применимость. Константа не может заявлять декомпозицию или покрытие встроенной формулы; без известной измеренной популяции символов её покрытие равно not-applicable.

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-method-count class
cohesion.tcc-method-count class
cohesion.unread-trait-count 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.abstractness namespace
coupling.distance namespace
coupling.ca-own namespace
coupling.ce-own namespace
coupling.instability-own namespace
coupling.abstractness-own namespace
coupling.distance-own namespace
coupling.distance-own.avg project
coupling.distance-own.count project
size.symbol-declaring-namespace-count 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.

coupling.instability-own (как и coupling.cbo-own) доступен только на пространстве имён, объявляющем собственный тип: на остальных ключ отсутствует, а не равен 0. Формула, читающая m["coupling.instability-own"] без ??, не получает там значения, и такое пространство имён попадает в computedMetricOutcomes; чтобы сохранить значение, пишите m["coupling.instability-own"] ?? 0. Пространство имён с типом, но без собственных рёбер (Ca-own = Ce-own = 0), несёт 0 по соглашению.

Это не исчерпывающий список — в формулах можно использовать любую метрику, собираемую Qualimetrix, по её ключу. Команда bin/qmx check src/ --format=metrics покажет все доступные метрики и их точные ключи для вашего проекта.

Неизвестные ссылки на метрики

Если формула ссылается на несуществующий ключ метрики (например, опечатка m["complexity.ccn.abg"] вместо m["complexity.ccn.avg"]), Qualimetrix выдаст явную ошибку вместо молчаливого возврата нуля. Используй ??, когда запасное значение имеет смысл для формулы; отсутствие входа отличается от выбранного 0 или нейтрального значения.

Метрики, которых нет на уровне

Формула выполняется на каждом из своих levels:, и ключ проверяется на этом уровне:

  • Ни один символ уровня не несёт ключ, а формула читает его без ?? — ошибка конфигурации (код выхода 3). Сюда относится и чтение другой computed-метрики на уровне, которого нет в её собственных levels:: computed.a с levels: [class] нельзя читать без ?? в формуле уровня project, и ошибка называет уровни, на которых computed.a публикуется. Уровень project, унаследовавший формулу namespace, проверяется как project.
  • Ключ есть у части символов — символы без него не получают значения вместо сфабрикованного 0, а успешный отчёт содержит структурную сводку по метрике и уровню: отдельные счётчики недостающих входов и null-результатов, точные недостающие ключи и не более трёх детерминированных примеров точных предметов.

m["a"] ?? m["b"] читает b только там, где нет a, поэтому символ пропускается, лишь когда у него нет ни того, ни другого. Чтобы значение получил каждый символ, завершите цепочку литералом: m["a"] ?? m["b"] ?? 0.

Тернарный оператор читает только ту ветвь, которую выбрал. m["size.method-count"] > 0 ? 7 : m["cohesion.tcc"] читает cohesion.tcc лишь у класса без методов, поэтому ключ, который читает только одна ветвь, никогда не даёт ошибку конфигурации: каждый символ судится по ветви, выбранной его собственными значениями, а символ, чья ветвь читает отсутствующий у него ключ, попадает в ту же сводку отсутствующих значений. Правая часть and / or судится так же. Условие выполняется всегда, поэтому чтение без ?? в нём считается как в арифметике — отсутствующая метрика выбрала бы ветвь ни на чём (null > 0 ложно); защищайте его через ??. Ключ, который читают обе ветви, считается прочитанным.

weighted_mean принимает упорядоченные чередующиеся допускающие null значения и положительные конечные числовые веса. Пропускается только null, 0 сохраняется; без участвующих значений результат равен null. Этот null сохраняется только в точной форме внешнего clamp от weighted mean; обычный clamp от null завершается ошибкой. Остальные функции сохраняют семантику PHP. Неприменимость встроенной формулы не создаёт сообщения; отсутствие у применимой встроенной формулы и ошибки вычисления отказывают с источником выбранной формулы и кодом 3, без успешного частичного отчёта.

Доступные функции

Функция Описание
min(a, b) Минимум из двух значений
max(a, b) Максимум из двух значений
abs(x) Модуль числа
sqrt(x) Квадратный корень
log(x) Натуральный логарифм
log10(x) Десятичный логарифм
clamp(value, min, max) Ограничение значения диапазоном [min, max]
weighted_mean(value, weight, ...) Среднее участвующих nullable-значений с положительными конечными весами
?? Null coalescing (значение по умолчанию, если метрики нет)
** Возведение в степень

Выбирай осмысленные значения по умолчанию

Используй ??, когда запасное значение имеет смысл для формулы. Отсутствие входа отличается от выбранного 0 или нейтрального значения; допускающие null значения weighted mean могут оставаться отсутствующими.

Граница для отдельного класса

В docblock класса можно установить конечную границу: @qmx-threshold health.cohesion 40 либо другое точное встроенное правило health. Поддерживается одна граница: число или одинаковые warning/error. Она меняет находки этого объявления; пороги неймспейса и проекта остаются в конфигурации, а действие директивы проверяет qmx directives. Пользовательские метрики computed.* сохраняют пороги уровня конфигурации.