← Назад к вопросам

Что такое когнитивная сложность кода?

2.0 Middle🔥 151 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когнитивная сложность кода (Cognitive Complexity)

Определение

Когнитивная сложность — это метрика, которая измеряет сложность понимания кода человеком. Это не то же самое, что цикломатическая сложность (которая считает количество путей выполнения).

Введена SonarSource в 2016 году. Идея: сложность ≠ количество условий, сложность = трудность понимания.

Цикломатическая сложность vs Когнитивная

Цикломатическая сложность (McCabe complexity)

Считает количество независимых путей выполнения:

def calculate_fee(age, income, credit_score):
    if age > 18:           # +1
        if income > 50000: # +1
            if credit_score > 700:  # +1
                fee = 0.01
            else:
                fee = 0.05
        else:
            fee = 0.1
    else:
        fee = 0.2
    return fee

# Цикломатическая сложность = 4 (4 пути выполнения)

Проблема: это считает пути выполнения, не сложность понимания.

Когнитивная сложность

Шкала для человеческого восприятия:

Без вложений:     +1
Вложенная логика: +1 за каждый уровень вложения
Рекурсия:         +1
Логические операторы: &&, || → +1
Скачки управления: break, continue → +1
Тернарный оператор: ?:  → +1
Обработчик исключений: catch, finally → +1
def calculate_fee(age, income, credit_score):
    if age > 18:  # +1
        if income > 50000:  # +2 (вложенная на уровень 2)
            if credit_score > 700:  # +3 (вложенная на уровень 3)
                fee = 0.01
            else:
                fee = 0.05
        else:
            fee = 0.1
    else:
        fee = 0.2
    return fee

# Когнитивная сложность = 1 + 2 + 3 = 6

Примеры когнитивной сложности

Пример 1: Простая функция (низкая сложность)

def is_adult(age: int) -> bool:
    """Когнитивная сложность = 1"""
    return age >= 18

# +1 за условие

Пример 2: Вложенные условия (высокая сложность)

def process_order(order):
    """Когнитивная сложность = 6"""
    if order.is_valid():  # +1
        if order.total > 100:  # +2 (вложенная)
            if order.customer.is_premium():  # +3 (дважды вложенная)
                discount = 0.1
            else:  # else считается частью if, не добавляет
                discount = 0.05
        else:
            discount = 0
    else:
        return None
    
    return order.total * (1 - discount)

Пример 3: Логические операторы (повышают сложность)

def can_buy(user):
    """Когнитивная сложность = 3"""
    if user.age >= 18 and user.has_money and user.is_not_banned:  # +1, но 2 && = +2 больше
        return True
    return False

# +1 за if
# +1 за &&
# +1 за &&
# = 3

Пример 4: Цикл с условиями

def find_premium_users(users):
    """Когнитивная сложность = 4"""
    result = []
    for user in users:  # +1 (цикл не добавляет, но условия внутри считаются)
        if user.is_premium:  # +1
            if user.balance > 1000:  # +2 (вложенное в if)
                if not user.is_suspended:  # +3 (дважды вложенное)
                    result.append(user)
    return result

Пример 5: Рекурсия (повышает сложность)

def factorial(n):
    """Когнитивная сложность = 2"""
    if n <= 1:  # +1
        return 1
    return n * factorial(n - 1)  # +1 за рекурсию

Плохой код с высокой когнитивной сложностью

def process_data(data, config, user, cache):
    """Когнитивная сложность = 12 (ОЧЕНЬ ПЛОХО!)"""
    if data is not None:
        if config.get('validate'):
            if user.is_admin or user.is_moderator:
                if cache.has(data.id):
                    return cache.get(data.id)
                try:
                    if data.status == 'active':
                        for item in data.items:
                            if item.valid and not item.deleted:
                                process_item(item)
                except Exception as e:
                    if config.get('verbose'):
                        log_error(e)
    return None

Это невозможно понять с первого раза!

Хороший код с низкой когнитивной сложностью

def process_data(data: dict, config: dict, user: User, cache: Cache) -> None:
    """Когнитивная сложность = 2 (ОЧЕНЬ ХОРОШО!)"""
    if not should_process(data, config, user):
        return None
    
    return get_or_process_items(data, cache)

