> Что делать, если нельзя писать логи внутри функции 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, который логирует все запросы и ответы. Сами хендлеры остаются чистыми.

Пример кода

GO
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
)
type FetchResult struct {
StatusCode int
Body []byte
Duration 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-статуса и ошибки парсинга.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью