← Назад к вопросам

Какие знаешь виды работы с ошибками в Go?

1.0 Junior🔥 241 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные подходы к обработке ошибок в Go

В Go обработка ошибок является явной и составляет фундаментальную часть идиоматического кода. Вот основные виды и методы работы с ошибками:

1. Базовый паттерн "возврат ошибки"

Стандартный подход, где функция возвращает ошибку как последнее возвращаемое значение:

func ReadFile(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("не удалось прочитать файл %s: %w", filename, err)
    }
    return data, nil
}

Ключевые аспекты:

  • Проверка ошибок сразу после вызова функции
  • Использование fmt.Errorf с %w для обертывания ошибок (появилось в Go 1.13)
  • Явная обработка в вызывающем коде

2. Кастомные типы ошибок

Создание собственных типов ошибок для классификации и дополнительной информации:

type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("ошибка валидации поля %s: %s", e.Field, e.Message)
}

// Использование
func ValidateUser(user User) error {
    if user.Name == "" {
        return ValidationError{Field: "Name", Message: "не может быть пустым"}
    }
    return nil
}

3. Проверка типов ошибок и обертывание

Методы для работы с обернутыми ошибками (Go 1.13+):

func ProcessFile(path string) error {
    if err := validatePath(path); err != nil {
        // Обертывание ошибки с сохранением контекста
        return fmt.Errorf("ошибка обработки файла: %w", err)
    }
    return nil
}

// Проверка типа ошибки
if err := ProcessFile("test.txt"); err != nil {
    var valErr ValidationError
    if errors.As(err, &valErr) {
        // Обработка конкретного типа ошибки
        fmt.Printf("Поле %s: %s\n", valErr.Field, valErr.Message)
    }
    
    // Проверка на конкретную ошибку
    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("Файл не существует")
    }
}

4. Паника и восстановление (panic/recover)

Использование для критических ошибок, которые не должны происходить в нормальных условиях:

func SafeExecute(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("паника восстановлена: %v", r)
        }
    }()
    
    fn()
    return nil
}

// Использование только для действительно невосстановимых состояний
func MustParse(s string) int {
    v, err := strconv.Atoi(s)
    if err != nil {
        panic(fmt.Sprintf("невозможно преобразовать %s в число", s))
    }
    return v
}

5. Пакет errors и стандартные практики

Стандартная библиотека предоставляет утилиты для работы с ошибками:

import "errors"

// Создание простых ошибок
var ErrNotFound = errors.New("ресурс не найден")
var ErrPermissionDenied = errors.New("доступ запрещен")

// Сравнение ошибок
if err == ErrNotFound {
    // Обработка
}

// Объединение ошибок (Go 1.20+)
func MultipleErrors() error {
    var errs []error
    // ... добавление ошибок
    return errors.Join(errs...)
}

6. Стратегии для повторных попыток (retry)

Обработка временных ошибок с повторными попытками:

func Retry(attempts int, delay time.Duration, fn func() error) error {
    for i := 0; i < attempts; i++ {
        err := fn()
        if err == nil {
            return nil
        }
        
        // Проверяем, стоит ли повторять
        if isTemporaryError(err) {
            time.Sleep(delay)
            continue
        }
        
        return err
    }
    return fmt.Errorf("превышено количество попыток: %d", attempts)
}

7. Контекстуальные ошибки

Использование context для отмены операций и передачи причин отмены:

func LongOperation(ctx context.Context) error {
    select {
    case <-time.After(5 * time.Second):
        return nil
    case <-ctx.Done():
        return ctx.Err() // Возвращает context.Canceled или context.DeadlineExceeded
    }
}

8. Логирование и мониторинг ошибок

Интеграция ошибок в системы мониторинга:

type ErrorWithContext struct {
    Err     error
    Context map[string]interface{}
}

func (e ErrorWithContext) Error() string {
    return fmt.Sprintf("%v (context: %v)", e.Err, e.Context)
}

func LogError(err error) {
    // Логирование с дополнительным контекстом
    log.Printf("ОШИБКА: %v\n", err)
    
    // Отправка в систему мониторинга
    metrics.Increment("error_count")
}

Ключевые принципы обработки ошибок в Go:

  • Явность лучше неявности — всегда проверяйте ошибки
  • Добавляйте контекст — оборачивайте ошибки с полезной информацией
  • Используйте типизированные ошибки для программной обработки
  • Избегайте паники в обычном потоке выполнения
  • Документируйте возвращаемые ошибки в комментариях к функциям
  • Сохраняйте оригинальную ошибку при обертывании для дебаггинга

Эти подходы позволяют создавать надежные и поддерживаемые приложения, где ошибки являются не исключительными ситуациями, а полноценной частью потока выполнения программы.