14 июл. 2025 г.·6 мин чтения

Общие сетевые слои в мобильных приложениях без слепых зон

Общие сетевые слои в мобильных приложениях могут сократить повторяющийся код, но слишком много обёрток усложняет поиск ошибок. Узнайте, где остановиться и что логировать.

Общие сетевые слои в мобильных приложениях без слепых зон

Почему простой API-код со временем становится запутанным

В начале API-вызов кажется очень простым. Экран отправляет запрос, получает JSON и обновляет интерфейс. Проблемы начинаются позже, когда этот путь оборачивают в дополнительные слои, добавляют повторы, преобразования и переиспользуют по всему приложению.

Частая проблема — два экрана вызывают один и тот же endpoint чуть по-разному. Один добавляет лишний заголовок. Другой убирает пустое поле перед отправкой. Третий использует кешированный токен, а четвёртый сначала обновляет токен. Код приложения может выглядеть почти одинаково, но реальный HTTP-трафик уже другой. Как только эта разница исчезает внутри общего клиента, отладка превращается в гадание.

Обработка ошибок часто делает всё ещё хуже. Обёртка ловит реальный ответ сервера, например 401, 422 или 500, и превращает его в «Что-то пошло не так». В интерфейсе это выглядит аккуратно, но полезные детали теряются. Неверные учётные данные, истёкшая сессия и сбой backend теперь выглядят как одна и та же ошибка.

Тихие повторы тоже размывают картину. Если первый запрос не прошёл из-за таймаута или плохого соединения, а клиент повторил его молча, финальная ошибка может относиться уже ко второй попытке. Первый запрос был самым полезным, но он так и не попал в отчёт о баге. Логи backend показывают дублирующиеся вызовы, а команда приложения видит одну расплывчатую ошибку.

Худший вариант — баг-репорт без метода, URL, кода ответа и тела ответа. «На одном устройстве не удалось войти» почти ничего не говорит. Это был POST-запрос? На какой хост он ушёл? Сервер вернул 403 или 503? Было ли в теле ответа полезное сообщение об ошибке, которое приложение потеряло?

Общий сетевой код всё равно полезен. Он убирает копипасту и держит правила авторизации в одном месте. Но каждая лишняя обёртка может скрыть ещё один факт. Если разработчик не может понять, что ушло, что пришло и был ли повтор, слой уже слишком непрозрачный.

Что должен делать общий слой

Общий сетевой слой должен отвечать за то, что есть у каждого запроса. Так экраны и view model становятся проще, а одна и та же логика авторизации и разбора данных не расползается по проекту.

В первую очередь там должны жить заголовки. Если каждому запросу нужен токен авторизации, версия приложения, локаль или информация об устройстве, добавляйте это в одном месте. Когда формат меняется, вы обновляете один файл вместо того, чтобы искать десять экранов.

Туда же относится разбор ответов. Если у вашего API есть стандартный формат успеха и стандартный формат ошибки, обрабатывайте их одинаково каждый раз. Особенно важно это при сбое. Один экран не должен сохранять тело ошибки, а другой — выбрасывать его.

Обновление токена тоже хорошо подходит для общего слоя. Когда токен истекает, клиент должен обновить его и повторить исходный запрос, не заставляя каждый экран знать об этом процессе. Экран просто запросил данные профиля или итог корзины. Ему не нужно думать о refresh token, порядке повторов или гонках.

Таймауты, повторы и правила backoff тоже должны жить в одном месте и быть легко находимыми. Если запрос ждёт 60 секунд в мобильной сети, но только 15 секунд по Wi‑Fi, это правило должно быть рядом с настройкой клиента, а не внутри собственной обёртки в трёх слоях глубины.

Сам запрос при этом должен оставаться видимым там, где он определён. Разработчик должен быстро находить «POST /login» и JSON-тело. Если метод, путь, query и payload исчезают внутри абстракции, слой делает слишком много.

Здесь хорошо работает простое правило:

  • Помещайте общие механизмы в общий слой.
  • Оставляйте бизнес-смысл рядом с кодом запроса.
  • Сделайте так, чтобы логи показывали реальный HTTP-запрос и ответ.
  • Сохраняйте коды статуса, тела и полезные заголовки в ошибках.

Такой баланс делает общий клиент полезным, а не запутывающим. Вам нужен один слой для повторяющегося поведения, но при отладке вы всё равно должны быстро узнавать настоящий HTTP-вызов.

Когда обёртки начинают мешать

Общий клиент помогает, пока не начинает скрывать обычный HTTP. Обычно это происходит, когда один запрос проходит через несколько helper-функций прежде, чем дойдёт до сервера. Простой вызов входа превращается в поиск по разным файлам, и команда перестаёт видеть, что именно сломалось.

