> Что делать, если нельзя писать логи внутри функции fetch, а можно только в функции main? (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Короткий ответ
Если логировать можно только в main, значит fetch должна возвращать результат или ошибку, а логирование делегируется вызывающему коду. Это соответствует принципу разделения ответственности: функция выполняет работу, а вызывающий решает, что с результатом делать. В Go это реализуется через возврат ошибок и прокидывание контекста. Такой подход упрощает тестирование и переиспользование функции.
Подробное объяснение
Ограничение на логирование внутри fetch - это осознанное архитектурное решение, которое заставляет разделять бизнес-логику и инфраструктурные concerns. Функция fetch должна быть чистой: принимать входные параметры, выполнять HTTP-запрос, возвращать данные и ошибку. Всё, что связано с логированием, метриками или трейсингом, выносится на уровень main (или middleware, если речь о HTTP-сервере).
Основная причина такого ограничения - тестируемость. Если fetch пишет логи, то в unit-тестах придётся либо мокать логгер, либо игнорировать вывод. Возврат ошибок позволяет тестировать поведение функции через проверку возвращаемых значений.
Второй аспект - гибкость. Вызывающий код может решить, как обрабатывать ошибку: логировать, отправлять в Sentry, ретраить или просто игнорировать. Если логирование зашито внутри fetch, такая гибкость теряется.
Третий момент - контекст. В main доступен полный контекст выполнения: какие аргументы переданы, какие флаги установлены, какая конфигурация загружена. Логировать с этим контекстом гораздо информативнее, чем внутри изолированной функции.
На практике
На практике это означает, что fetch должна возвращать структурированную ошибку с достаточной информацией для диагностики. Например, статус код, URL, тело ответа (если нужно). В main эта ошибка оборачивается в лог с дополнительным контекстом.
Если нужно логировать промежуточные шаги внутри fetch, можно использовать паттерн callback или передавать логгер как dependency injection. Но это уже нарушает исходное ограничение, поэтому лучше придерживаться простого подхода: функция возвращает результат, main решает, что с ним делать.
Для HTTP-серверов это ограничение обычно решается через middleware, который логирует все запросы и ответы. Сами хендлеры остаются чистыми.
Пример кода
GOpackage mainimport ("context""fmt""log""net/http""time")type FetchResult struct {StatusCode intBody []byteDuration time.Duration}func fetch(ctx context.Context, url string) (*FetchResult, error) {req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)if err != nil {return nil, fmt.Errorf("create request: %w", err)}start := time.Now()resp, err := http.DefaultClient.Do(req)if err != nil {return nil, fmt.Errorf("do request: %w", err)}defer resp.Body.Close()body := make([]byte, 0)buf := make([]byte, 1024)for {n, readErr := resp.Body.Read(buf)body = append(body, buf[:n]...)if readErr != nil {break}}return &FetchResult{StatusCode: resp.StatusCode,Body: body,Duration: time.Since(start),}, nil}func main() {ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()result, err := fetch(ctx, "https://example.com")if err != nil {log.Printf("fetch failed: %v", err)return}log.Printf("status=%d duration=%s body_len=%d",result.StatusCode, result.Duration, len(result.Body))}
Как отвечать на собеседовании
Начните с того, что это ограничение - не проблема, а архитектурное требование. Объясните, что fetch должна быть чистой функцией без side effects. Подчеркните важность возврата структурированных ошибок через fmt.Errorf с wrapping. Упомяните, что в main можно добавить контекст: URL, время выполнения, параметры запуска. Если интервьюер спрашивает про промежуточное логирование, предложите вариант с передачей логгера как параметра, но отметьте, что это нарушает исходное ограничение.
Что проверяет интервьюер
Интервьюер проверяет понимание разделения ответственности и умение проектировать чистые функции. Важно показать, что вы понимаете trade-off между удобством логирования и тестируемостью. Также проверяется знание паттернов обработки ошибок в Go: wrapping, errors.Is, errors.As. Дополнительно оценивается умение структурировать код так, чтобы main оставалась тонкой, а бизнес-логика - изолированной.
Типичные ошибки
- Попытка обойти ограничение через глобальный логгер или пакет
logвнутриfetch- это прямое нарушение условия. - Возврат слишком общих ошибок без контекста, например, просто
errors.New("request failed"). - Игнорирование контекста выполнения: не передавать
context.Contextвfetch. - Логирование в
mainбез дополнительной информации, простоlog.Println(err)- теряется диагностическая ценность. - Смешивание бизнес-ошибок и инфраструктурных: не различать ошибки сети, ошибки HTTP-статуса и ошибки парсинга.
> Похожие задачи по Go
Почему интерфейсы лучше размещать в месте их применения?
Как реализовать graceful shutdown в Go?
Что происходит с горутиной, если она пытается записать в канал после ошибки?
Как организовать прослушивание канала в отдельной горутине, чтобы избежать блокировок?
> Похожие задачи по backend
Почему интерфейсы лучше размещать в месте их применения?
Как реализовать graceful shutdown в Go?
Что происходит с горутиной, если она пытается записать в канал после ошибки?
Как организовать прослушивание канала в отдельной горутине, чтобы избежать блокировок?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью