Перейти к основному содержимому

Проверка типов и Panic

Когда вы оборачиваете ошибки через %w, вы создаете «цепочку» ошибок. Это очень полезно для отладки, но возникает вопрос: как проверить, является ли ошибка конкретным типом, если она скрыта под слоем вашего текста?

1. Как проверить тип ошибки: errors.Is и errors.As

Раньше (до Go 1.13) проверяли ошибки простым сравнением err == ErrNotFound. Теперь, когда есть обертки, нужно использовать специальные функции из пакета errors.

errors.Is: Проверка на конкретное значение

Используется, если ошибка — это заранее объявленная «константа» (например, io.EOF или sql.ErrNoRows).

var ErrNotFound = errors.New("не найдено")

func findUser() error {
return fmt.Errorf("ошибка БД: %w", ErrNotFound)
}

func main() {
err := findUser()

// errors.Is "разматывает" цепочку ошибок и ищет внутри ErrNotFound
if errors.Is(err, ErrNotFound) {
fmt.Println("Пользователь не найден, показываем 404")
}
}

errors.As: Проверка на тип (структуру)

Используется, если ваша ошибка — это сложная структура с данными (например, код ошибки или IP-адрес).

type DBError struct {
Code int
}
func (e *DBError) Error() string { return "ошибка БД" }

func main() {
err := &DBError{Code: 500}

var dbErr *DBError
// errors.As проверяет, можно ли "привести" ошибку к типу *DBError
if errors.As(err, &dbErr) {
fmt.Println("Код ошибки:", dbErr.Code)
}
}

2. Игнорирование ошибок: _

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

// Мы игнорируем ошибку, потому что нам не важно, удалось ли закрыть файл
_ = file.Close()

Совет: Делайте это как можно реже. Игнорирование ошибок — это главный источник багов в Go.

3. Когда panic — это не баг, а фича

Многие новички пытаются использовать panic как исключение (аналог throw в Java). Это ошибка.

  • panic останавливает выполнение программы.
  • Его стоит использовать только для неисправимых критических ситуаций (например, программа не может запуститься без конфиг-файла или повреждена сама память).
  • Для всего остального — ошибок, которые могут возникнуть в процессе работы (сеть отвалилась, пользователь ввел неверный пароль) — используйте error.
func initConfig() {
_, err := os.ReadFile("settings.yaml")
if err != nil {
// Программа не может работать без конфига, поэтому паникуем
panic("Критическая ошибка: конфиг не найден")
}
}

4. recover: Как "выжить" после паники

Если вы пишете сервер, то паника в одной горутине «уронит» весь процесс. Чтобы этого избежать, используют recover. Его можно вызвать только внутри defer.

func safeWorker() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Спаслись от паники:", r)
}
}()

// Какая-то логика, которая может упасть
panic("Что-то пошло не так!")
}

Итоговая шпаргалка

ИнструментКогда использовать
if err != nilПовседневная работа с ошибками.
fmt.Errorf("%w", err)Обертывание ошибки для добавления контекста.
errors.Is(err, target)Сравнение ошибки с известным значением.
errors.As(err, &target)Извлечение данных из ошибки-структуры.
panicТолько для фатальных сбоев при старте.
recoverДля защиты сервисов от падения из-за ошибок в горутинах.