Серая кнопка в заказе: как UI-баг привёл к графу зависимостей приложения
UI-баг, который возвращается после исправления, почти всегда симптом: экраны опираются на общие сервисы, а те ссылаются друг на друга по кругу. Лечится в четыре шага: построить граф зависимостей, найти цикл, разорвать его через интерфейс или событие, закрепить результат тестом и проверкой в сборке.
Почему возникает UI-баг, который не уходит после исправления 🔍
Понедельник, начало десятого. Торговый представитель оптовой компании стоит у кассы магазина в Люберцах и оформляет заказ в мобильном приложении. Он меняет скидку на позицию, но итог внизу остаётся прежним, а кнопка «Отправить» горит серым. Представитель закрывает экран и открывает снова: кнопка активна, итог другой. Какой из двух верный, он не знает. Звонит в поддержку и встаёт в очередь: в понедельник утром приложение открыто у всех представителей сразу.
На вид это мелочь на одном экране. Я исправил её за вечер: подписал итог на изменение скидки. Но через два дня серая кнопка появилась на экране возврата товара. Поэтому смотреть я стал уже не на экран, а на схему: кто кого вызывает в коде.
Схема показала цикл. Модель экрана заказа обращалась к сервису цен, сервис цен читал данные из репозитория клиентов, репозиторий запускал менеджер синхронизации с 1С, а менеджер по окончании обмена обновлял заказ, то есть ту же модель, с которой всё началось. Четыре объекта держали по собственной копии заказа и перезаписывали друг друга. Побеждала копия, пришедшая последней, но последней она приходила не всегда: порядок зависел от скорости связи в магазине.
Почему цикл не поймали раньше
Менеджер синхронизации передавали в репозиторий не через конструктор, а полем после создания объекта. Компилятор на такое не реагирует, и цикл жил тихо, пока один экран не показал два состояния одного заказа. В Android-проектах Dagger и Hilt остановят сборку с ошибкой о цикле, если зависимости идут через конструкторы. Ручное присвоение поля эту проверку обходит.
Чем рискует бизнес, если лечить только экран
Заказ, который нельзя отправить на месте, представитель записывает на бумаге или в заметках и вводит вечером. Отсюда дубли в 1С, расхождения по ценам и споры с клиентом: ему назвали одну сумму, а в документе другая. Если представители ездят по торговым точкам Москвы и области, серая кнопка останавливает основной канал продаж. Часть заказов уходит конкуренту, пока менеджер ждёт ответа поддержки.
Есть и скрытая статья расходов. Каждая правка на уровне экрана рождает следующий баг на соседнем. Релизы замедляются, тестировщики перепроверяют всё приложение целиком, а подрядчик выставляет часы за то, что чинил на прошлой неделе.
Как найти причину: граф зависимостей мобильного приложения 🧭
Граф отвечает на один вопрос: кто от кого зависит. Собирать его нужно на двух уровнях.
Модули и пакеты
Связи между модулями показывают задача dependencies в Gradle и команда swift package show-dependencies в Swift Package Manager. Для JS и TypeScript в React Native круговые ссылки между файлами сразу выводит утилита madge с ключом --circular.
Классы внутри модуля
Циклы внутри одного модуля эти инструменты не покажут. Здесь нужен небольшой скрипт: он читает импорты или конструкторы и выводит связи в формате Graphviz. На выходе картинка, где цикл видно глазами.
На картинке ищите три вещи:
- ✓замкнутые цепочки стрелок, как в истории с заказом;
- ✓узлы, в которые входит больше всего стрелок: через них проходит половина приложения, и любая правка там отзывается везде;
- ✓стрелки снизу вверх, например репозиторий, который знает про экран.
На нашем графе узел «заказ» стоял в центре: стрелки шли к нему от экранов, сервиса цен и синхронизации, а цепочка из четырёх объектов замыкалась на него же. Такая картина говорит, что у данных нет владельца.
Как исправить цикл зависимостей без переписывания приложения 🛠
Переписывать всё не нужно. Порядок такой: сначала тест, который воспроизводит баг, затем по одной разрываем стрелки и после каждой запускаем тест.
В нашем случае тест был простым: создать заказ, поменять скидку, дождаться ответа синхронизации и сравнить итог с состоянием кнопки. Он падал стабильно, поэтому каждую правку можно было проверять, а не надеяться.
Способов разорвать цикл четыре. Они отличаются трудоёмкостью и риском.
| Способ | Когда подходит | Трудоёмкость | Риск |
|---|---|---|---|
| Единый источник состояния | Несколько копий одних данных, как у нас с заказом | Средняя | Все экраны придётся перевести на одно хранилище |
| Интерфейс вместо прямого вызова | Нижний слой должен сообщить верхнему, но знать о нём не обязан | Низкая | Небольшой: меняется только граница между слоями |
| События или потоки данных | Синхронизация обновляет данные, а экраны только подписаны на них | Средняя | Сложнее отлаживать порядок событий |
| Общий код в отдельный модуль или слияние объектов | Два объекта связаны так плотно, что по сути это одна сущность | Высокая | Много правок, нужна ручная проверка сценариев |
Здесь подошли первый и третий способы. Состояние заказа переехало в одно хранилище, а сервис цен стал считать итог из него, а не из репозитория клиентов. Менеджер синхронизации перестал трогать заказ напрямую: он публикует событие «обмен завершён», а хранилище само решает, что с ним делать. Стрелка от менеджера к модели заказа исчезла, цикл разомкнулся.
После правки экраны заказа и возврата показывают один итог при любой скорости связи. Новый способ обмена с 1С подключается без правок экранов: он публикует те же события.
Что делать, если ошибка повторяется 🔁
Если серая кнопка вернулась, чаще всего вылечили не ту стрелку. Проверьте по порядку.
- ✓Остались ли две копии одних и тех же данных. Поиск по коду по названию сущности показывает это быстро.
- ✓Приходят ли обновления из фонового потока и сразу меняют интерфейс. Интерфейс должен обновляться из главного потока.
- ✓Одинаков ли порядок создания объектов при холодном и тёплом запуске. Ленивые одиночки (singleton) ведут себя по-разному.
Баг проявляется только при плохой связи
Представители работают в подвалах складов и на окраинах, где связь рвётся. Обмен с 1С там идёт долго или обрывается на середине, и гонка между копиями данных срабатывает именно в этот момент. Эмуляторы и инструменты разработчика позволяют ограничить скорость сети и оборвать соединение, поэтому добавьте такие сценарии в тесты. Если приложение ждёт ответа 1С, прежде чем обновить экран, пересматривать нужно сам протокол обмена: интеграция приложения с 1С должна работать офлайн и безопасно повторять отправку.
Баг появился после обновления конфигурации 1С
Если цикл разорван, а ошибка вернулась после обновления типовой конфигурации, часто виноват формат обмена: приложение получает поля в другом виде. Здесь нужен тест на формат ответа, а не очередная правка экрана.
Как предотвратить возвращение бага: правила для кода и приёмки
Правила простые, но записать их нужно до начала разработки, а не после первого инцидента.
- ✓Зависимости идут в одну сторону: экран знает про бизнес-логику, логика про данные, данные не знают ни про экран, ни про логику.
- ✓Зависимость передаётся через конструктор. Тогда цикл не соберётся, и компилятор скажет об этом сразу.
- ✓У каждого куска данных один владелец. Остальные читают его и подписываются на изменения.
- ✓Проверка циклов встроена в сборку: madge, анализатор модулей или свой скрипт. Новый цикл ломает сборку и не доходит до тестировщика.
- ✓На каждый UI-баг пишется тест, который его воспроизводит.
Малому и среднему бизнесу это особенно важно: в штате обычно один-два разработчика или внешний подрядчик, и уход человека не должен превращать код в загадку.
Заказчику стоит добавить в приёмку два пункта: схему зависимостей и отчёт проверки циклов. Если их нет, при следующей доработке вы заплатите за разбор чужого кода заново. Заодно это быстрый способ сравнить предложения команд, пока вы выбираете, кто сделает B2B-приложение с 1С для торговых представителей и клиентов.
Как мы разбираем такие баги в ukved.ru и сколько это стоит 💼
Для московской оптовой или сервисной компании с выездными сотрудниками запрос обычно звучит так: «приложение глючит, а каждый фикс ломает соседний экран». Когда к нам приходит приложение, написанное другой командой, мы сначала строим граф и показываем его заказчику: где циклы, какие узлы перегружены, какие баги с ними связаны. Потом вместе решаем, что лечить сразу, а что отложить. Стоимость доработки считаем после просмотра кода: без графа цену честно не назвать, потому что один цикл разрывается небольшой правкой, а клубок связей требует поэтапного плана.
В новых проектах схема слоёв и проверка циклов появляются с первой сборки, а вместе с исходным кодом заказчик получает схему зависимостей. Разработка мобильного приложения стоит от 500 000 руб., итог зависит от числа экранов, платформ и объёма обмена с 1С. Остальные расценки собраны на странице цены и тарифы.
❓ Частые вопросы
Почему UI-баг в мобильном приложении возвращается после исправления?
Чаще всего правили симптом на экране, а причина осталась глубже: две копии одних данных или цикл зависимостей между сервисами. Тогда следующий экран, использующий те же сервисы, получает тот же баг. Постройте граф зависимостей, найдите цикл или дубль состояния и исправьте его, а не отдельный экран.
Как найти циклические зависимости в мобильном приложении?
Связи между модулями показывают задача dependencies в Gradle и команда swift package show-dependencies в Swift Package Manager. Для JS и TypeScript в React Native круговые ссылки находит madge с ключом --circular. Циклы между классами внутри модуля находятся скриптом, который читает импорты или конструкторы и строит граф в Graphviz.
Сколько стоит разработка мобильного B2B-приложения с интеграцией 1С?
Разработка мобильного приложения в ukved.ru стоит от 500 000 руб. Итоговая цена зависит от числа экранов, платформ и объёма обмена с 1С. Стоимость доработки готового приложения считаем после просмотра кода и графа зависимостей, потому что без него объём работ не оценить честно.
Можно ли исправить приложение, которое написала другая команда?
Можно. Сначала строится граф зависимостей и находятся циклы, дубли состояния и перегруженные узлы. Затем критичные места правят по одному и после каждой правки запускают тест на воспроизведение бага. Полное переписывание нужно редко: чаще хватает разорвать несколько связей и добавить проверку циклов в сборку.
Как понять, что приложению нужна проверка зависимостей, а не очередной фикс?
Признаки: один и тот же баг всплывает на разных экранах, правка в одном месте ломает соседнее, сборка и тесты замедляются, а новый разработчик долго разбирается в коде. Если знакомы хотя бы два пункта, начинайте с графа зависимостей, а не с очередной правки экрана.
Или позвоните: +7 906 045-28-27 — в рабочее время с 9:00 до 19:00
Остались вопросы? Нужна помощь?
Менеджеры компании с радостью ответят на ваши вопросы, произведут расчет стоимости услуг и подготовят индивидуальное коммерческое предложение.
Бесплатная консультация

