Инженерия ПО
Метрики кода
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