Это легко заметить. Разработчик спрашивает: «Почему этот запрос не прошёл?», а код отвечает чем-то расплывчатым вроде AppError.invalidState. Звучит аккуратно, но полезная часть теряется: это был 401, 403, 422 или таймаут?

Слишком большое количество обёрток ещё и переименовывает обычные веб-термины в внутренний жаргон приложения. «Resource not found» превращается в «empty content state». «Unauthorized» становится «session mismatch». Через некоторое время новым разработчикам нужна таблица перевода, чтобы просто читать лог.

Старайтесь держать модели ответа близко к тому, что реально присылает сервер. Если API возвращает status, message и errors, начинайте с этого. Преобразовывайте сырой формат в модель экрана позже, рядом с той функцией, которой он нужен. Команды, которые слишком рано преобразуют данные сервера, часто выбрасывают именно те поля, которые сильнее всего нужны при отладке.

Типичная цепочка сбоя выглядит так: транспортный слой получает ответ 422 с полезным телом, парсер превращает его в ValidationError, а ещё одна обёртка — в LoginFailed. К тому моменту, когда ошибка доходит до экрана, никто уже не понимает, был ли введён неправильный пароль, отсутствовало поле или сервер отклонил формат.

Если для поиска пути и метода запроса нужно открывать несколько файлов, если в логах написано только «request failed», если обёртки возвращают null или false вместо нормальных деталей ошибки, или если приложение придумывает новые названия для «timeout» и «unauthorized», слой уже слишком толстый.

Общий слой должен сокращать повторяющийся код, а не заменять HTTP на загадку. Если запрос вернул 500, разработчики должны видеть именно 500.

Оставляйте HTTP видимым в коде и логах

Общий клиент может убрать повторения, но он никогда не должен превращать обычный HTTP-вызов в загадку. Когда запрос не проходит, разработчикам нужно видеть, что отправило приложение, что вернул сервер и где именно оборвался путь.

Логируйте один и тот же базовый набор данных для каждого запроса. Формат должен быть настолько простым, чтобы его можно было прочитать за несколько секунд: метод, путь, query string, если он есть, код статуса, длительность, количество повторов, request ID и финальное сообщение об ошибке.

Этот небольшой набор закрывает удивительно много проблем с отладкой. Последовательность важнее, чем красивое оформление.

Секретные данные требуют другого правила. Оставляйте достаточно деталей для отладки, но никогда не выводите секреты. Маскируйте access token, пароли, session cookie и персональные данные, такие как email, номера телефонов или полные имена, прежде чем что-то попадёт в логи устройства или отчёты о сбоях.

Request ID помогает больше, чем ожидает большинство команд. Сгенерируйте его на клиенте или передавайте серверный trace ID, если backend уже создаёт его. Затем используйте этот же ID в клиентских логах, логах повторов и серверных логах. Когда одно устройство сообщает о странном 401 или 500, вы сможете отследить конкретный запрос по всей цепочке вместо того, чтобы гадать.

Ошибки парсинга требуют дополнительного контекста. Если приложение ожидает JSON, а получает HTML-страницу ошибки, одно сообщение парсера почти ничего не говорит. Логируйте короткий фрагмент сырого ответа, ограниченный безопасным размером, чтобы команда могла увидеть, вернул ли сервер битый JSON, страницу ошибки прокси или экран входа.

Сохраняйте видимой и саму форму HTTP-запроса в коде. Builder запроса должен явно показывать, какой метод, маршрут, заголовки, query-параметры и тело вы отправляете. Если обёртка превращает GET /profile?include=team в набор generic-helper-вызовов, самый быстрый путь к багу исчезает.

Хорошие логи не обязаны быть красивыми. Они должны быстро отвечать на четыре вопроса: что отправило приложение, что пришло обратно, сколько было повторов и что в итоге сломалось.

Собирайте слой по шагам

Упростите сетевой слой
Уберите лишние обёртки и оставьте HTTP-детали видимыми в коде и логах.

Самый безопасный вариант общего клиента обычно самый скучный. Начните с тонкой обёртки вокруг вашей HTTP-библиотеки. Оставьте метод запроса, URL, заголовки, query-параметры и тело легко читаемыми в коде.

Первый слой должен делать совсем немного. Он должен отправлять запросы, разбирать ответы и записывать логи в одном формате. Если спрятать слишком многое слишком рано, каждый баг превратится в поиск по обёрткам, helper-функциям и собственным объектам запросов.

