Источник: 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