# Обработка ошибок в Go

> В Go ошибки — обычные значения типа error, которые функции возвращают явно; вызывающий код проверяет их через if err != nil, оборачивает с контекстом и анализирует через errors.Is и errors.As.

Источник: https://llmdocs.ru/wiki/go-errors/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Принцип**: ошибки — значения, возвращаемые явно
- **Тип**: встроенный интерфейс error с методом Error() string
- **Обёртывание**: fmt.Errorf с %w, errors.Is и errors.As (Go 1.13, 2019)
- **Несколько ошибок**: errors.Join (Go 1.20)
- **Исключительные ситуации**: panic и recover
- **Синтаксический сахар**: предложения отклонены (2019, 2025)
**Обработка ошибок в Go** построена не на исключениях, а на **явных возвращаемых значениях**. Функция, которая может завершиться неудачей, возвращает последним результатом значение встроенного типа `error`; вызывающий код обязан его проверить. Отсюда знаменитая конструкция `if err != nil`, которая встречается в Go-коде повсюду. Роб Пайк сформулировал философию так: «ошибки — это значения», и с ними можно программировать, как с любыми другими данными.[5]

Подход делает пути ошибок видимыми в коде: читая функцию, сразу видно, где что-то может пойти не так и что тогда произойдёт.

## Тип error

```go
type error interface {
    Error() string
}
```

Любой тип с методом `Error() string` является ошибкой. Типичная функция возвращает результат и ошибку:

```go
func ReadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("чтение конфигурации %s: %w", path, err)
    }
    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("разбор конфигурации %s: %w", path, err)
    }
    return &cfg, nil
}

func main() {
    cfg, err := ReadConfig("app.json")
    if err != nil {
        log.Fatal(err)   // чтение конфигурации app.json: open app.json: no such file or directory
    }
    _ = cfg
}
```

По соглашению при ненулевой ошибке остальные возвращаемые значения не используются, а сообщения об ошибках пишут со строчной буквы и без точки в конце, потому что их часто вкладывают друг в друга.[4]

## Обёртывание и анализ

Go 1.13 (2019) добавил стандартный механизм **обёртывания**: глагол `%w` в `fmt.Errorf` сохраняет исходную ошибку внутри новой, добавляя контекст. Функции пакета `errors` разворачивают цепочку:[2]

```go
var ErrNotFound = errors.New("не найдено")      // ошибка-маркер (sentinel)

type ValidationError struct {                     // собственный тип ошибки с данными
    Field string
}
func (e *ValidationError) Error() string { return "некорректное поле " + e.Field }

err := service.GetUser(ctx, id)
switch {
case errors.Is(err, ErrNotFound):                 // есть ли в цепочке конкретная ошибка
    http.Error(w, "нет такого пользователя", http.StatusNotFound)
case errors.Is(err, context.DeadlineExceeded):
    http.Error(w, "таймаут", http.StatusGatewayTimeout)
default:
    var ve *ValidationError
    if errors.As(err, &ve) {                      // есть ли ошибка нужного типа — и достать её
        http.Error(w, ve.Error(), http.StatusBadRequest)
        return
    }
    http.Error(w, "внутренняя ошибка", http.StatusInternalServerError)
}
```

- **`errors.Is`** сравнивает с конкретным значением — для ошибок-маркеров вроде `io.EOF` или `sql.ErrNoRows`.
- **`errors.As`** ищет ошибку определённого типа и даёт доступ к её полям.
- **`errors.Join`** (Go 1.20) объединяет несколько ошибок в одну — например, при закрытии нескольких ресурсов.[3]

Сравнивать ошибки по тексту сообщения (`err.Error() == "..."`) не следует — текст может измениться.

## panic и recover

`panic` предназначен для действительно исключительных ситуаций — ошибок программиста и нарушенных инвариантов: выход за границы массива, разыменование `nil`, невозможное состояние. Паника раскручивает стек, выполняя отложенные (`defer`) вызовы, и завершает программу, если её не перехватить через `recover` в отложенной функции. HTTP-серверы перехватывают панику в обработчике, чтобы один сбойный запрос не уронил весь сервис. Для ожидаемых ситуаций — файла нет, сеть недоступна, данные некорректны — используют обычные ошибки, а не панику.[1]

## Критика и попытки упростить

Главная претензия к подходу — многословие: в типичной функции значительная часть строк приходится на `if err != nil { return ..., err }`. Команда Go несколько раз предлагала сократить запись — встроенная функция `try` (2019), оператор `?` по образцу [Rust](https://llmdocs.ru/wiki/rust-errors.md), — но предложения не нашли поддержки сообщества. В 2025 году команда объявила, что больше не планирует менять синтаксис обработки ошибок, сосредоточившись на инструментах и библиотеках.[6]

Сторонники подхода отмечают, что явность — достоинство: ошибка не может «пролететь» через несколько уровней незамеченной, как необработанное исключение, а контекст, добавляемый на каждом уровне, даёт понятные сообщения вроде «сохранение заказа 42: запись в базу: connection refused».

## Практические советы

- **Добавляйте контекст** при передаче ошибки наверх: что делала функция и с какими данными.
- **Обрабатывайте ошибку один раз**: либо залогируйте, либо верните наверх — не то и другое сразу, иначе одна ошибка появится в логах многократно.
- **Не игнорируйте ошибки** через `_` без веской причины; линтеры (`errcheck`, `go vet` и сборки вроде golangci-lint) находят пропущенные проверки.
- **Экспортируйте ошибки-маркеры и типы** в библиотеках, чтобы пользователи могли их различать, а внутренние детали оборачивайте.
- Учитывайте ошибки отмены и таймаута контекста — в сетевом коде с [горутинами](https://llmdocs.ru/wiki/goroutines-channels.md) они встречаются постоянно.

## Сравнение с другими языками

| Язык | Механизм |
|---|---|
| Go | значение `error`, явная проверка |
| [Rust](https://llmdocs.ru/wiki/rust.md) | тип `Result<T, E>`, компилятор требует обработать, оператор `?` для проброса |
| [Python](https://llmdocs.ru/wiki/python.md), Java | исключения, `try`/`except` |
| C | коды возврата и `errno` |

## См. также

- [Go (язык)](https://llmdocs.ru/wiki/go.md)
- [Обработка ошибок в Rust (Result, ?)](https://llmdocs.ru/wiki/rust-errors.md)
- [Горутины и каналы](https://llmdocs.ru/wiki/goroutines-channels.md)
- [Go-модули (go.mod)](https://llmdocs.ru/wiki/go-modules.md)
- [Тестирование в Go](https://llmdocs.ru/wiki/go-testing.md)

## Источники

1. [The Go Blog — Error handling and Go](https://go.dev/blog/error-handling-and-go)
2. [The Go Blog — Working with Errors in Go 1.13](https://go.dev/blog/go1.13-errors)
3. [Go — package errors](https://pkg.go.dev/errors)
4. [Effective Go — Errors](https://go.dev/doc/effective_go#errors)
5. [Rob Pike — Errors are values (2015)](https://go.dev/blog/errors-are-values)
6. [The Go Blog — On | No syntactic support for error handling (2025)](https://go.dev/blog/error-syntax)