Лучше работает спокойная последовательность, а не большой переписывающий проект:

  1. Добавьте один тонкий клиент поверх HTTP-библиотеки.
  2. Перенесите в него общие правила авторизации.
  3. Добавьте правила таймаутов и повторов, которые применяются ко всем запросам, если экран не исключён явно.
  4. Создайте один тип ошибки, который всегда содержит код статуса, тело ответа и исходные детали запроса.
  5. Перенесите один endpoint и проверьте его на реальном устройстве.

Этот тип ошибки действительно важен. Если вход не удался, вам нужен один объект, который покажет, вернул ли сервер 401, было ли в теле полезное сообщение и отправил ли клиент неправильный заголовок. Разносить эту информацию по нескольким собственным исключениям — пустая трата времени.

Не переносите всё приложение сразу. Возьмите один endpoint с понятным поведением, например получение профиля или вход, и пропустите его через новый слой. Если этот путь работает в обычном использовании, при плохой сети и при истёкшем токене, тогда переносите следующий endpoint.

Юнит-тесты помогают, но не показывают полной картины. Реальные устройства проявляют неприятные детали: медленные мобильные сети, дрейф часов, кешированный DNS, нестабильные повторы и отсутствие логов, когда приложение уходит в фон. Лог запроса, который в тестах выглядит нормально, на телефоне может стать бесполезным, если он обрезает тело, сокращает заголовки или скрывает повторы.

Делайте каждую строку лога скучной и полной. Показывайте метод, путь, статус, длительность, количество повторов и безопасную версию заголовков и тела. Когда баг появляется только на одном устройстве, такая простая запись часто экономит час и больше.

Реальный пример: вход не работает на одном устройстве

Пользователь входит дома по Wi‑Fi и сразу попадает в приложение. Через два часа тот же телефон снова пробует через мобильную сеть, а приложение показывает только «Что-то пошло не так». Поддержка сообщает о баге входа, и команда начинает читать код экрана логина.

Это уводит в неправильную сторону.

Экран отправляет обычный POST-запрос. Проблема находится уровнем ниже. В мобильной сети запрос идёт по другому сетевому пути и получает редирект 307. Приложение следует за ним, но второй запрос уже не содержит заголовок X-Api-Key, который ожидает backend. Сервер отклоняет запрос, а общий слой превращает всю цепочку в одну общую ошибку.

POST https://api.example.com/login -> 307
Location: https://edge.example.com/login

POST https://edge.example.com/login -> 401
X-Api-Key: missing

Если в логе клиента написано только «login failed», никто не увидит настоящую причину. Хорошая отладка сохраняет HTTP-детали видимыми даже тогда, когда вы используете общий клиент. Вам нужен метод, полный URL, код статуса, цель редиректа, request ID и безопасный вид заголовков с замаскированными секретами. Ещё важно, чтобы лог показывал каждый переход, а не только последний ответ.

Чаще всего виноваты лишние обёртки. Аккуратная функция login() выглядит чисто, но если она скрывает редиректы, переписывает коды статуса и прячет заголовки, команда теряет часы. Один разработчик проверяет состояние формы. Другой тестирует правила пароля. Кто-то ещё обвиняет оператора связи. А баг сидит в правиле редиректа.

Исправление небольшое. Либо оставьте endpoint входа на одном хосте, либо убедитесь, что перенаправленный запрос сохраняет нужный серверу заголовок. Интерфейс не требовал переписывания. Логам просто нужно было показать настоящий путь запроса.

Ошибки, которые замедляют отладку

Привлеките fractional CTO
Получите внешнюю помощь CTO по архитектуре клиента, инфраструктуре и рабочим процессам разработки с AI-first подходом.

Общий сетевой слой может убрать повторяющийся код, но часто он создаёт слепые зоны, когда команды пытаются сделать каждую ошибку аккуратной. Реальный HTTP — это не идеально чистая схема, и отладка сильно замедляется, когда код скрывает эту сложность.

Одна из распространённых ошибок — выбрасывать сообщение сервера и оставлять только общий текст вроде «request failed». Это звучит аккуратно, но убирает ту самую деталь, которая могла бы объяснить баг. Если backend пишет «refresh token expired» или «email not verified», это сообщение должно оставаться доступным в логах и для разработчиков, даже если интерфейс показывает более простой текст.

Ещё одно замедление появляется, когда команды логируют только распарсенные модели. Если парсинг ломается, самая полезная улика уже потеряна. Сначала логируйте сырой код статуса, нужные заголовки и тело ответа. Потом уже разбирайте его. Когда устройство присылает неверную кодировку, HTML-страницу ошибки или сообщение backend, не совпадающее со схемой, сырые логи экономят много времени.

Повторы создают другую проблему. Слепой повтор POST-запроса может привести к дублированию действия. Это может означать два обращения в поддержку, два заказа или две сессии входа. GET-запросы обычно безопаснее повторять. Для POST-запросов нужны чёткие правила, а иногда и idempotency token, прежде чем клиент попробует ещё раз.

