Меню
Обвеска или смерть: Как я приручил Gemini CLI в Android-проекте

Обвеска или смерть: Как я приручил Gemini CLI в Android-проекте

от Nikolay Vlasov

Генерировать код стало слишком просто.

У меня был показательный опыт: в одном pet-проекте я попробовал вайбкодинг почти без ревью — по принципу «что агент написал, то и принимаем». Сначала это казалось быстрым и удобным. Но довольно быстро проект начал разваливаться: код стал громоздким, плохо поддерживаемым, часть функциональности ломалась, появились задержки, утечки памяти и в целом весь набор проблем, который обычно копится месяцами — здесь он возник почти сразу.

В какой-то момент стало очевидно: проблема не в том, что агент пишет «плохой» код. Проблема в скорости, с которой этот код накапливается без контроля.


Что ломается при таком подходе

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

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

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


Почему одного промпта оказалось недостаточно

Моя первая попытка была вполне очевидной — я собрал большой rules.md, куда вынес:

  • архитектурные ограничения;
  • naming conventions;
  • правила слоёв;
  • локальные договорённости проекта.

Это частично помогло, но не решило проблему.

На практике оказалось:

  • длинный контекст работает нестабильно;
  • часть правил со временем игнорируется;
  • модель не всегда последовательно применяет ограничения;
  • стоимость и время ответа растут вместе с размером промпта.

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


1. Konsist: архитектура как исполняемый контракт

Архитектурные ограничения и контроль ИИ в Android разработке
Архитектурные ограничения (guardrails) создают безопасный контур для работы ИИ-агента

Агент по умолчанию выбирает самый простой путь реализации. Если можно обойти слой — он это сделает.

Чтобы это ограничить, я начал описывать архитектуру через тесты с помощью Konsist (инструмент для проверки архитектурных правил Kotlin-кода через тесты).

Пример:

@Test
fun `use cases should have UseCase suffix and reside in domain package`() {
    val classes = Konsist.scopeFromProject().classes()

    val violations = classes
        .filter { classDeclaration ->
            classDeclaration.name?.endsWith("UseCase") != true ||
            !classDeclaration.resideInPackage("com.core.domain")
        }

    if (violations.isNotEmpty()) {
        val message = buildString {
            appendLine("VIOLATION: UseCase naming conventions not followed")
            appendLine("FIX: Rename class to end with 'UseCase'")
            appendLine("FIX: Move class to com.core.domain package")
        }
        fail(message)
    }
}

Здесь для меня важны две вещи:

  1. Проверка фиксирует нарушение;
  2. Сообщение даёт понятное направление, как его исправить.

Агент начинает работать не «в вакууме», а в контуре обратной связи.


2. Сжатие логов: меньше шума — быстрее итерации

Одна из проблем, с которой я столкнулся — объём логов.

Если отдавать агенту полный вывод Gradle или JUnit, он просто теряется в этом объёме. Контекст заполняется шумом, а не сигналом.

Поэтому я сделал простой слой сжатия:

  • оставляю только упавшие тесты;
  • беру короткое сообщение об ошибке;
  • ограничиваю стектрейс;
  • убираю всё лишнее.
Фильтрация и сжатие логов для эффективной работы контекстного окна LLM
Сжатие логов: фильтрация «шума» позволяет модели фокусироваться на реальных ошибках

Пример:

def parse_xml_reports(root_dir):
    summary = []

    for testcase in root.findall(".//testcase"):
        failure = testcase.find("failure")
        if failure is not None:
            message = failure.get("message", "No message")
            text = failure.text or ""

            stacktrace = "\n".join(text.strip().split("\n")[:15])

            summary.append(f"FAILED: {testcase.get('name')}")
            summary.append(f"Message: {message}")
            summary.append(f"Stacktrace:\n{stacktrace}\n")

    return summary

После этого цикл «сломалось → починили» стал заметно быстрее и предсказуемее.


