История вопроса:
Go отличился от многих языков таким подходом к обработке ошибок, где ошибки — это значения, а не исключения. Такой дизайн был выбран ради прозрачности и простоты: всякий раз, когда что-то может пойти не так, функция возвращает ошибку явно как второе возвращаемое значение.
Проблема:
Многие новички пытаются применить к Go привычные паттерны try-catch, теряются в большом количестве проверок ошибок или не используют обёртывание ошибок для передачи контекста. Это приводит к потере информативности и трудному дебагу.
Решение:
В Go функция, которая может вернуть ошибку, объявляется так:
func doSomething() (ResultType, error) { // ... if somethingWrong { return nil, errors.New("something went wrong") } return result, nil }
Проверка ошибок — обязанность вызывающей стороны:
res, err := doSomething() if err != nil { log.Fatalf("process failed: %w", err) }
Go 1.13 и выше позволяют "оборачивать" ошибки с помощью fmt.Errorf("%w", err) для построения цепочек ошибок. Это улучшает диагностику и продвигает best practice контекстного описания ошибок.
Ключевые особенности:
Зачем создавать свои типы ошибок, если можно просто возвращать errors.New("...")?
Правильный ответ: собственные типы ошибок позволяют не только сообщить текст ошибки, но и сохранить дополнительную информацию для дальнейшей обработки (например, коды возврата, контекст), а также реализовать более тонкую обработку через type assertion.
Пример:
type NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) } // Проверка if _, ok := err.(NotFoundError); ok { // обработка ошибки поиска }
Является ли panic хорошей заменой error для критических ошибок?
Нет, panic в Go применяется только в действительно безвыходных ситуациях — например, при ошибках самой программы (программных инвариантах), но не для обычных сбоев (например, не удалось открыть файл). Использование panic для сигнализации о рутинных ошибках — зло и приводит к нечитабельному, неуправляемому коду.
Что происxодит, если return пропустить обработку ошибки (err) во вложенных функциях?
Ошибка "теряется", код продолжает исполняться, что может привести к распространению ошибочного состояния. Всегда важно правильно обрабатывать каждый возврат ошибки.
_ = ...Каждая функция просто возвращает errors.New(...), ошибки не завернуты, типы ошибок разные, но обработка всегда одна — логируется и выбрасывается. В результате лог-файлы наполнены неинформативными сообщениями, их невозможно отследить до исходной причины.
Плюсы:
Минусы:
Используются обёртывания ошибок через fmt.Errorf("%w", err), собственные ошибки с полезными полями, проверки через errors.Is()/errors.As(). Благодаря этому, можно логировать с деталями, отделять бизнес-ошибки от сбоев окружения и писать надёжный retry-логика.
Плюсы:
Минусы: