Проверка типов и 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 | Для защиты сервисов от падения из-за ошибок в горутинах. |