Общие бизнес-правила для iOS и Android: быстрее исправляйте баги
Общие бизнес-правила для iOS и Android помогают сократить переделки, но неправильная схема замедляет исправления. Сравните копируемые правила, спецификации и общие модули.

Почему команды сталкиваются с этой проблемой
Мобильная команда часто начинает с хороших намерений, а в итоге одно и то же правило оказывается в двух местах. Ограничение на скидку, налоговое правило или проверка при регистрации пишутся один раз для iPhone и один раз для Android, потому что каждое приложение нужно выпускать отдельно.
Сначала это кажется безобидным. Потом правило меняется, а обновление получает только одно приложение.
Проблема обычно начинается с простой бизнес-логики. Корзина может давать бесплатную доставку после определённой суммы, а купон — работать только для новых покупателей. Если приложение iOS проверяет одно условие, а Android — другое, пользователи получают разные результаты, даже когда делают одно и то же.
Люди быстро это замечают. Один клиент видит скидку на iPhone, другой получает ошибку на Android, а поддержке приходится объяснять баг, которого вообще не должно было быть.
Один баг-репорт тоже превращается в два отдельных исправления. Два разработчика смотрят разный код, QA проводит два цикла тестирования, а сроки релиза могут не совпадать в разных магазинах приложений. Маленькая ошибка в цене может висеть днями только потому, что логика живёт в двух кодовых базах.
Именно поэтому команды начинают говорить об общих бизнес-правилах для iOS и Android. Они не гонятся за изяществом. Им нужно меньше расхождений, быстрее исправления и меньше времени на доказательство того, что оба приложения следуют одному и тому же правилу.
Самое раздражающее в этой проблеме то, что в начале она почти не выглядит большой. Каждая команда делает маленький локальный выбор, чаще всего ради скорости. Через несколько месяцев эти решения накапливаются и расходятся: одно приложение округляет вверх, другое — вниз; одно блокирует просроченный код, другое всё ещё его принимает.
Потом начинается спор. Оставить ли копируемые правила, генерировать ли их из общей спецификации или перенести логику в общий модуль? Пока инженеры спорят об архитектуре, поддержка ждёт, QA ждёт, а пользователи продолжают натыкаться на один и тот же баг с двух сторон.
Вот почему эта проблема так часто всплывает. Само правило обычно простое. Сложно — сохранить его одинаковым в двух приложениях.
Три способа делиться правилами
Когда лимит на оформление заказа, правило скидки или проверка права на участие должны работать одинаково на iOS и Android, команды обычно приходят к одному из трёх вариантов. Разница не только в стиле кода. Она меняет то, как быстро кто-то найдёт баг, исправит его и сможет доверять результату.
- Некоторые команды копируют одно и то же правило в каждое приложение и ведут две версии вручную.
- Некоторые пишут правило один раз в виде спецификации, а потом генерируют код для приложений из этого источника.
- Некоторые выносят правило в общий модуль, который используют оба приложения.
Копируемые правила сначала выглядят просто. Каждая мобильная команда может двигаться сама по себе, и не нужны дополнительные шаги сборки. Но проблема проявляется позже. Небольшое изменение политики может попасть в одно приложение во вторник, а в другое — через неделю. Именно в таком разрыве и живёт много надоедливых багов.
Сгенерированные спецификации находятся посередине. Вы описываете правило один раз, обычно в структурированном формате, а код создаётся для обоих приложений. Это уменьшает расхождения, если спецификация понятна. Но когда сгенерированный результат выглядит странно, разработчики могут дольше разбираться, где именно источник бага: в самом правиле, в генераторе или в коде приложения вокруг него.
Общие модули идут ещё дальше в сторону повторного использования. Правило живёт в одном месте, и оба приложения вызывают одну и ту же логику. Это может очень ускорить исправления, потому что менять нужно только одно поведение. Но это же может пойти не так, если команды делятся слишком многим и тащат в модуль ещё и решения по интерфейсу. Тогда простая продуктовая правка превращается в кроссплатформенную переделку.
Этот последний момент важнее, чем кажется многим командам. Интерфейс должен оставаться отдельно от правила. Правило акции можно делить. А вот то, как iOS показывает сообщение об ошибке и как Android размещает предупреждение, должно оставаться локальным для каждого приложения.
Хорошее разделение скучное специально: общие входные данные, общие решения, отдельные экраны. Когда команды держат эту границу, исправления обычно становятся меньше, безопаснее и гораздо проще для проверки.
Копируемые правила и скорость исправления багов
Копирование одного и того же правила в оба приложения сначала может сделать исправление багов быстрым. Если проблема проявляется только на iOS, разработчик iOS может поправить Swift-код, проверить один этот случай и отправить релиз. Не нужно пересобирать общий пакет, не нужен общий кроссплатформенный релизный поезд и не возникает спора, где именно должно жить исправление.
Эта скорость реальна, но она покрывает только первый шаг. Прежде чем закрыть задачу, кто-то всё равно должен проверить второе приложение.
Одна из частых ошибок — исправить видимый баг и предположить, что в другом приложении всё в порядке. Команды обжигаются, когда две копии со временем расходятся. У правила может быть одна и та же цель, но названия, значения по умолчанию или крайние случаи различаются настолько, что второй баг остаётся незаметным.
Хороший пример — правило оформления заказа. Приложение на iPhone может отклонять купон, когда сумма корзины ровно 50 $, а Android — принимать его, потому что в одном приложении используется «>=», а в другом — «>». Первое исправление занимает 20 минут. А чтобы найти несоответствие во втором приложении, может уйти полдня, в основном потому, что код больше не совпадает построчно.
Вот такой компромисс у копируемых бизнес-правил. Локальные исправления просты. Полные исправления требуют дисциплины.
Когда копируемые правила работают хорошо
Копируемые правила лучше подходят небольшим продуктам и небольшим наборам правил, чем многие готовы признать. Они часто нормальны, когда:
- правило меняется несколько раз в год
- одна платформа ловит баги, которые в основном связаны с интерфейсом или особенностями платформы
- команда может протестировать оба приложения перед релизом
- правило достаточно короткое, чтобы сравнить его без боли
Для общих бизнес-правил на iOS и Android копирование логики обычно безопасно только тогда, когда правило остаётся небольшим и простым. Как только в нём начинают расти ветвления для налогов, скидок, прав доступа или региональных особенностей, расхождения перестают быть небольшой рисковой зоной и становятся обычной работой.
Если вы выбираете копируемые правила, сделайте одно правило без вариантов: каждое исправление бага должно проходить шаг «проверь другое приложение». Этот один шаг не даст быстрому локальному патчу превратиться в повторный инцидент на следующей неделе.
Сгенерированные спецификации и скорость исправления багов
Сгенерированные спецификации могут сократить время исправления, если правило достаточно простое, чтобы описать его один раз и использовать везде. Вы меняете один файл спецификации, заново генерируете код, и оба приложения получают одно и то же правило. Это лучше, чем искать по Swift и Kotlin и надеяться, что оба исправления совпадут.
Но тут есть нюанс: спецификация — это только половина дела. Генератор ещё и решает, как поля, перечисления, значения null и ошибки валидации превращаются в реальный код приложения. Если это сопоставление неясно, баг, который выглядел как исправление за 10 минут, может растянуться на долгий день.
Частый пример — валидация форм. Допустим, оба приложения должны отклонять просроченный код членства и показывать одинаковый текст ошибки. Если в спецификации сказано «invalid_code», но iOS превращает это в общее сообщение, а Android — в ошибку на уровне поля, пользователи всё равно получают два разных результата. Правило изменилось в одном месте, но поведение для пользователя — нет.
Когда исправление затрагивает сгенерированные спецификации, команды обычно двигаются быстрее, если проверяют четыре вещи:
- спецификация изменила именно правило, а не просто подпись
- генератор по-прежнему одинаково сопоставляет все поля в обоих приложениях
- коды ошибок превращаются в правильные сообщения для пользователя
- разработчики могут читать сгенерированный код без догадок
Последний пункт важнее, чем принято признавать. Некоторые генераторы создают код, к которому никто не хочет прикасаться. Когда продакшен-баг прилетает в пятницу вечером, незнакомый сгенерированный код сильно тормозит работу. Правило может жить в одном файле, но реальное исправление всё равно зависит от того, понимает ли кто-то, что вышло на другом конце.
Сгенерированные спецификации лучше всего работают там, где у правил есть понятные входы и выходы. Округление цены, проверки права на участие, переходы состояний и правила валидации часто подходят хорошо. Поведение, завязанное на интерфейс, сценарии, зависящие от устройства, и всё, что связано с особенностями платформы, обычно — нет.
Если ваша команда выбирает этот подход, заложите немного дополнительного времени на поддержку генератора и на чтение сгенерированного кода во время срочных исправлений. Когда спецификация чистая, а результат предсказуемый, исправления идут быстрее. Когда генератор ощущается как чёрный ящик, за каждое исправление приходится платить небольшой налог.
Общие модули и скорость исправления багов
Общие модули могут сильно сократить время исправления багов, потому что команда меняет одно правило вместо того, чтобы чинить одну и ту же проблему дважды. Если правило скидки, налоговое правило или проверка права на участие живёт в одном общем модуле, оба приложения получают одно и то же исправление из одного и того же пути кода.
Это особенно важно, когда правила большие, запутанные и постоянно меняются. Небольшой общий слой для простых проверок часто добавляет больше настройки, чем скорости. А вот более крупный движок правил, модель ценообразования или политика оформления заказа обычно быстро окупаются, потому что баги там повторяются.
Но есть и трение. Баг может жить в общем коде, а исправление всё равно должно пройти через сборку и релиз каждого приложения. Команды теряют время, когда у общего модуля медленная компиляция, хрупкий bridge-код или неудобные обновления версий между мобильными приложениями.
Где команды выигрывают в скорости
Самая быстрая схема держит общую часть узкой и простой. Поместите бизнес-правила в модуль, а интерфейс, API устройства и платформенные особенности оставьте вне его. Тогда исправление бага выглядит просто:
- меняем правило один раз
- запускаем общие тесты
- проверяем тонкие обёртки iOS и Android
- выпускаем каждое приложение с уверенностью
Этот последний шаг важнее, чем ожидают команды. Если обёртки вокруг общей логики тонкие, тестировщики могут быстро их проверить. Если же обёртки делают слишком много сопоставления, форматирования или fallback-логики, команда снова начинает отлаживать два приложения, только в менее очевидном месте.
Хорошо это видно на примере оформления заказа. Допустим, бесплатная доставка должна включаться от 50 $, но оба приложения начинают брать плату за доставку уже с 49,99 $ из-за проблемы с округлением. При чистом общем модуле одно исправление корректирует правило, и оба приложения его наследуют. При грязных обёртках iOS всё ещё может округлять в одну сторону, а Android — в другую, и баг выживает, хотя общий код уже правильный.
Что тормозит исправления
Общие модули перестают помогать, когда команды игнорируют трение на релизе. Обратите внимание на несколько типичных проблем:
- bridge-код скрывает платформенные баги
- медленный CI для общего пакета
- у приложений разные версии модуля
- слабые тесты на входные данные и крайние случаи
Используйте общие модули там, где правила меняются часто и стоят денег, когда ломаются. Для стабильной, маленькой логики копируемый код может быть быстрее. Для больших наборов правил, которые меняются каждый спринт, общий модуль обычно выигрывает по скорости исправления багов, если обёртки остаются тонкими, а путь релиза — простым.
Простая история о баге в оформлении заказа
Клиент добавляет в корзину товар за 19,99 $. На iOS налог показывается как 1,64 $, на Android — как 1,65 $, а поддержка получает два скриншота для, казалось бы, одного и того же заказа.
Клиент думает, что цена изменилась. Поддержка считает, что приложение сломалось. У инженерной команды теперь простой вопрос с неприятным ответом: какое приложение право и где именно разделилось правило?
Если команда хранит копируемые бизнес-правила в обоих приложениях, поиск начинается в двух местах. Один разработчик открывает Swift-код, другой — Kotlin-код, и примерно через час оба находят немного разные функции округления. Само исправление небольшое, может быть 20 минут на каждое приложение, но потом команда всё равно тестирует оба потока, пересобирает оба приложения и делает два релиза. Даже для ошибки в один цент общий путь часто выходит примерно таким: полдня на поиск, ещё час на исправление, несколько часов на тестирование, а потом — сколько займут релизы в магазинах.
Один и тот же баг, три пути исправления
При сгенерированных спецификациях команда обычно находит правило быстрее, если в спецификации явно указано округление налога. Если нет, уходит время на проверку спецификации, генератора, сгенерированного кода и тонкого слоя приложения. Чистая схема может занять 45 минут на поиск, 15 минут на исправление в спецификации и ещё час на повторную генерацию, проверку и тестирование обоих приложений. Выпуск всё равно означает два релиза приложений, но путь исправления самого бага короче.
Небольшой общий модуль обычно самый быстрый. Один разработчик находит функцию округления один раз, меняет её один раз и запускает тесты для правила один раз. На поиск бага может уйти 20–30 минут, на исправление кода — 10 минут, а на тестирование — около часа, если приложениям нужны только лёгкие UI-проверки вокруг общего результата. Выпуск всё равно занимает время, потому что Apple и Google по-прежнему требуют отдельные сборки, но трудная часть заканчивается раньше.
Но здесь всё решает масштаб. Общие бизнес-правила для iOS и Android приносят больше всего пользы, когда модуль владеет простыми правилами вроде налогов, скидок и проверок права на участие. Если модуль ещё и управляет состоянием интерфейса, сетевым поведением и платформенными крайними случаями, следующий баг снова станет медленнее. Маленькая ошибка округления быстро показывает это.
Эти несостыковки на скриншотах из поддержки не только доказывают наличие бага. Они показывают, может ли ваша команда пройти по одному понятному пути исправления или один и тот же баг распадается на три разные задачи.
Как выбрать самую маленькую общую поверхность
Большинство команд пытается делиться слишком многим и слишком рано. Так хороший план превращается в медленное исправление в пятницу вечером.
Безопаснее сделать меньше. Выберите одну группу правил, которая часто меняется и больно ломается. Ценообразование — частая отправная точка. Проверки при регистрации — ещё один хороший вариант, потому что оба приложения обычно должны дать один и тот же ответ, но не обязаны иметь одинаковый интерфейс.
Для одного такого фрагмента напишите простой тест-кейс, который люди с iOS и Android смогут прочитать без перевода. Сделайте его скучным и точным: «Если сумма корзины больше 50 $, а у пользователя есть купон на первый заказ, примените купон до налога и ограничьте скидку 10 $». Если product manager не может это прочитать, правило всё ещё слишком спрятано в коде.
Этот общий пример важнее, чем сам способ совместного использования. Он даёт обеим командам один источник истины, когда появляется баг.
Начните с самого лёгкого варианта
Если вы выбираете между копируемыми правилами, сгенерированными спецификациями и общими модулями, не начинайте с самого тяжёлого инструмента. Попробуйте самый лёгкий вариант, который способен сохранять правило понятным с обеих сторон.
Во многих случаях это означает одно из следующего:
- копируемые правила с одинаковыми простыми тест-кейсами в обоих репозиториях
- небольшая сгенерированная спецификация для входов и ожидаемых результатов
- маленький общий модуль только для чистой логики без платформенного кода
Обратите внимание на схему: одна группа правил, один узкий эксперимент. Не оформление заказа, ценообразование, состояние аккаунта и промо-логика одновременно.
Обычно это лучший способ подойти к общим бизнес-правилам для iOS и Android. Сначала вы понимаете, где на самом деле больно, и только потом строите более крупный общий слой, который обе команды потом должны тянуть.
Измеряйте исправления, а не изящность
Проведите эксперимент в течение месяца. Отслеживайте простые цифры, а не мнения.
- Сколько времени занял каждый баг на обеих платформах?
- Как часто одно приложение выпускало исправление раньше другого?
- Сколько раз команды не соглашались по поводу правила?
- Сэкономила ли общая часть время или добавила нагрузку на ревью и релизы?
Если цифры улучшаются, расширяйте решение по одному шагу. Добавьте ещё одно правило ценообразования. Потом ещё одну маленькую группу. Если цифры не меняются, остановитесь. Меньшую общую поверхность проще доверять, проще тестировать и проще убрать, если окажется, что это была неудачная идея.
Ошибки, которые замедляют исправления
Команды обычно теряют скорость исправления багов, когда делятся слишком многим, плохо называют вещи или строят схему, которую может менять только один человек. В случае общих бизнес-правил для iOS и Android цель не в максимальном повторном использовании. Цель в том, чтобы следующий баг было дёшево найти и дёшево исправить.
Одна частая ошибка — делиться всей функцией вместо одного правила. Правило скидки, налоговое правило или лимит оформления заказа могут хорошо жить в одном общем слое. Полный поток экрана — обычно нет. Когда команды заставляют сеть, состояния интерфейса, обработку крайних случаев и поведение платформы жить в одном общем блоке, каждое небольшое исправление начинает затрагивать слишком много кода. Тогда простая ошибка в цене превращается в рискованный релиз на обоих приложениях.
Туманные названия только усугубляют ситуацию. Если в баг-репорте написано, что итог неверный, никто не хочет искать в файлах с названиями вроде «helper», «common» или «util». Такие названия скрывают смысл. Файл под названием «правила права на купон» сразу показывает, где искать. А файл «checkout helpers» тратит время ещё до начала реальной работы.
Общие тесты важнее общего кода. Если обе команды могут запускать один и тот же небольшой набор тест-кейсов, они могут проверить правило до релиза. Без этого iOS может исправить баг одним способом, а Android — другим. Код может выглядеть аккуратно и всё равно начать расходиться.
Несколько ранних признаков проблемы:
- Одним человеком владеет генератор правил, и больше никто не хочет к нему прикасаться
- Исправление бага требует встречи, прежде чем кто-то откроет код
- iOS и Android описывают одно и то же правило разными словами
- Маленькое изменение правила требует полного повторного тестирования функции
Генераторы могут помочь, но только если команда может читать входные данные, доверять результату и разбирать ошибки, не вызывая автора генератора. Если генератор ощущается как личный фокус, он будет замедлять каждое срочное исправление.
Важен и темп релизов. iOS и Android почти никогда не двигаются идеально синхронно. Одно приложение может выйти сегодня, а другое — ждать модерации или сидеть в более длинной релизной очереди. Общий код должен поддерживать эту реальность, а не бороться с ней. Если одной платформе нужен быстрый патч, модель правила должна позволять это, не ломая график другого приложения.
Самые быстрые команды делятся небольшой поверхностью правил, дают ей понятные названия и держат тесты на виду.
Быстрые проверки перед коммитом
Перед тем как влить любое изменение правила, проверьте, помогает ли ваша схема по-прежнему быстро исправлять баги. Красивой идеи на бумаге мало, если срочный патч превращается в полдня поисков.
Используйте короткую проверку, на которую обе мобильные команды смогут ответить без спора.
- Попросите одного разработчика iOS и одного разработчика Android сказать правило одним простым предложением. Если формулировки расходятся, правило уже сложнее поддерживать, чем кажется.
- Запустите один и тот же небольшой набор тест-кейсов на обоих приложениях. Используйте реальные крайние случаи, а не только счастливые пути. Если одному приложению нужна своя тестовая логика для того же правила, это тревожный знак.
- Возьмите недавний баг и засеките время, за которое один разработчик проходит путь от симптома до правила. Если источник не находится примерно за 15 минут, схема совместного использования скрывает слишком много.
- Попробуйте фальшивое экстренное исправление. Измените только сломавшееся правило, а потом проверьте, приходится ли трогать скрипты сборки, посторонние модули или специфичный код приложения. Если да, зона поражения слишком широка.
Простой пример с оформлением заказа это хорошо показывает. Допустим, оба приложения отклоняют заказ, когда сумма корзины после купона становится ниже минимального порога. Если обе команды одинаково объясняют это правило, запускают одни и те же примеры и исправляют его в одном месте без побочных эффектов, подход работает. Если одно приложение читает лимит из копируемого кода, другое — из сгенерированного файла, и никто не знает, какой источник главный, скорость исправления резко упадёт.
Для общих бизнес-правил на iOS и Android ясность обычно важнее хитрости. Если две или больше проверок не проходят, сократите общую поверхность, прежде чем добавлять ещё код. Небольшое, скучное совместное использование правил часто проще отлаживать, чем большая система, которая пытается объединить всё.
Что делать дальше
Начните с одного правила, которое часто ломается и стоит реального времени. Ограничение скидки, правило округления налога или проверка права на оформление заказа лучше, чем расплывчатая группа вроде «логика ценообразования». Постройте полный путь исправления для этого одного правила: где появляется баг, где правило живёт сейчас, кто его меняет, кто тестирует и сколько времени нужно, чтобы добраться до обоих магазинов приложений.
Такая простая карта быстро снимает спор. Командам, которые обсуждают общие бизнес-правила для iOS и Android, обычно не нужен большой переписанный проект в первую очередь. Им нужно увидеть, какой вариант сокращает путь от баг-репорта до подтверждённого релиза для тех правил, которые ломаются чаще всего.
Используйте один и тот же чек-лист для каждого подхода:
- Для копируемых правил посчитайте, сколько файлов меняется ради одного исправления и как часто iOS и Android расходятся.
- Для сгенерированных спецификаций проверьте, легко ли читать спецификацию, ревьюить её и заново генерировать под давлением.
- Для общих модулей задайте прямой вопрос: может ли ваша мобильная команда отлаживать общий код, не ожидая помощи от кого-то ещё?
Потом запишите ответственность простыми словами. Укажите, кто утверждает изменения правил. Укажите, кто владеет тестами. Укажите, кто выпускает исправление, если Android и iOS выходят в разные дни. Если эти роли размыты, структура кода вас не спасёт.
Честно оцените ограничения команды. Небольшая команда, хорошо знающая Swift и Kotlin, может исправлять копируемые бизнес-правила быстрее, чем общий модуль, к которому все боятся прикасаться. Команда с более сильными инструментами и одним инженером, которому нравится работа со схемами, может быстрее двигаться с сгенерированными спецификациями. Общие модули окупаются только тогда, когда люди на дежурстве могут быстро их прочитать и исправить.
Если выбор всё ещё буксует, возьмите второе мнение у человека, который видел эту проблему в компаниях разного масштаба. Oleg Sotnikov работает как Fractional CTO и помогает командам выбирать лёгкие решения для продуктовой логики, инфраструктуры и разработки с поддержкой ИИ. Короткого разбора одного недавнего бага, одного правила и одного пути релиза обычно достаточно, чтобы следующая развилка стала очевидной.