Irbisa · Шпаргалка
Ошибки и паники
error как значение, обёртка через %w, errors.Is/As, panic и recover
Как устроена обработка ошибок в Go: что такое тип
errorи почему Go возвращает ошибки, а не бросает исключения?func parsePort(s string) (int, error) { n, err := strconv.Atoi(s) if err != nil { return 0, fmt.Errorf("parse port %q: %w", s, err) } if n < 1 || n > 65535 {…Что такое sentinel-ошибка, как её объявить и сравнить и почему
errors.New("x") == errors.New("x")даёт false?var ErrUserNotFound = errors.New("user not found") func main() { a, b := errors.New("user not found"), errors.New("user not found") fmt.Println(a == b) // false: two distinct pointers fmt.Println(a.Error() == b.Error()) // true: same text, and irrelevant…Какие соглашения есть для текста ошибки в Go и как добавить к ошибке контекст, прежде чем вернуть её выше?
Как создать свой тип ошибки и когда тип лучше, чем
errors.Newилиfmt.Errorf?type ValidationError struct { Field string Reason string} func (e *ValidationError) Error() string {…Как работают
panicиrecoverи где нужно вызватьrecover, чтобы он перехватил панику?func SafeDo(job Job) (err error) { defer func() { if r := recover(); r != nil { // called directly by the deferred func: works err = fmt.Errorf("job %s panicked: %v\n%s", job.ID, r, debug.Stack()) } }()…Чем отличаются
panic,os.Exitиlog.Fatalи что в каждом случае происходит с отложенными вызовами?func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, "report:", err) os.Exit(1) // every defer inside run has already finished }}…Когда допустимо вызвать
panicвместо возврата ошибки и что такое соглашениеMust?Чем отличается
fmt.Errorfс%wотfmt.Errorfс%vи когда стоит сознательно взять%v?func main() { lost := fmt.Errorf("query user: %v", sql.ErrNoRows) kept := fmt.Errorf("query user: %w", sql.ErrNoRows) fmt.Println(lost.Error() == kept.Error()) // true: same text fmt.Println(errors.Is(lost, sql.ErrNoRows)) // false: chain cut…Что напечатает эта программа и где
errors.Isпрекращает обход цепочки?var ErrNotFound = errors.New("not found") func main() { err := fmt.Errorf("get order 7: %w", ErrNotFound) fmt.Println(err == ErrNotFound) fmt.Println(errors.Is(err, ErrNotFound)) cut := fmt.Errorf("handler: %v", err) fmt.Println(errors.Is(cut, ErrNotFound)) _, openErr := os.Open("/no/such/file") wrapped := fmt.Errorf("load config: %w", openErr) fmt.Println(os.IsNotExist(wrapped), errors.Is(wrapped, fs.ErrNotExist))}Как достать типизированную ошибку вроде
*ValidationErrorиз обёрнутой цепочки и почему утверждение типа перестаёт работать, как только добавили контекст?type ValidationError struct{ Field string } func (e *ValidationError) Error() string { return "invalid " + e.Field } func main() { var err error = &ValidationError{Field: "email"}…Как сделать, чтобы ваш тип ошибки работал с
errors.Isиerrors.As: что делают методыUnwrapиIs?type NotFound struct { Key string cause error} func (e *NotFound) Error() string { return "not found: " + e.Key }…Как объединить несколько ошибок в одну и найдёт ли
errors.Isкаждую из них?var ( ErrMissingField = errors.New("missing field") ErrBadFormat = errors.New("bad format")) func Validate(c Config) error {…Эта функция компилируется и проходит тесты. Что не так с обработкой ошибок в ней?
func SaveReport(path string, r Report) error { f, err := os.Create(path) if err != nil { log.Printf("create report: %v", err) return err } defer f.Close() data, _ := json.Marshal(r) if _, err := f.Write(data); err != nil { return errors.New("Failed to write report: " + err.Error()) } return nil}Найдите баг:
Transferпереводит деньги, но каждый вызов возвращает ошибку, и клиенты повторяют запрос.func (s *Store) Transfer(ctx context.Context, from, to int64, amount int) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("transfer: begin: %w", err) } defer tx.Rollback() if err := debit(ctx, tx, from, amount); err != nil { return fmt.Errorf("transfer: debit %d: %w", from, err) } if err := credit(ctx, tx, to, amount); err != nil { return fmt.Errorf("transfer: credit %d: %w", to, err) } return fmt.Errorf("transfer: commit: %w", tx.Commit())}Что происходит, когда горутина паникует, может ли другая горутина её восстановить и как защитить горутины, которые вы запускаете?
// Go runs fn in a new goroutine and turns a panic into a logged error.func Go(ctx context.Context, name string, fn func(context.Context)) { go func() { defer func() { if r := recover(); r != nil { slog.ErrorContext(ctx, "goroutine panicked",…Найдите баг: когда задача паникует, воркер продолжает работать, но задача засчитывается как успешная.
// runJob runs one job; a panic inside it must come back as an error.func runJob(job Job) error { var err error defer func() { if r := recover(); r != nil { err = fmt.Errorf("job %s panicked: %v", job.ID, r) } }() err = job.Run() return err}Есть ли у ошибок в Go стек-трейс? Как его получить, когда он нужен, и почему стандартная библиотека его не добавляет?
type stackError struct { err error pcs []uintptr} func (e *stackError) Error() string { return e.err.Error() }…Как сопоставлять доменные ошибки с кодами HTTP или gRPC, не раскрывая внутренности, и где должно жить это сопоставление?
// httpError translates a domain error into a status and a client-safe message.func httpError(err error) (int, string) { var ve *ValidationError switch { case errors.As(err, &ve): return http.StatusBadRequest, "invalid " + ve.Field…Как реализовать повтор с экспоненциальным backoff и ограничением числа попыток, который останавливается сразу после отмены контекста?
func Retry(ctx context.Context, attempts int, base time.Duration, op func(context.Context) error) error { var err error delay := base for i := range attempts { if err = op(ctx); err == nil || !retryable(err) { return err…Как спроектировать API ошибок публичного пакета (sentinel-значения, экспортируемые типы или интерфейсы поведения) и к чему обязывает каждый вариант?
// Only the behaviour is promised; notFoundError stays private and free to change.type notFoundError struct { Resource, Key string} func (e *notFoundError) Error() string { return e.Resource + " " + e.Key + " not found" }…Что, кроме вызова
recover, должен делать правильно middleware восстановления в HTTP (вспомнитеhttp.ErrAbortHandlerи ответ, который уже наполовину записан)?type recorder struct { http.ResponseWriter wrote bool} func (r *recorder) WriteHeader(code int) { r.wrote = true; r.ResponseWriter.WriteHeader(code) }…