Как обрабатывать ошибки в Go? Что такое error wrapping?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка ошибок в Go
В Go обработка ошибок является явной и составной частью сигнатур функций. В отличие от исключений в других языках, ошибки в Go — это обычные значения, которые возвращаются как последнее значение из функций.
Основные подходы к обработке
1. Проверка ошибок после каждого вызова функции
file, err := os.Open("file.txt")
if err != nil {
// Обработка ошибки
log.Printf("Не удалось открыть файл: %v", err)
return err
}
defer file.Close()
2. Типизированные ошибки с errors.New() и fmt.Errorf()
var ErrNotFound = errors.New("ресурс не найден")
func FindUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("некорректный ID: %d", id)
}
// ... логика поиска
}
3. Сравнение ошибок с errors.Is() и errors.As()
// Проверка на конкретную ошибку
if errors.Is(err, os.ErrNotExist) {
// Обработка случая "файл не существует"
}
// Приведение к конкретному типу ошибки
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Ошибка пути: %s\n", pathErr.Path)
}
Error Wrapping (Обёртывание ошибок)
Error wrapping — это механизм добавления контекста к ошибке при её передаче вверх по стеку вызовов, сохраняя при этом оригинальную ошибку для последующего анализа.
Реализация через fmt.Errorf() с %w
func processFile(filename string) error {
data, err := readFile(filename)
if err != nil {
// Оборачиваем ошибку, добавляя контекст
return fmt.Errorf("не удалось обработать файл %s: %w", filename, err)
}
// ... обработка данных
return nil
}
func readFile(filename string) ([]byte, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("ошибка чтения файла: %w", err)
}
return content, nil
}
Практические аспекты wrapping
1. Сохранение цепочки ошибок
// Исходная ошибка
err := io.EOF
// Первый уровень обёртки
err = fmt.Errorf("чтение данных: %w", err)
// Второй уровень обёртки
err = fmt.Errorf("обработка запроса: %w", err)
// errors.Is() пройдёт по всей цепочке
fmt.Println(errors.Is(err, io.EOF)) // true
2. Кастомные обёрнутые ошибки
type ServiceError struct {
Operation string
Err error
}
func (e *ServiceError) Error() string {
return fmt.Sprintf("ошибка в %s: %v", e.Operation, e.Err)
}
func (e *ServiceError) Unwrap() error {
return e.Err
}
Best Practices
- Всегда проверяйте ошибки сразу после вызова функции
- Добавляйте контекст при передаче ошибки на уровень выше
- Используйте
errors.Is()вместо==для проверки ошибок - Избегайте избыточного обёртывания на одном уровне абстракции
- Создавайте sentinel-ошибки как переменные уровня пакета
- Логируйте ошибки только один раз на самом верхнем уровне
Пример полной обработки
func main() {
if err := runApp(); err != nil {
// Логирование на верхнем уровне
log.Printf("Сбой приложения: %v", err)
// Проверка конкретных ошибок для пользователя
if errors.Is(err, ErrConfigNotFound) {
fmt.Println("Конфигурационный файл не найден")
}
os.Exit(1)
}
}
Error wrapping стал стандартом начиная с Go 1.13, обеспечивая баланс между добавлением полезного контекста и сохранением возможности анализа исходных ошибок. Этот подход делает отладку более простой, так как цепочка ошибок показывает полный путь возникновения проблемы через все уровни абстракции приложения.