Источник: https://t.me/ntuzov/552
Высокая цикломатическая сложность — это проблема. Guard Expression — одно из её решений, которое хорошо работает в большинстве ситуаций.
Давайте начнём с осознания проблемы. Представим вот такой утрированный пример:
func processOrder(order Order) error {
if order != nil {
if order.Items != nil {
if len(order.Items) > 0 {
discount := calculateDiscount(order.Total)
order.Total -= discount
saveOrder(order)
return nil
} else {
return errors.New("empty order items")
}
} else {
return errors.New("items list is nil")
}
} else {
return errors.New("order not found")
}
}
Что здесь не так? Ну то есть, умными словами, без ”…” и “спагетти-код”. А проблема в том, что у этой функции высокая цикломатическая сложность. Чтобы понять что это значит, приведу определение этого термина:
Цикломатическая сложность – это количество возможных путей выполнения кода. Проще говоря, чем больше условий и ветвлений в коде, тем она выше и тем сложнее понять, как именно работает код.
Окей, проблему осознали, но как её решать? А вот тут как раз и приходит на помощь Guard Expression. Суть простая — мы выносим все проверки в начало функции и сразу возвращаем результат, если что-то не так. Смотрите, как преобразится наш код:
func processOrder(order Order) error {
if order == nil {
return errors.New("order not found")
}
if order.Items == nil {
return errors.New("items list is nil")
}
if len(order.Items) == 0 {
return errors.New("empty order items")
}
discount := calculateDiscount(order.Total)
order.Total -= discount
saveOrder(order)
return nil
}
Стало намного проще, не так ли? Мы сразу чётко разделяем - вот проверки, вот бизнес-логика. Код читается сверху вниз, плоско, как книга.
К слову, на мой взгляд, else
вообще полезен только в редких случаях. Нет, я не отношусь к радикалам, которые вообще его отрицают, но в большинстве случаев без него действительно лучше, и это тесно связано с темой поста. Задумайтесь 🤓
UPD: в комментариях подсказывают, что многие знакомы с этим подходом под другим названием — early return.
В Go это особенно актуально, так как обработка ошибок у нас явная, и такой подход делает её более понятной.
И бонусом держите линтер gocyclo
, которой проверяет цикломатическую сложность и ругается, если она слишком высокая (да, он есть в golangci-lint
).
📂 Best Practices | Последнее изменение: 02.12.2024 13:19