Команды также теряют время, когда сваливают все ошибки в один bucket под названием «network error». Нет интернета на устройстве, 401 из-за истёкшей авторизации, 422 из-за неверных данных и 500 на сервере — это разные проблемы. Им нужны разные исправления. Если приложение считает их одинаковыми, разработчики начинают гадать вместо того, чтобы диагностировать.

Собственные названия тоже могут мешать. Метод с именем sendUserAction говорит намного меньше, чем строка лога вроде «POST /login -> 401». Бизнес-имена уместны в коде приложения, но в логах должны оставаться видимыми HTTP-метод, маршрут, статус и request ID.

Если вход не работает на одном телефоне, а на другом работает, вам не нужны красивые формулировки в логах. Вам нужен сырой запрос, сырой ответ и точное место, где приложение превратило одно в другое.

Быстрые проверки перед релизом

Настройте более умные правила повторов
Настройте правила повторных попыток так, чтобы уменьшить дублирующиеся действия и упростить объяснение сбоев.

Перед релизом проверьте сетевой слой так, как его будут использовать поддержка и разработчики под давлением. Возьмите одно типичное действие, например вход, и проследите его от нажатия пользователя до финального ответа сервера. Если одно нажатие запускает повторы или запрос на обновление токена, логи должны это ясно показать.

Каждый лог запроса должен содержать метод, URL или path, код статуса, длительность и request ID. Без этих полей два похожих вызова выглядят одинаково, и люди начинают искать не ту проблему.

Пишите логи простым языком. Сотрудник поддержки должен понимать строку вроде «POST /login returned 401 in 420 ms», не зная внутренних названий классов. Ещё полезно, если один неудачный вызов легко воспроизвести вне приложения. Разработчик должен суметь вставить детали запроса в curl или другой клиент и увидеть тот же результат. Этот шаг часто выявляет неверные заголовки, неправильные окружения или проблемы с авторизацией, связанные только с одним устройством.

Добавьте понятный переключатель для подробного логирования. Держите его включённым в debug-сборках. Перед релизом уменьшите уровень логов или выключите их, чтобы не утекали токены, не забивалось локальное хранилище и не замедлялось приложение.

Скрывайте секреты до того, как что-то попадёт на диск. Не оставляйте токены, пароли и персональные данные в логах, даже в тестовых сборках.

Ещё одна простая и полезная проверка: попросите человека, который не участвовал в создании сетевого слоя, отследить один неудачный запрос. Если он не может понять, что произошло и где именно всё сломалось, слой всё ещё слишком трудно проверять.

Приведите стек клиента в порядок

Самое быстрое улучшение часто даёт удаление кода, а не добавление ещё одного слоя. Начните с одного типичного пользовательского сценария, например входа, поиска или оформления заказа, и разберите все запросы, которые он отправляет. Включите метод, путь, нужные вам заголовки, поведение повторов и момент, когда приложение превращает ответ в состояние интерфейса.

Такая карта обычно быстро показывает две вещи. Во-первых, где общий слой экономит время. Во-вторых, где лишние обёртки просто снова и снова переименовывают одни и те же HTTP-детали. Если обёртка не добавляет реального поведения, уберите её. Тонкий клиент, который отправляет запросы, разбирает ответы и сообщает о сбоях, доверия заслуживает больше, чем стек helper-функций, скрывающих то, что ушло по сети.

Убирать нужно небольшими и заметными шагами. Выберите один сценарий, который часто ломается или особенно важен пользователям. Проследите каждый запрос и ответ в этом сценарии от нажатия на экран до ответа сервера. Уберите одну обёртку, которая просто прокидывает данные дальше. Напишите один короткий формат логов и используйте его и в iOS, и в Android. Каждая строка лога должна отвечать на одни и те же вопросы: что отправили, куда отправили, что пришло обратно и почему приложение сочло это успехом или ошибкой.

Формат лога не должен быть сложным. Timestamp, request ID, метод, путь, код статуса, длительность, количество повторов и короткая причина ошибки закрывают большинство случаев. Если запрос не проходит на одном устройстве, вы должны суметь сравнить два лога рядом и заметить разницу меньше чем за минуту.

Задайте одно правило и придерживайтесь его: общий слой может стандартизировать способ отправки запросов, но он не должен скрывать реальность HTTP.

Если вашей команде нужен второй взгляд, Oleg Sotnikov на oleg.is советует стартапам и небольшим компаниям по вопросам архитектуры клиента, инфраструктуры и рабочих процессов разработки с AI-first подходом. Короткий внешний аудит часто помогает заметить одну лишнюю обёртку и одну пропущенную строку лога, которые продолжают съедать время.