Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Когнитивная сложность кода (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 когнитивной сложности — разбей её на несколько функций. Это не только улучшит читаемость, но и упростит тестирование и поддержку кода.