Что из метрик кода действительно предсказывает поломки
Цикломатическая сложность, связность, расстояние до главной последовательности. Что из этих метрик подтверждено эмпирически, а что осталось фольклором.
Коротко. Самый устойчивый эмпирический предиктор дефектов — не сложность, а частота изменений вместе с размером. Метрики полезны для поиска выбросов и вредны в роли цели.
Метрики кода делятся на две группы: те, которые вычисляются точно, и те, которые предсказывают что-то полезное. Пересечение групп меньше, чем принято думать.
Разберём по порядку, потому что путаница здесь дорого стоит: на плохо выбранной метрике строят пороги в CI, и она начинает управлять кодом.
Цикломатическая сложность
Введена Маккейбом в 1976 году. Определяется точно: число независимых путей в графе управления, для структурированного кода — число точек ветвления плюс один.
Величина вычисляемая, воспроизводимая, не зависит от вкуса. С этим всё в порядке.
Проблема в другом: цикломатическая сложность очень сильно коррелирует с размером кода. Это воспроизведено многократно и неудивительно — в длинной функции больше ветвлений просто потому, что она длинная.
Отсюда неприятный для метрики вопрос: добавляет ли она предсказательную силу сверх количества строк? Если знание размера уже даёт прогноз, а сложность почти линейно из размера следует, то отдельной информации в ней немного.
Я не считаю, что метрику надо выбросить. Я считаю, что порог по ней в CI — плохая идея, и вот почему.
Метрика в роли цели
Поставили правило: сложность функции не выше десяти. Правило исполняется буквально: функция на тридцать разрезается на три по десять.
Суммарная сложность не изменилась. Число ветвлений то же. Но добавились три новых имени, три вызова и необходимость держать в голове, как части связаны. Читаемость могла ухудшиться, метрика — улучшилась.
Это закон Гудхарта в чистом виде: показатель, ставший целью, перестаёт быть хорошим показателем. Он применим к любой метрике кода, и это главная причина, по которой я не ставлю на них жёстких порогов.
Что работает вместо порога — сравнение с распределением по своему же проекту. Не «сложность выше десяти», а «эта функция в верхнем проценте по проекту». Такая формулировка указывает на выброс и не поощряет дробление ради числа.
Метрики связности Мартина
Здесь ситуация лучше, потому что величины описывают структуру, а не объём.
Для модуля определяются входящая связность Ca (сколько модулей
зависят от него) и исходящая Ce (от скольких зависит он). Из них —
нестабильность:
I = Ce / (Ca + Ce)
I = 0 — модуль максимально стабилен: от него зависят, он не зависит
ни от кого, менять его дорого. I = 1 — наоборот.
Отдельно вводится абстрактность A — доля абстрактных типов в модуле.
Принцип устойчивых абстракций утверждает, что стабильные модули должны
быть абстрактными, а нестабильные — конкретными. Отсюда расстояние
до главной последовательности:
D = |A + I − 1|
Модули с большим D — либо конкретные и при этом стабильные («болезненная
зона»: их дорого менять и невозможно расширять), либо абстрактные
и нестабильные («зона бесполезности»: абстракции, которыми никто
не пользуется).
Ценность этих величин не в пороге, а в том, что они делают видимым направление зависимостей. Модуль с высокой входящей связностью и высокой изменчивостью — это конкретная точка боли, и её видно числом, а не ощущением.
Что предсказывает дефекты лучше всего
Здесь эмпирика однозначнее, чем в остальных разделах.
Наиболее устойчивый результат в исследованиях предсказания дефектов: частота изменений файла — сколько раз его правили и насколько сильно — предсказывает дефекты лучше, чем статические метрики сложности. Работа Нагаппана и Болла об относительном изменении кода (2005) — одна из самых цитируемых в этой линии.
Объяснение простое и не требует теории: файл, который никто не трогал два года, не сломается, какой бы сложный он ни был. Файл, который правят каждую неделю, — это место, где живёт неопределённость требований.
Отсюда практика горячих точек: пересечение высокой частоты изменений и большого размера или сложности. Не одно и не другое, а именно пересечение. Файл, который часто меняют и который при этом велик, — кандидат на разделение с наибольшей отдачей.
Данные для этого уже есть: история репозитория. Считается одним проходом по журналу и не требует ни инструментов анализа, ни настройки.
Чем я пользуюсь
Частота изменений и размер, пересечением. Раз в квартал, по журналу репозитория. Верхние десять файлов — список кандидатов на разговор, а не на автоматическую переделку.
Направление зависимостей, тестом архитектуры. Не метрика, а правило: циклы запрещены, внутренности модуля закрыты. Ломает сборку — значит работает.
Цикломатическую сложность — как индикатор выброса, без порога в CI.
Чем не пользуюсь: индексом сопровождаемости. Он собран из нескольких метрик по формуле с подобранными коэффициентами, и происхождение этих коэффициентов не выдерживает вопроса «почему именно такие». Число получается, смысл — нет.
Где я могу быть неправ. В регламентной среде, где порог по сложности требуется внешним стандартом, спорить бессмысленно: это условие приёмки, а не инструмент улучшения кода. Тогда порог выполняется, а настоящая работа с горячими точками идёт отдельно и параллельно.