Расскажите про идиоматическую обработку ошибок в 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)
    }

Ответ 18+ 🔞

А, Го, ёпта! Ну, слушай, тут у них, блядь, целая философия на ровном месте. Подход, сука, явной обработки ошибок. Это как в жизни, понимаешь? Не как у этих, блядь, изнеженных питонятников с их исключениями, которые летят нахуй непонятно куда и ломают всё. Тут ты, сука, сам за всё отвечаешь. Ошибка — это просто ещё одно значение, которое функция тебе, падла, возвращает. Как сдача в магазине. Получил — смотри, не обманули ли.

Основной прикол, на котором всё держится: Функция, которая может накосячить, возвращает тебе два значения: результат и ошибку. И ошибка — это всегда последний аргумент, (value, error). И ты, мудак, обязан её проверить, а не делать вид, что её нет. Вот смотри, как это выглядит, в рот меня чих-пых:

import (
    "fmt"
    "os"
)

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

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

  1. Интерфейс error: Это ж гениально просто, ёба! Любой тип, у которого есть метод Error() string, — уже считается ошибкой. Можно свои ошибки городить, с блэкджеком и контекстом.

  2. Как ошибки рождаются:

    • errors.New("сообщение"): Банально, как пять копеек. Создал ошибку с текстом и пошёл дальше.
    • fmt.Errorf("сообщение с форматом: %w", err): А вот это уже поинтереснее. Директива %w — это, сука, обёртка. Она берёт старую ошибку 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)
    }

    Вот так вот, блядь. Теперь в ошибке лежит и операция, и код, и предыдущая ошибка. Красота, ебать мои старые костыли!