Какие существуют способы обработки ошибок в Go и какова философия этого подхода?

Ответ

В Go обработка ошибок — это фундаментальная часть языка, основанная на идее, что ошибки являются обычными значениями, а не исключениями. Это поощряет явную проверку ошибок в коде.

Основные подходы:

  1. Явная проверка if err != nil Это самый распространенный и идиоматичный способ в Go. Функции, которые могут завершиться с ошибкой, возвращают её в качестве последнего значения. Вызывающий код обязан проверить это значение.

    file, err := os.Open("filename.txt")
    if err != nil {
        log.Printf("Не удалось открыть файл: %v", err)
        return err
    }
  2. Создание собственных ошибок Для описания специфических сбоев в вашем домене создаются кастомные ошибки.

    • errors.New(): для создания простых статических ошибок.
    • fmt.Errorf(): для создания ошибок с динамическим текстом.
      
      var ErrUserNotFound = errors.New("пользователь не найден")

    func GetUser(id int) (*User, error) { if id <= 0 { return nil, fmt.Errorf("некорректный ID пользователя: %d", id) } // ... логика поиска if user == nil { return nil, ErrUserNotFound } return user, nil }

  3. Оборачивание ошибок (Wrapping) Начиная с Go 1.13, появилась возможность оборачивать ошибки с помощью директивы %w в fmt.Errorf. Это позволяет сохранить контекст исходной ошибки.

    if err != nil {
        return fmt.Errorf("ошибка при чтении данных пользователя: %w", err)
    }
  4. Проверка типа и значения ошибки Пакет errors предоставляет инструменты для инспекции цепочки обернутых ошибок:

    • errors.Is(err, target): проверяет, есть ли в цепочке ошибок конкретная ошибка target (например, ErrUserNotFound).
    • errors.As(err, &target): проверяет, имеет ли ошибка в цепочке конкретный тип, и присваивает её переменной target.
      
      _, err := GetUser(1)
      if errors.Is(err, ErrUserNotFound) {
      // Обработка случая, когда пользователь не найден
      }

    var pathError *fs.PathError if errors.As(err, &pathError) { // Обработка ошибки, связанной с путем к файлу fmt.Println("Ошибка в пути:", pathError.Path) }

  5. Паника и восстановление (panic / recover) Этот механизм используется только для исключительных, невосстановимых ситуаций, таких как критические ошибки во время инициализации или ошибки программиста (например, выход за пределы среза). Его не следует использовать для обычной обработки ошибок, таких как сбой сетевого запроса или отсутствие файла.

    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Произошла паника, но мы её обработали:", r)
        }
    }()
    panic("произошло что-то непоправимое")