def should_process(data, config, user) -> bool:
    """Когнитивная сложность = 3"""
    if data is None:
        return False
    if not config.get('validate'):
        return False
    if not (user.is_admin or user.is_moderator):
        return False
    return True

def get_or_process_items(data, cache):
    """Когнитивная сложность = 2"""
    cached = cache.get(data.id)
    if cached:
        return cached
    
    return process_items(data.items)

def process_items(items) -> list:
    """Когнитивная сложность = 1"""
    valid_items = [item for item in items if is_valid(item)]
    return [process_item(item) for item in valid_items]

def is_valid(item) -> bool:
    """Когнитивная сложность = 1"""
    return item.valid and not item.deleted

Метрики когнитивной сложности

От 1 до 5:      ХОРОШО (легко понять)
От 6 до 15:     НОРМАЛЬНО (нужно работать)
От 16 до 25:    ПЛОХО (требует рефакторинга)
Больше 25:      ОЧЕНЬ ПЛОХО (переписывать нужно)

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

SonarQube

# Установка
pip install sonar-scanner

# Запуск
sonar-scanner \
  -Dsonar.projectKey=myproject \
  -Dsonar.sources=. \
  -Dsonar.host.url=http://localhost:9000

Python linters

# radon — инструмент для измерения сложности
pip install radon

# Проверить когнитивную сложность
radon cc mymodule.py --total-average

# Вывод:
# mymodule.py
#     M 4:0 calculate_fee - A (1.0)
#     M 15:0 process_order - B (6.0)
#     M 28:0 find_users - B (4.0)

pylint

pip install pylint
pylint --disable=all --enable=too-many-branches,too-many-nested-blocks mymodule.py

Best Practices для снижения когнитивной сложности

1. Извлекай условия в отдельные функции

# ❌ Плохо
if user.age >= 18 and user.has_money and user.is_not_banned:
    proceed()

# ✅ Хорошо
if can_purchase(user):
    proceed()

def can_purchase(user) -> bool:
    return user.age >= 18 and user.has_money and user.is_not_banned

2. Используй guard clauses (ранний выход)

# ❌ Плохо (глубокая вложенность)
def process(data):
    if data:
        if data.valid:
            if data.complete:
                return handle(data)
    return None

# ✅ Хорошо (плоская структура)
def process(data):
    if not data:
        return None
    if not data.valid:
        return None
    if not data.complete:
        return None
    return handle(data)

3. Упрощай логические выражения

# ❌ Плохо (много &&)
if user.is_admin and user.is_active and user.has_permission and user.is_verified:
    grant_access()

# ✅ Хорошо
if is_authorized_admin(user):
    grant_access()

def is_authorized_admin(user) -> bool:
    return all([
        user.is_admin,
        user.is_active,
        user.has_permission,
        user.is_verified
    ])

4. Используй list comprehensions вместо циклов

# ❌ Плохо (цикл с вложениями)
result = []
for item in items:
    if item.valid:
        if not item.deleted:
            result.append(process(item))

# ✅ Хорошо (list comprehension)
result = [
    process(item)
    for item in items
    if item.valid and not item.deleted
]

5. Избегай глубокой вложенности

Максимум 3 уровня вложенности!

# ❌ 4+ уровня вложенности
for user in users:
    if user.active:
        for order in user.orders:
            if order.status == 'pending':
                for item in order.items:
                    if item.in_stock:
                        process(item)

# ✅ Макимум 2 уровня
for user in active_users:
    process_pending_orders(user.orders)

def process_pending_orders(orders):
    for order in pending_orders:
        items = in_stock_items(order.items)
        for item in items:
            process(item)

Итог

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

✓ Низкая сложность (1-5): код легко понять ✓ Средняя (6-15): требует внимания ✓ Высокая (16-25): нужен рефакторинг ✓ Очень высокая (25+): переписывать

Правило: Если функция требует более 15 когнитивной сложности — разбей её на несколько функций. Это не только улучшит читаемость, но и упростит тестирование и поддержку кода.