Обвіска або смерть: Як я приручив Gemini CLI в Android-проєкті
від Nikolay Vlasov
Генерувати код стало занадто просто.
У мене був показовий досвід: в одному 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-тестування мобільних додатків).