Ошибки и defer

Тип error: возврат, проверка и оборачивание

Содержание курса

Проверка ошибки через if err != nil и функция validatePositive

После вызова функции, возвращающей error, принято проверять результат немедленно и завершать ветку с ошибкой явно — через return, return err или передачу выше:

result, err := someFunc()
if err != nil {
    return err // или log + return, или return 0, err
}
// только здесь result можно использовать согласно контракту функции

Ключ — в завершении ветки. Если после if err != nil { ... } нет return или другого выхода, выполнение продолжится с тем же result. Что именно содержит result при ошибке — зависит от контракта конкретной функции: одни возвращают нулевое значение, другие — частичный результат (например, io.Reader.Read сообщает, сколько байт успело прочитаться до сбоя). Считать result пригодным без явного разрешения контракта нельзя.

Соберём полный пример:

package main

import (
    "errors"
    "fmt"
)

var ErrNotPositive = errors.New("число должно быть положительным")

func validatePositive(n int) (int, error) {
    if n <= 0 {
        return 0, ErrNotPositive
    }
    return n, nil
}

func process(n int) (int, error) {
    result, err := validatePositive(n)
    if err != nil {
        return 0, fmt.Errorf("process: %w", err)
    }
    return result, nil
}

func main() {
    var n int
    fmt.Scan(&n)

    result, err := process(n)
    if err != nil {
        fmt.Println("ошибка:", err)
        if errors.Is(err, ErrNotPositive) {
            fmt.Println("причина: число не положительное")
        }
        return
    }
    fmt.Println("результат:", result)
}

Запустим с вводом -3:

ошибка: process: число должно быть положительным
причина: число не положительное

Несколько деталей, на которые стоит обратить внимание.

validatePositive и process возвращают 0 в позиции int при ошибке — не потому что 0 что-то означает, а просто как неиспользуемое значение: такой контракт выбран явно. Именно поэтому process, увидев err != nil от validatePositive, немедленно возвращает 0, fmt.Errorf(...) — значение результата отбрасывается, ошибка с контекстом поднимается выше.

errors.Is внутри main находит исходный ErrNotPositive даже сквозь обёртку "process: " — этот механизм разобран в предыдущей секции, здесь мы видим его в действии целиком.

Теперь о том, что происходит при пропуске проверки:

result, _ := process(-3) // ошибка выброшена
fmt.Println(result)      // печатает 0 — но это нулевое значение по контракту process при сбое, не результат вычисления

Компилятор не запретит такой код. result равен 0 не потому что вычисление дало ноль, а потому что произошёл сбой — и process намеренно вернула 0 по своему контракту. Никакого предупреждения нет — ошибка растворилась. Поэтому _ для ошибки допустим только когда вы точно знаете, что сбой в данном месте невозможен или заведомо несущественен.

Как проверить обёрнутую ошибку

Функция возвращает ошибку отсутствия записи, а следующий слой добавляет контекст через fmt.Errorf с %w. Сравнение текста ломается при смене формулировки. Для известных sentinel errors используйте errors.Is, чтобы проверить причину через цепочку обёрток.

Разделите три случая: успешный результат, ожидаемое отсутствие записи и технический сбой. Пользовательский ответ может отличаться, но внутренний текст ошибки не обязан целиком попадать наружу. При обработке не забудьте вернуть ошибку выше, если текущий слой её не исправил. Игнорирование err и использование нулевого результата часто делает сбой похожим на реальные данные.

Продолжите разбор: defer: порядок выполнения и отличие ожидаемой ошибки от panic.


Что делать, если запрос пришёл дважды, а база недоступна?

В сервисе мало вернуть error: нужно понять, можно ли повторить операцию, как ответить клиенту и не создать одну запись дважды. Такие ситуации разберём на практике в платном курсе:

Для старта понадобятся структуры, интерфейсы, HTTP, JSON и go test. Эти темы вы изучите здесь; программу продолжения можно посмотреть уже сейчас.

Посмотреть задачи курса →

Выбрать другое направление в каталоге Latorn →

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.