Расскажите про идиоматическую обработку ошибок в Go.

Ответ

В Go принят подход явной обработки ошибок, в отличие от механизма исключений (exceptions) в других языках. Ошибка — это обычное значение, которое функция возвращает наравне с результатом. Это заставляет разработчика осознанно обрабатывать каждый возможный сбой.

Основной паттерн: Функция, которая может завершиться с ошибкой, возвращает результат и ошибку как последний параметр (value, error). Вызывающий код обязан проверить, не является ли ошибка nil.

import (
    "fmt"
    "os"
)

f, err := os.Open("filename.txt")
if err != nil {
    // Ошибка произошла, обрабатываем её
    log.Printf("error opening file: %v", err)
    return
}
defer f.Close()
// Ошибки нет, продолжаем работу с f

Ключевые концепции и инструменты:

  1. Интерфейс error: Любой тип, реализующий метод Error() string, удовлетворяет стандартному интерфейсу error. Это позволяет создавать кастомные типы ошибок с дополнительной информацией.

  2. Создание ошибок:

    • errors.New("сообщение"): Создает простую ошибку с текстовым сообщением.
    • fmt.Errorf("сообщение с форматом: %w", err): Создает ошибку с форматированием. Директива %w оборачивает (wraps) исходную ошибку err, сохраняя её в цепочке для дальнейшего анализа.
  3. Проверка и извлечение ошибок (пакет errors):

    • errors.Is(err, target): Проверяет, есть ли в цепочке ошибок err конкретная ошибка target (например, io.EOF). Это предпочтительнее, чем err == io.EOF, так как работает с обёрнутыми ошибками.
    • errors.As(err, &target): Проверяет, есть ли в цепочке ошибка, которую можно присвоить переменной target (например, кастомному типу ошибки), и в случае успеха извлекает её.
  4. Кастомные типы ошибок: Позволяют переносить больше контекста, чем просто строка.

    type MyError struct {
        Operation string
        Code      int
        Err       error
    }
    
    func (e *MyError) Error() string {
        return fmt.Sprintf("operation %s failed with code %d: %v", e.Operation, e.Code, e.Err)
    }