3. Spotless и detekt: базовая гигиена без участия человека

Следующий слой — автоматическая проверка качества:

  • Spotless (универсальный инструмент для автоматического форматирования кода);
  • detekt (статический анализатор для поиска потенциальных проблем и сложных конструкций в Kotlin).

Я перестал воспринимать это как «дополнительные инструменты». Это просто часть процесса.

Если код не проходит эти проверки — он не считается готовым. Агент сам возвращается и правит.

Это убирает:

  • мелкие правки на ревью;
  • споры о стиле;
  • постепенную деградацию читаемости.

4. Переводы: устранение механических ошибок

strings.xml оказался неожиданной точкой проблем.

LLM регулярно ошибаются с:

  • апострофами;
  • кавычками;
  • escape-последовательностями.

Я не стал пытаться «обучить» модель этому через текст. Проще оказалось добавить:

  • проверку;
  • автоматическое исправление;
  • работу через парсинг XML.

Важно: без грубых глобальных замен — только точечные исправления.


5. Производительность и размер

Отдельный эффект, который проявился со временем — незаметная деградация продукта.

Агент может решить задачу, но при этом:

  • добавить тяжёлую зависимость;
  • усложнить код;
  • увеличить размер сборки;
  • повлиять на производительность.

Чтобы это отслеживать, я добавил:

  • базовые бенчмарки;
  • контроль размера сборки.
Мониторинг производительности и размера Android-приложения
Постоянный мониторинг метрик предотвращает «тихую» деградацию продукта

Это не даёт полной гарантии, но позволяет хотя бы вовремя увидеть отклонения.


6. Pre-commit: быстрый фильтр (Mercurial/hg или Git)

Часть проверок я вынес в pre-commit:

  • автоформатирование;
  • точечный линтинг;
  • архитектурные проверки для критичных модулей.

Идея не в том, чтобы «запретить всё», а в том, чтобы:

  • быстро отлавливать базовые проблемы;
  • не тащить их в репозиторий;
  • не нагружать ревью лишним шумом.

Почему это работает

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

Изменилось другое: скорость, с которой появляется код.

Когда изменения генерируются быстро и в большом объёме, ручной контроль перестаёт масштабироваться. То, что раньше можно было «поймать глазами», теперь просто не успеваешь обработать.

В этом контексте автоматические проверки перестают быть «хорошей практикой» и становятся базовой необходимостью — просто чтобы держать систему в устойчивом состоянии.


Итог

Я не воспринимаю этот подход как универсальный или обязательный для всех.

Но в моём случае он дал довольно ощутимый эффект — в первую очередь за счёт снижения когнитивной нагрузки.

Мне больше не нужно:

  • держать в голове все ограничения;
  • вручную вычитывать каждое изменение;
  • разбирать огромные логи;
  • постоянно перепроверять базовые вещи.

Система берёт это на себя, а я подключаюсь там, где это действительно имеет смысл.

Из практических бонусов, которые я для себя отметил:

  • более стабильные итерации без случайных регрессий;
  • предсказуемое поведение агента через обратную связь;
  • более быстрый цикл исправлений;
  • меньше шума на ревью;
  • проще масштабировать использование агента.

Это не решает все проблемы, но делает процесс работы с агентом заметно более управляемым — по крайней мере в моём опыте.


Полезные ссылки

  • Konsist — актуальная документация по архитектурному линту для Kotlin.
  • Spotless — инструмент для автоматического форматирования кода.
  • detekt — статический анализатор кода для Kotlin.
  • Mercurial (hg) — система контроля версий.
  • Maestro — платформа для автоматизации UI-тестирования мобильных приложений.

В следующей статье разберу, как я добавил к этому автоматическое прохождение UI-сценариев и проверку интерфейса через MCP Mobile и Maestro (фреймворк для простого и декларативного UI-тестирования мобильных приложений).