🕳 context.Context: Хватит превращать контекст в мусорное ведро — 8 июля 2026 г. в 06:04:35.920
🕳 context.Context: Хватит превращать контекст в мусорное ведро Мы передаем ctx context.Context первым аргументом почти в каждую функцию. Это кровеносная система Go-приложений, которая отлично справляется с отменой операций и таймаутами. Но есть в интерфейсе контекста один метод, который открывает портал в ад - это Value(). Часто разработчики (особенно выходцы из языков с thread-local storage) смотрят на ctx.Value и думают: "О, отличная глобальная мапа! Положу-ка я сюда инстанс базы данных, логгер и данные пользователя, чтобы не прокидывать их через аргументы 10 функций". Давайте разберем, почему это архитектурное преступление. ❌ Проблема 1: Убийство статической типизации Сила Go - в строгой типизации на этапе компиляции. Когда вы кладете что-то в контекст, оно превращается в any (или interface{}). // Где-то в мидлваре ctx = context.WithValue(ctx, "db", dbConnection) // Где-то в репозитории db := ctx.Value("db").(*sql.DB) // Молимся, чтобы там не было nil Ваша функция теперь имеет скрытую зависимость. Глядя на сигнатуру func GetUser(ctx context.Context), невозможно понять, что для ее работы нужен коннект к БД. Узнаете вы об этом только в рантайме, когда словите panic: interface conversion. ❌ Проблема 2: Медленный поиск (O(N)) Контекст - это не map (хэш-таблица). Под капотом WithValue каждый раз создает новый узел, который ссылается на родительский контекст. Образуется связный список (дерево). Когда вы вызываете ctx.Value("key"), Go берет текущий узел и проверяет ключ. Если не нашел — идет к родителю. И так до самого верха. Если ваш ключ лежит в самом начале цепочки из 20 мидлварей, поиск будет прочесывать память каждый раз, убивая кэш процессора. Как использовать ctx.Value правильно? Официальная документация гласит: "Используйте значения контекста только для данных, привязанных к области видимости запроса (request-scoped data)". ✅ Идеальные кандидаты для ctx.Value: • TraceID / RequestID (для распределенного трейсинга). • IP-адрес клиента. • ID авторизованного пользователя (но не вся структура User с бизнес-логикой). То есть данные, которые нужны инфраструктуре (логгеру, метрикам), но никак не влияют на бизнес-логику функции. 🔥 Защита от коллизий ключей Никогда не используйте встроенные типы (например, string) в качестве ключей для WithValue. Если два разных пакета используют ключ "id", они перезапишут данные друг друга. Всегда создавайте неэкспортируемый кастомный тип: type contextKey string const userIDKey contextKey = "user_id" // Обертка для записи func WithUserID(ctx context.Context, id int) context.Context { return context.WithValue(ctx, userIDKey, id) } // Обертка для чтения (безопасная, возвращает (int, bool)) func UserIDFromContext(ctx context.Context) (int, bool) { id, ok := ctx.Value(userIDKey).(int) return id, ok } Про context.TODO(): Если вы пишете код и не знаете, откуда взять контекст (например, рефакторите старый легаси) - используйте context.TODO(). Технически это тот же context.Background(), но семантически это маячок для линтеров и коллег: "Я оставил здесь технический долг, позже нужно прокинуть нормальный контекст". #golang #architecture #context #bestpractices #cleancode 👉 @golang_lib

