Инженерия ПО

Метрики кода

2019 год. Microsoft анализирует свою кодовую базу Windows через Code Hotspot метрики. Находит: 20% файлов содержат 80% дефектов. Эти файлы - hotspots: высокая cyclomatic complexity + высокий churn. Целенаправленный рефакторинг только этих 20% снижает defect rate на 35% при минимальных затратах.

  • **SonarQube** (10K+ enterprise deployments): CC, coupling, duplication - основные метрики Quality Gate перед production deploy
  • **CodeScene** (Adam Tornhill): behavioral code analysis через git history - используется в Spotify, Ericsson для приоритизации tech debt
  • **Google's Code Health**: внутренняя программа измерения code quality коррелирует с engineer retention - плохой код буквально отпугивает разработчиков

Cyclomatic Complexity: сложность как число путей

NASA Software Engineering Laboratory изучило связь между цикломатической сложностью и дефектами: модули с CC > 10 имеют в 2.5 раза больше дефектов чем модули с CC <= 10. Это не корреляция - это причинно-следственная связь: высокая CC означает много путей выполнения, каждый требует отдельного тест-кейса.

Cyclomatic Complexity (CC) = число независимых путей через код = E - N + 2P, где E - рёбра графа потока управления, N - узлы, P - компоненты связности. Упрощенная формула: CC = 1 + (число решений). Каждый if, else if, while, for, case, &&, || добавляет 1. CC 1-10: низкая сложность. CC 11-20: умеренная, внимание. CC 21-50: высокая, рефакторинг. CC > 50: нетестируемо.

CC == минимальное количество тест-кейсов для полного покрытия ветвей. CC = 15 означает нужно минимум 15 тестов. Это прямая связь с cost of testing. Google's Testing Blog: функции с CC > 6 статистически требуют переработки тестов при каждом изменении. Инструменты: ESLint (complexity rule), SonarQube, PyFlakes, Java's PMD.

Функция имеет CC = 15. Минимальное количество unit-тестов для полного покрытия ветвей?

Coupling: цена зависимостей

Michael Feathers в Working Effectively with Legacy Code: 'Модуль с высоким coupling невозможно тестировать изолированно'. Coupling - степень зависимости модулей друг от друга. Чем выше coupling, тем сильнее изменение в одном модуле распространяется по системе - ripple effect.

Afferent coupling (Ca): сколько других модулей зависит от данного. Высокий Ca = ответственный модуль, изменение в нем влияет на многих. Efferent coupling (Ce): сколько других модулей данный использует. Высокий Ce = зависимый модуль, уязвим к изменениям в зависимостях. Instability I = Ce / (Ca + Ce): 0 = максимально стабильный, 1 = максимально нестабильный. Нестабильные модули не должны зависеть от нестабильных.

Stable Dependencies Principle (SDP) - один из принципов component coupling от Uncle Bob: модуль должен зависеть только от более стабильных модулей. Граф зависимостей должен быть DAG (Directed Acyclic Graph) - циклические зависимости разрушают эту иерархию. Инструменты для обнаружения: Dependency Cruiser (JS), NDepend (.NET), Structure101 (Java).

Модуль OrderService имеет Ce = 12 (зависит от 12 модулей) и Ca = 2 (от него зависят 2 модуля). Instability I = ?

Cohesion: модуль делает одно хорошо

Cohesion - обратная сторона coupling: насколько методы и поля модуля связаны между собой. Высокая cohesion = методы работают с одними и теми же данными, реализуют одну концепцию. Низкая cohesion = 'utility class' с несвязанными функциями, God object. Правило: если класс или модуль трудно назвать без AND - cohesion низкая.

LCOM (Lack of Cohesion of Methods) - формальная метрика. LCOM4: число connected components в графе, где вершины - методы, рёбра - общие поля. LCOM4 = 1: один связный компонент, высокая cohesion. LCOM4 > 1: несвязные группы методов - класс можно разбить. Например, класс с методами read/write (используют connection) и методами parse/format (только строки) - LCOM4 = 2.

