0Pricing
Go Academy · Урок

Создание пользовательских ошибок

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 — локальная установка не требуется.

Все уроки этого курса

  1. Интерфейс error
  2. Создание пользовательских ошибок
  3. Обёртывание и распаковка ошибок
  4. panic, recover и defer
← Назад к Go Academy