Ранний возврат

Есть вот такая функция, которая проверяет разрешения пользователя.

// CanEdit returns true if the user can edit objects.
func CanEdit(user *User) bool {
    if user.IsActive() {
        perms := user.Permissions()
        for _, perm := range perms {
            if perm == PermWrite {
                return true
            }
        }
        return false
    } else {
        return false
    }
}

Я спросил на канале, что люди о ней думают. Вот результаты опроса:

Что думаете о функции?

 4% Да это ж круто!

42% Сомнительно, но окэй
    ■■■■■■■■

46% Это конечно печально
    ■■■■■■■■■

 8% Вот мне лично это не интересно
    ■■

Что ж, давайте разбираться.

Оставим за скобками вопросики к типу User — например, почему CanEdit не сделан методом. Для простоты будем считать, что тип User менять нельзя.

Первая проблема — нет проверки на nil. Вторая — отсутствие early return (он же guard clause). С nil, думаю, и так понятно, а вот на раннем возврате остановимся подробнее.

В реальной жизни проверки в функциях часто бывают более сложными, вроде таких:

func Do() {
    if cond_1 {
        // do stuff
        if cond_2 {
            // do more stuff
            if cond_3 {
                // do even more stuff
            }
        }
    }
}

Читать такое больно из-за лесенки ифов. Лучше переделать на ранний возврат, а «основной сценарий» сделать без отступов:

func Do() {
    if !cond_1 {
        return
    }
    // do stuff

    if !cond_2 {
        return
    }
    // do more stuff

    if !cond_3 {
        return
    }
    // do even more stuff
}

Теперь явно видно, что делает функция, и что в ней может пойти не так.

Применим к CanEdit:

func CanEdit(user *User) bool {
    if user == nil || !user.IsActive() {
        return false
    }
    for _, perm := range user.Permissions() {
        if perm == PermRead {
            return true
        }
    }
    return false
}

Можно еще заменить цикл на slices.Contains (хотя это уже частности):

func CanEdit(user *User) bool {
    if user == nil || !user.IsActive() {
        return false
    }
    perms := user.Permissions()
    return slices.Contains(perms, PermRead)
}

Главное — не забывайте проверять на nil и использовать ранний возврат.

★ Подписывайтесь на новые заметки.