Создание пользовательских ошибок
errors.New, fmt.Errorf и специальные ошибки
«Создание пользовательских ошибок» — бесплатный урок Go Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.
Зачем нужны пользовательские типы ошибок?
Пользовательские типы ошибок содержат структурированные данные помимо текстового сообщения, позволяя вызывающему коду проверять конкретные условия ошибки и реагировать на них:
package main
import "fmt"
// Custom error type carries extra context
type HTTPError struct {
StatusCode int
Message string
}
func (e *HTTPError) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message)
}
func fetch(url string) error {
return &HTTPError{404, "page not found"}
}
func main() {
err := fetch("http://example.com")
fmt.Println(err) // HTTP 404: page not found
}Реализация интерфейса error
Любой тип с методом Error() string соответствует error:
package main
import "fmt"
type ValidationError struct {
Field string
Value interface{}
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation: field %q value %v — %s", e.Field, e.Value, e.Message)
}
func validateAge(age int) error {
if age < 0 || age > 150 {
return &ValidationError{"age", age, "must be between 0 and 150"}
}
return nil
}
func main() {
fmt.Println(validateAge(-5))
}Ошибки-маркеры с помощью errors.New
Ошибки-маркеры — это переменные уровня пакета для известных условий ошибок:
package main
import ("fmt"; "errors")
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrAlreadyExists = errors.New("already exists")
)
func getUser(id int) (string, error) {
if id != 1 { return "", ErrNotFound }
return "Alice", nil
}
func main() {
_, err := getUser(99)
if errors.Is(err, ErrNotFound) {
fmt.Println("user does not exist")
}
}fmt.Errorf с %w для обёртывания
fmt.Errorf с %w оборачивает ошибку, сохраняя исходную ошибку для errors.Is и errors.As:
package main
import ("fmt"; "errors")
var ErrDatabase = errors.New("database error")
func queryDB() error {
return fmt.Errorf("queryDB: %w", ErrDatabase)
}
func main() {
err := queryDB()
fmt.Println(err) // queryDB: database error
fmt.Println(errors.Is(err, ErrDatabase)) // true — unwrapped
}Пользовательская ошибка с Unwrap
Реализуйте Unwrap() error в своём пользовательском типе ошибки, чтобы он участвовал в цепочках ошибок:
package main
import ("fmt"; "errors")
var ErrNotFound = errors.New("not found")
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query %q: %v", e.Query, e.Err)
}
func (e *QueryError) Unwrap() error { return e.Err }
func main() {
err := &QueryError{"SELECT *", ErrNotFound}
fmt.Println(err)
fmt.Println(errors.Is(err, ErrNotFound)) // true
}errors.Is — проверка идентичности
errors.Is проходит по цепочке ошибок в поисках ошибки, равной целевой:
package main
import ("fmt"; "errors")
var ErrTimeout = errors.New("timeout")
func deepCall() error {
return fmt.Errorf("layer3: %w", fmt.Errorf("layer2: %w", ErrTimeout))
}
func main() {
err := deepCall()
fmt.Println(errors.Is(err, ErrTimeout)) // true — found deep in chain
}errors.As — извлечение типа
errors.As находит в цепочке первую ошибку, соответствующую целевому типу, и присваивает её:
package main
import ("fmt"; "errors")
type PermissionError struct{ User string }
func (e *PermissionError) Error() string { return "permission denied for " + e.User }
func action(user string) error {
return fmt.Errorf("action: %w", &PermissionError{user})
}
func main() {
err := action("bob")
var pe *PermissionError
if errors.As(err, &pe) {
fmt.Println("denied:", pe.User) // denied: bob
}
}Обёртывание нескольких ошибок (Go 1.20+)
В Go 1.20 появились errors.Join и fmt.Errorf с несколькими %w для объединения ошибок:
package main
import ("fmt"; "errors")
func validate(name, email string) error {
var errs []error
if name == "" { errs = append(errs, errors.New("name required")) }
if email == "" { errs = append(errs, errors.New("email required")) }
return errors.Join(errs...)
}
func main() {
err := validate("", "")
fmt.Println(err)
// name required
// email required
}Соглашения об именовании типов ошибок
Соглашения об именовании переменных и типов ошибок в Go:
- Переменные ошибок-маркеров:
ErrXxx(например,ErrNotFound) - Пользовательские типы ошибок:
XxxError(например,ValidationError) - Для ошибки-маркера используется переменная уровня пакета, а для содержательных ошибок — тип указателя
- Экспортируйте их, чтобы вызывающий код мог использовать их в
errors.Isиerrors.As
Когда использовать каждый подход
Выбор подходящего подхода к обработке ошибок:
errors.New— простая ошибка без дополнительных данных, проверка идентичности ошибки-маркераfmt.Errorf— форматированное сообщение с возможностью обёртывания- Пользовательский тип — когда вызывающему коду нужны структурированные данные (поля, коды)
errors.Join— объединение нескольких ошибок проверки
Быстрая проверка
Какая функция позволяет извлечь конкретный тип ошибки из цепочки ошибок?
Повторение: пользовательские ошибки
Краткое содержание:
- Реализуйте
Error() string, чтобы создать пользовательский тип ошибки - Добавьте
Unwrap() error, чтобы участвовать в цепочках ошибок - Используйте
errors.Isдля проверки идентичности, аerrors.As— для извлечения типа - Ошибки-маркеры:
ErrXxx; пользовательские типы:XxxError errors.Joinобъединяет несколько ошибок (Go 1.20+)
Часто задаваемые вопросы
Урок «Создание пользовательских ошибок» бесплатный?
Да — полный текст урока «Создание пользовательских ошибок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.
Чему я научусь в уроке «Создание пользовательских ошибок»?
errors.New, fmt.Errorf и специальные ошибки Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Go Academy?
Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Создание пользовательских ошибок»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Go Academy?
Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Интерфейс error
- Создание пользовательских ошибок
- Обёртывание и распаковка ошибок
- panic, recover и defer