Философия обработки ошибок в Go
Go не использует исключения (exceptions). Вместо этого ошибки — это значения, которые возвращаются вместе с результатом. Это делает потоки выполнения явными и предсказуемыми.
▸Базовый паттерн
1result, err := someOperation()2if err != nil {3 return fmt.Errorf("operation failed: %w", err)4}
Создание ошибок
▸errors.New
1import "errors"23var ErrNotFound = errors.New("not found")4var ErrUnauthorized = errors.New("unauthorized")56func GetUser(id int) (*User, error) {7 user := db.Find(id)8 if user == nil {9 return nil, ErrNotFound10 }11 return user, nil12}
▸fmt.Errorf
1func process(id int) error {2 user, err := GetUser(id)3 if err != nil {4 return fmt.Errorf("failed to get user %d: %w", id, err)5 }6 // ...7}
▸Кастомные ошибки
1type ValidationError struct {2 Field string3 Message string4}56func (e *ValidationError) Error() string {7 return fmt.Sprintf("validation error on field %s: %s", e.Field, e.Message)8}910func ValidateEmail(email string) error {11 if !strings.Contains(email, "@") {12 return &ValidationError{13 Field: "email",14 Message: "invalid email format",15 }16 }17 return nil18}
Проверка ошибок
▸errors.Is
1import "errors"23err := GetUser(1)4if errors.Is(err, ErrNotFound) {5 fmt.Println("User not found")6} else if err != nil {7 fmt.Println("Other error:", err)8}
▸errors.As
1var validationErr *ValidationError2if errors.As(err, &validationErr) {3 fmt.Printf("Validation failed: %s on field %s\n",4 validationErr.Message, validationErr.Field)5}
▸Проверка типа
1var pathErr *os.PathError2if errors.As(err, &pathErr) {3 fmt.Println("Path:", pathErr.Path)4 fmt.Println("Op:", pathErr.Op)5}
Sentinel Errors
▸Определение
1// errors.go2var (3 ErrNotFound = errors.New("not found")4 ErrAlreadyExists = errors.New("already exists")5 ErrPermission = errors.New("permission denied")6 ErrTimeout = errors.New("timeout")7)
▸Использование
1func FindUser(id int) (*User, error) {2 user := db.Find(id)3 if user == nil {4 return nil, ErrNotFound5 }6 return user, nil7}89// Вызывающий код10user, err := FindUser(1)11switch {12case errors.Is(err, ErrNotFound):13 // Обработка "не найдено"14case err != nil:15 // Другие ошибки16}
Обработка ошибок в цепочке
▸%w для оборачивания
1func ProcessOrder(orderID int) error {2 order, err := GetOrder(orderID)3 if err != nil {4 return fmt.Errorf("ProcessOrder: %w", err)5 }67 err = ValidateOrder(order)8 if err != nil {9 return fmt.Errorf("ProcessOrder: %w", err)10 }1112 return nil13}
▸Проверка вложенных ошибок
1err := ProcessOrder(123)2if errors.Is(err, ErrNotFound) {3 // Это сработает, даже если ошибка обёрнута4 fmt.Println("Order not found")5}
Паника и восстановление
▸Когда использовать panic
1func MustParse(s string) Template {2 t, err := Parse(s)3 if err != nil {4 panic(err) // Только для фатальных ошибок инициализации5 }6 return t7}
▸Восстановление
1func safeGoroutine() {2 defer func() {3 if r := recover(); r != nil {4 log.Printf("recovered from panic: %v", r)5 }6 }()78 // Код, который может вызвать panic9 riskyOperation()10}
best practices
▸Не игнорируйте ошибки
1// Плохо2result, _ := someOperation()34// Хорошо5result, err := someOperation()6if err != nil {7 return fmt.Errorf("someOperation: %w", err)8}
▸Оборачивайте ошибки с контекстом
1// Плохо2return err34// Хорошо5return fmt.Errorf("failed to process order %d: %w", orderID, err)
▸Используйте sentinel errors
1// Плохо2if err.Error() == "not found" {34// Хорошо5if errors.Is(err, ErrNotFound) {
▸Избегайте nil pointer
1func GetUser(id int) (*User, error) {2 user := db.Find(id)3 if user == nil {4 return nil, ErrNotFound5 }6 return user, nil // Явный nil check7}
Заключение
Обработка ошибок в Go — это не просто проверка err != nil. Важно понимать sentinel errors, оборачивание с контекстом, errors.Is/As и когда использовать panic. На собеседовании спрашивают про разницу между errors.Is и ==, как оборачивать ошибки и когда паниковать.