High cohesion + Low coupling - два крыла хорошего дизайна. Они взаимосвязаны: когда класс делает одно (high cohesion), у него меньше причин зависеть от других модулей (low coupling). God object нарушает оба принципа одновременно: делает всё (low cohesion) и зависит от всего (high coupling). SRP в SOLID - это принцип high cohesion на языке бизнес-причин для изменения.

Класс OrderProcessor содержит методы: processPayment(), sendEmail(), generatePDF(), updateInventory(). Что говорит это о cohesion?

Code Churn: история изменений как метрика риска

Michael Feathers и Adam Tornhill (CodeScene): корреляция между частотой изменений файла (churn) и числом дефектов - одна из самых сильных в software engineering исследованиях. Часто меняющийся файл с высокой complexity - hotspot: зона максимального технического долга и максимального риска.

Churn = число изменений файла за период. Сам по себе churn не плохой: активно развиваемый код меняется. Но комбинация churn + complexity создает hotspot. Визуализация: CodeScene (Adam Tornhill) строит 'code city' где радиус файла = complexity, цвет = churn. Красные большие круги - приоритет рефакторинга. Этот подход позволяет направлять усилия туда где ROI максимален.

Temporal coupling через git log - сильный инструмент обнаружения скрытых зависимостей. Если order.service.ts и payment.service.ts меняются вместе в 80% случаев - у них есть неявная связь, которая не выражена в коде явно. Adam Tornhill в книге Software Design X-Rays называет это 'change coupling' и показывает что оно часто предсказывает баги точнее чем static analysis.

Метрики кода объективны - высокая CC всегда плохо, низкий coupling всегда хорошо

Метрики - индикаторы, требующие контекста: высокая CC в state machine нормальна, нулевой coupling может означать дублирование

State machine с 50 состояниями имеет CC=50 по необходимости - это не технический долг. Полное decoupling иногда достигается дублированием кода - что хуже coupling. Метрики указывают на места для внимания, не диагностируют автоматически

Файл auth.service.ts имеет churn=95 (менялся 95 раз за полгода) но CC=3. Является ли он hotspot?

Связанные темы

Метрики кода дают количественную основу для качественных решений:

  • Clean Code — Принципы clean code снижают CC и улучшают cohesion - метрики подтверждают
  • Рефакторинг — Hotspot анализ (churn + complexity) определяет где рефакторинг даст максимальный ROI
  • SOLID принципы — SRP снижает coupling, DIP инвертирует зависимости - метрики Ca/Ce измеряют результат

Ключевые идеи

  • **Cyclomatic Complexity**: число независимых путей = минимальное число тест-кейсов; CC > 10 удваивает defect rate по NASA данным
  • **Coupling**: afferent (Ca) / efferent (Ce) / instability I = Ce/(Ca+Ce); нестабильные модули должны зависеть от стабильных
  • **Cohesion**: LCOM4 = число связных компонентов в методах; >1 означает класс можно разбить; high cohesion + low coupling = хороший дизайн
  • **Churn**: hotspot = высокий churn + высокая complexity; temporal coupling через git log обнаруживает скрытые зависимости

Вопросы для размышления

  • Можно ли иметь класс с идеальными метриками (CC=2, coupling=0, LCOM4=1) но плохим дизайном? Приведи пример.
  • Как использовать churn-анализ при планировании спринта? Какие решения он помогает принять?
  • Temporal coupling: два файла меняются вместе в 90% случаев. Это всегда плохо или иногда нормально? Когда каждый вариант?

Связанные уроки

  • se-15 — Clean code принципы - качественная основа, метрики измеряют их количественно
  • se-09 — Рефакторинг применяется там где метрики показывают проблемы
  • se-11 — Code review использует метрики как объективные критерии
  • se-05 — SOLID принципы снижают coupling и повышают cohesion - метрики это подтверждают
  • alg-02 — Cyclomatic complexity напрямую связана с number of paths - понятие из теории графов
  • stat-05-hypothesis
Метрики кода

0

1

Войти