🤨 Оборачивать каждую ошибку контекстом не нужно — 6 июля 2026 г. в 10:12:01.079
🤨 Оборачивать каждую ошибку контекстом не нужно Популярный совет в Go звучит разумно. Оборачивайте каждую ошибку, пока она всплывает по стеку вызовов, чтобы всегда знать, откуда она пришла: func getUser(id string) (*User, error) { u, err := db.QueryUser(id) if err != nil { return nil, fmt.Errorf("getUser: query failed: %w", err) } return u, nil } В маленьком проекте это работает. Но в системе, где один запрос проходит через шесть сервисов, вы получаете строки ошибок вот такого вида: processPayment: validateAccount: fetchBalance: getUser: query failed: context deadline exceeded Это не наблюдаемость, а стектрейс, переодетый в сообщение об ошибке. Вы дублируете то, что структурированное логирование уже даёт (имя функции, файл, строку), и платите за аллокацию fmt.Errorf на каждом несчастливом пути. ➡️ Что делать вместо Оборачивать ошибки на границах доверия. Это вызов другого сервиса, слой базы данных или внешняя зависимость. Внутри одного пакета пусть ошибка летит как есть, а работу делает логгер: // На границе оборачиваем func (s *PaymentService) Process(ctx context.Context, req *ProcessRequest) error { if err := s.ledger.Debit(ctx, req.AccountID, req.Amount); err != nil { return fmt.Errorf("debit account %s: %w", req.AccountID, err) } return nil } // Внутри пакета просто возвращаем func validate(req *ProcessRequest) error { if req.Amount <= 0 { return ErrInvalidAmount } return nil } Оборачивание уместно на стыках, где теряется контекст. Внутри пакета оно лишь раздувает сообщения и аллокации. Применяйте его как эвристику под конкретную границу, а не как правило для каждой функции.

