Обвеска или смерть: Как я приручил Gemini CLI в Android-проекте
Генерировать код стало слишком просто.
У меня был показательный опыт: в одном pet-проекте я попробовал вайбкодинг почти без ревью — по принципу «что агент написал, то и принимаем». Сначала это казалось быстрым и удобным. Но довольно быстро проект начал разваливаться: код стал громоздким, плохо поддерживаемым, часть функциональности ломалась, появились задержки, утечки памяти и в целом весь набор проблем, который обычно копится месяцами — здесь он возник почти сразу.
В какой-то момент стало очевидно: проблема не в том, что агент пишет «плохой» код. Проблема в скорости, с которой этот код накапливается без контроля.
Что ломается при таком подходе
Когда убираешь ручной контроль, начинают проявляться типичные паттерны:
- обход архитектурных слоёв;
- дублирование логики;
- размытые границы модулей;
- случайные зависимости;
- усложнение кода без необходимости;
- постепенная деградация производительности.
Важно, что всё это происходит не одномоментно. Каждый отдельный шаг выглядит «нормально», но суммарно система быстро выходит из-под контроля.
Почему одного промпта оказалось недостаточно
Моя первая попытка была вполне очевидной — я собрал большой rules.md, куда вынес:
- архитектурные ограничения;
- naming conventions;
- правила слоёв;
- локальные договорённости проекта.
Это частично помогло, но не решило проблему.
На практике оказалось:
- длинный контекст работает нестабильно;
- часть правил со временем игнорируется;
- модель не всегда последовательно применяет ограничения;
- стоимость и время ответа растут вместе с размером промпта.
В итоге я пришёл к более прагматичному выводу: важные правила нужно проверять, а нет только описывать.
1. Konsist: архитектура как исполняемый контракт
Агент по умолчанию выбирает самый простой путь реализации. Если можно обойти слой — он это сделает.
Чтобы это ограничить, я начал описывать архитектуру через тесты с помощью 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)
}
}
Здесь для меня важны две вещи:
- Проверка фиксирует нарушение;
- Сообщение даёт понятное направление, как его исправить.
Агент начинает работать не «в вакууме», а в контуре обратной связи.
2. Сжатие логов: меньше шума — быстрее итерации
Одна из проблем, с которой я столкнулся — объём логов.
Если отдавать агенту полный вывод Gradle или JUnit, он просто теряется в этом объёме. Контекст заполняется шумом, а не сигналом.
Поэтому я сделал простой слой сжатия:
- оставляю только упавшие тесты;
- беру короткое сообщение об ошибке;
- ограничиваю стектрейс;
- убираю всё лишнее.
Пример:
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. Производительность и размер
Отдельный эффект, который проявился со временем — незаметная деградация продукта.
Агент может решить задачу, но при этом:
- добавить тяжёлую зависимость;
- усложнить код;
- увеличить размер сборки;
- повлиять на производительность.
Чтобы это отслеживать, я добавил:
- базовые бенчмарки;
- контроль размера сборки.
Это не даёт полной гарантии, но позволяет хотя бы вовремя увидеть отклонения.
6. Pre-commit: быстрый фильтр (Mercurial/hg или Git)
Часть проверок я вынес в pre-commit:
- автоформатирование;
- точечный линтинг;
- архитектурные проверки для критичных модулей.
Идея не в том, чтобы «запретить всё», а в том, чтобы:
- быстро отлавливать базовые проблемы;
- не тащить их в репозиторий;
- не нагружать ревью лишним шумом.
Почему это работает
Сама суть разработки не изменилась. Тесты, линтеры и архитектурные ограничения всегда были нормой.
Изменилось другое: скорость, с которой появляется код.
Когда изменения генерируются быстро и в большом объёме, ручной контроль перестаёт масштабироваться. То, что раньше можно было «поймать глазами», теперь просто не успеваешь обработать.
В этом контексте автоматические проверки перестают быть «хорошей практикой» и становятся базовой необходимостью — просто чтобы держать систему в устойчивом состоянии.
Итог
Я не воспринимаю этот подход как универсальный или обязательный для всех.
Но в моём случае он дал довольно ощутимый эффект — в первую очередь за счёт снижения когнитивной нагрузки.
Мне больше не нужно:
- держать в голове все ограничения;
- вручную вычитывать каждое изменение;
- разбирать огромные логи;
- постоянно перепроверять базовые вещи.
Система берёт это на себя, а я подключаюсь там, где это действительно имеет смысл.
Из практических бонусов, которые я для себя отметил:
- более стабильные итерации без случайных регрессий;
- предсказуемое поведение агента через обратную связь;
- более быстрый цикл исправлений;
- меньше шума на ревью;
- проще масштабировать использование агента.
Это не решает все проблемы, но делает процесс работы с агентом заметно более управляемым — по крайней мере в моём опыте.
Полезные ссылки
- Konsist — актуальная документация по архитектурному линту для Kotlin.
- Spotless — инструмент для автоматического форматирования кода.
- detekt — статический анализатор кода для Kotlin.
- Mercurial (hg) — система контроля версий.
- Maestro — платформа для автоматизации UI-тестирования мобильных приложений.
В следующей статье разберу, как я добавил к этому автоматическое прохождение UI-сценариев и проверку интерфейса через MCP Mobile и Maestro (фреймворк для простого и декларативного UI-тестирования мобильных приложений).