Обработка ошибок в Go — ошибка это значение, а не катастрофа
В большинстве языков ошибка — это исключение: оно «выпрыгивает» из функции, летит вверх по стеку и где-то перехватывается try/catch. В Go этого нет. Ошибка — обычное значение, которое функция возвращает наряду с результатом, а вызывающий код проверяет его сразу: if err != nil. Решение, что делать со сбоем, принимается на месте, а не где-то выше по стеку, и компилятор не даёт «случайно» проигнорировать возврат, если вы его читаете.
Простота тут обманчива. За if err != nil стоит несколько механизмов, на которых валятся на собеседованиях: error — это интерфейс с единственным методом Error() string, а значит свой тип ошибки можно объявить без единого импорта. Обёртывание через fmt.Errorf("...: %w", err) сохраняет ссылку на исходную ошибку, и именно по этой Unwrap-цепочке ходят errors.Is (ищет конкретное значение-sentinel) и errors.As (ищет ошибку конкретного типа) — путать их нельзя. А самая коварная ловушка вообще не про библиотеку: лишнее := во вложенном блоке затеняет внешнюю переменную, и ошибка тихо теряется. Эта тема разбирает обработку ошибок по слоям — от интерфейса до затенения.
Карта темы
- Интерфейс error —
errorэто встроенный интерфейс с одним методомError() string; любой тип с этим методом — ошибка, и объявить свою можно без импортов. - Ошибки как значения — ошибка возвращается как обычное значение (обычно последним), проверяется через
if err != nil, а sentinel-ошибки объявляются переменными. - errors.Is и errors.As —
%wоборачивает ошибку вUnwrap-цепочку;errors.Isищет в ней значение,errors.As— тип. - Затенение ошибки — лишнее
:=во вложенной области создаёт новую переменную и теряет внешнюю ошибку;go vetэто ловит.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Ждать try/catch и исключений | В Go нет исключений для обычных ошибок — ошибка это возврат |
| Думать, что для своего типа ошибки нужен импорт | error — встроенный интерфейс; достаточно метода Error() string |
| Возвращать ошибку не последним значением | Нарушение идиомы; читатели ждут (результат, error) |
Оборачивать через %v вместо %w | Теряется Unwrap-цепочка, и errors.Is/errors.As перестают находить |
Путать errors.Is и errors.As | Is сравнивает со значением, As извлекает по типу — не взаимозаменяемы |
Сравнивать обёрнутую ошибку через == | После обёртки err == ErrNotFound ложно; нужен errors.Is |
Лишнее := во вложенном блоке | Затеняет внешнюю переменную — ошибка молча теряется |
Жёстко возвращать nil на пути ошибки | Сбой проглатывается, наверх уходит ложный «успех» |
Значение для собеседований
Обработка ошибок — базовая тема Go-интервью на любом уровне. Интервьюер проверяет не знание слова «error», а рабочую модель: понимаете ли вы, что ошибка это значение, и владеете ли механикой обёртывания и распаковки.
Что обычно проверяют:
- Что такое тип
errorи что значит «возвращать ошибки как значения» — интерфейс с одним методом, проверка черезif err != nil. - Как объявить свой тип ошибки без импортов и почему он удовлетворяет
errorнеявно. - Как
%w,errors.Isиerrors.Asработают вместе, и в чём разница междуIsиAs. - Почему обёрнутую ошибку нельзя сравнивать через
==и зачем нуженerrors.Asдля доступа к полям типизированной ошибки. - Почему функция может вернуть
nil-ошибку даже при сбое — жёсткийreturn nilили затенение через:=.
Типичный неверный ответ: «errors.Is и errors.As — это одно и то же, просто разный синтаксис». Это запускает разбор того, что errors.Is ходит по Unwrap-цепочке и сравнивает со значением-sentinel, а errors.As ищет в той же цепочке ошибку нужного типа и присваивает её в указатель, давая доступ к её полям.