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

Алексей Думбадзе

В Android-приложении Dodo Pizza я начал переводить такие договорённости в автоматические проверки во время редизайна, а затем добавил проверки для большинства архитектурных принципов проекта. Новым разработчикам, в том числе тем, кто проходит испытательный срок в паре со мной, это заметно упростило погружение в правила кодовой базы.

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

Как превратить архитектурный принцип в правило

Фраза «соблюдать модульную архитектуру» ничего не сообщает инструменту проверки. Для автоматизации нужно определить конкретное нарушение.

Допустим, Android-приложение разделено на модули, а доступ к данным организован через интерфейсы. Команда может запретить UI-модулям напрямую зависеть от реализации хранилища. Такое ограничение уже поддаётся проверке.

Проверять его можно на разных уровнях. Запрещённую зависимость в конфигурации Gradle видно при анализе графа модулей. Нежелательные импорты классов выявляет статический анализ исходного кода. Для некоторых ограничений нужны специальные правила или архитектурные тесты. Одна проверка не заменяет остальные.

Поэтому правило сначала стоит сформулировать предельно конкретно. Что именно запрещено? Где действует запрет? Как выглядит допустимое решение? Если на эти вопросы трудно ответить, писать анализатор рано: возможно, команда ещё не до конца определилась с самим архитектурным решением.

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

Проверка должна помогать исправить нарушение

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

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

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

Хорошая диагностика связывает нарушение с объяснением и, где возможно, с примером допустимого решения. Архитектурная документация при этом остаётся необходимой: в ней описаны причины выбора подхода, границы его применения и особенности системы, которые не уместить в сообщение анализатора. Разработчику не приходится держать в памяти все устаревшие компоненты, запрещённые зависимости и правила использования внутренних API. Объяснение он получает в тот момент, когда сталкивается с ограничением.

Какие нарушения должны останавливать разработку

При внедрении автоматического анализа возникает соблазн: раз нарушение обнаруживается, значит, сборку нужно блокировать. Это слишком прямолинейно. Архитектурные проверки — лишь часть того, через что проходит изменение, но вопрос о строгости для любых проверок решается одинаково: по цене ошибки и надёжности диагностики.

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

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

Критерий здесь простой: разработчик должен понимать, почему система остановила его работу. Если объяснение сводится к «так настроено», это слабое основание для обязательного правила.

Что делать со старым кодом

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

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

Один из способов постепенного внедрения — зафиксировать известные нарушения в baseline. Такой механизм есть, например, у Android Lint и detekt. Инструмент сопоставляет результаты анализа с сохранённым списком диагностик и позволяет отдельно обрабатывать уже известные проблемы.

Но baseline не гарантирует, что технический долг перестал расти. Многое зависит от того, как инструмент идентифицирует нарушения, как обновляется список и какие проверки выполняются в CI. Если регулярно перегенерировать baseline без анализа изменений, в него попадут и новые проблемы. Поэтому контролировать нужно и сам список исключений: исправленное нарушение должно из него исчезать, а каждое новое — требовать объяснения.

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

Как не закрепить устаревшую архитектуру

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

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

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

Пересматривая правило, я задаю четыре вопроса. Какую проблему оно предотвращает сейчас? Сколько исключений требует? Не провоцирует ли искусственное усложнение кода? Соответствует ли текущей структуре проекта? Иногда достаточно уточнить область действия проверки, иногда её нужно заменить другой. А если ограничение потеряло смысл, сохранять его только потому, что когда-то на анализатор потратили время, незачем.

Что остаётся за код-ревью

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

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

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

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

Хороший технический стандарт должен выдерживать вопрос разработчика: «Зачем нам до сих пор это правило?» Если убедительного ответа нет, менять пора уже сам стандарт.

Алексей Думбадзе — Senior Android Developer в Dodo Brands. В компании с 2018 года: начинал с бэкенд-разработки на .NET, с 2020 года работает над Android-приложением Dodo Pizza. Это основной канал заказов сети из 1528 ресторанов в 28 странах; Android-версию установили около 8,9 млн раз, ежемесячно ею пользуются около 4,2 млн человек (данные на октябрь 2026 года). Настраивал автоматические архитектурные проверки кода и отвечал за автоматизацию выпуска приложения в четыре магазина, проводит технические собеседования и работает в паре с новыми разработчиками.