Что такое метрики кода (cyclomatic complexity)?python-55

  • N — количество узлов в графе потока управления
  • P — количество компонентов связности (обычно 1 для одного модуля)

Практические правила расчета:

  1. Начальная сложность: 1
  2. Каждое условие (if, elif, else, while, for, case) увеличивает на 1
  3. Каждый логический оператор (and, or) увеличивает на 1
  4. Каждый try/except увеличивает на 1

Примеры расчета

Пример 1: Простая функция

def simple_func(x):  # CC = 1
    return x * 2

Сложность: 1 (только один путь выполнения)

Пример 2: Условное выражение

def check_value(x):  # CC = 2
    if x > 0:       # +1
        return True
    return False

Сложность: 2 (два возможных пути)

Пример 3: Множественные условия

def process_value(x):       # CC = 4
    if x < 0:              # +1
        return "Negative"
    elif x == 0:           # +1
        return "Zero"
    else:                  # +1
        if x > 100:        # +1
            return "Large"
        return "Positive"

Сложность: 4 (четыре независимых пути)

Интерпретация значений

Сложность Уровень риска Рекомендации
1-10 Низкий Код прост для понимания и поддержки
11-20 Умеренный Рассмотреть рефакторинг
21-30 Высокий Требуется рефакторинг
31+ Очень высокий Критически сложный код

Инструменты для измерения

1. Radon

# Установка: pip install radon
# Анализ кода:
bash
radon cc my_module.py -a

2. Pylint

pylint --reports=y --disable=all --enable=cyclic-imports,design my_module.py

3. SonarQube

Интегрированная платформа для анализа качества кода с поддержкой многих метрик.

Как снизить цикломатическую сложность?

1. Разделение сложных функций

# Плохо:
def process_data(data):  # CC = 8
    if cond1:
        ...
    elif cond2:
        ...
    # ... много условий

# Хорошо:
def _process_case1(data):
    ...

def _process_case2(data):
    ...

def process_data(data):  # CC = 3
    if cond1:
        return _process_case1(data)
    elif cond2:
        return _process_case2(data)

2. Использование полиморфизма

# Вместо множества if/elif
class DataProcessor:
    def process(self):
        raise NotImplementedError

class Type1Processor(DataProcessor):
    def process(self):
        ...

class Type2Processor(DataProcessor):
    def process(self):
        ...

3. Применение стратегий обработки

# Использование словаря вместо условий
process_strategies = {
    'type1': handle_type1,
    'type2': handle_type2,
}

def process_data(data_type):
    return process_strategies.get(data_type, default_handler)()

Почему это важно?

  1. Сопровождаемость: Код с высокой сложностью сложнее понимать и изменять
  2. Тестируемость: Каждый путь требует отдельного теста
  3. Надежность: Больше путей → больше возможных ошибок
  4. Совместимость: Входит в стандарты качества кода (ISO 26262, MISRA)

Резюмируем

цикломатическая сложность — это важная метрика качества кода, помогающая оценивать его сложность и поддерживаемость. Поддержание низкой цикломатической сложности (желательно <10) делает код чище, надежнее и легче для тестирования.