Instrumentation eller döden: Hur jag tämjde Gemini CLI i ett Android-projekt
Att generera kod har blivit för enkelt.
Jag hade en intressant upplevelse: i ett hobbyprojekt testade jag "vibe-coding" nästan utan granskning — enligt principen "vi accepterar det som agenten skriver". Till en början verkade detta snabbt och smidigt. Men ganska snart började projektet falla isär: koden blev otymplig och svår att underhålla, delar av funktionaliteten gick sönder, det uppstod fördröjningar och minnesläckor — hela spektrat av problem som vanligtvis tar månader att bygga upp uppstod nästan omedelbart.
Vid en viss tidpunkt blev det uppenbart: problemet är inte att agenten skriver "dålig" kod. Problemet var hastigheten med vilken denna kod ackumulerades utan kontroll.
Vad som går sönder med detta tillvägagångssätt
När man tar bort den manuella kontrollen börjar typiska mönster visa sig:
- arkitektoniska lager kringgås;
- logik dupliceras;
- modulgränser suddas ut;
- slumpmässiga beroenden uppstår;
- koden kompliceras i onödan;
- prestandan degraderas gradvis.
Det viktiga är att allt detta inte händer på en gång. Varje enskilt steg ser "normalt" ut, men sammantaget går systemet snabbt utom kontroll.
Varför en enda prompt inte räckte
Mitt första försök var ganska uppenbart — jag sammanställde en stor rules.md som innehöll:
- arkitektoniska begränsningar;
- namnkonventioner;
- regler för lageruppdelning;
- projektets lokala överenskommelser.
Detta hjälpte delvis, men löste inte problemet.
I praktiken visade det sig att:
- lång kontext fungerar instabilt;
- vissa regler ignoreras med tiden;
- modellen tillämpar inte alltid begränsningar konsekvent;
- kostnad och svarstid ökar med promptens storlek.
Till slut kom jag till en pragmatisk slutsats: Viktiga regler måste kontrolleras, inte bara beskrivas.
1. Konsist: Arkitektur som ett körbart kontrakt
Agenten väljer som standard den enklaste vägen för implementering. Om det går att gå runt ett lager — kommer den att göra det.
För att begränsa detta började jag beskriva arkitekturen genom tester med hjälp av Konsist (ett verktyg för att kontrollera arkitektoniska regler i Kotlin-kod via tester).
Exempel:
@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)
}
}
Här är två saker viktiga för mig:
- Kontrollen fixerar överträdelsen;
- Meddelandet ger en tydlig riktning för hur den ska åtgärdas.
Agenten börjar arbeta inte "i ett vakuum", utan i en feedbackloop.
2. Loggkomprimering: mindre brus — snabbare iterationer
Ett av problemen jag stötte på var volymen på loggar.
Om man skickar hela Gradle- eller JUnit-output till agenten, tappar den bort sig i volymen. Kontexten fylls med brus istället för signal.
Därför skapade jag ett enkelt komprimeringslager:
- jag behåller bara de misslyckade testerna;
- jag tar ett kort felmeddelande;
- jag begränsar stacktrace;
- jag tar bort allt överflödigt.
Exempel:
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
Efter detta blev cykeln "det gick sönder → det lagades" märkbart snabbare och mer förutsägbar.
3. Spotless och detekt: grundläggande hygien utan mänsklig inblandning
Nästa lager är automatiska kvalitetskontroller:
- Spotless (ett universellt verktyg för automatisk kodformatering);
- detekt (en statisk analysator för att hitta potentiella problem och komplexa konstruktioner i Kotlin).
Jag slutade se detta som "extra verktyg". Det är helt enkelt en del av processen.
Om koden inte passerar dessa kontroller anses den inte vara klar. Agenten går själv tillbaka och fixar.
Detta tar bort:
- småfixar vid granskning;
- diskussioner om stil;
- gradvis försämring av läsbarheten.
4. Översättningar: eliminering av mekaniska fel
strings.xml visade sig vara en oväntad problemkälla.
LLM:er gör regelbundet fel med:
- apostrofer;
- citattecken;
- escape-sekvenser.
Jag försökte inte "lära" modellen detta via text. Det visade sig vara enklare att lägga till:
- kontroll;
- automatisk korrigering;
- arbete via XML-parning.
Viktigt: inga grova globala ersättningar — bara punktvisa korrigeringar.
5. Prestanda och storlek
En separat effekt som visade sig med tiden var den osynliga, långsamma degraderingen av produkten.
Agenten kan lösa uppgiften, men samtidigt:
- lägga till ett tungt beroende;
- komplicera koden;
- öka storleken på bygget;
- påverka prestandan.
För att följa detta lade jag till:
- grundläggande benchmarks;
- kontroll av byggstorlek.
Det ger ingen fullständig garanti, men gör det möjligt att åtminstone se avvikelser i tid.
6. Pre-commit: snabbt filter (Mercurial/hg eller Git)
Vissa kontroller flyttade jag till pre-commit:
- autoformatering;
- punktvis linting;
- arkitektoniska kontroller för kritiska moduler.
Idén är inte att "förbjuda allt", utan att:
- snabbt fånga upp grundläggande problem;
- inte dra in dem i repot;
- inte belasta granskningar med onödigt brus.
Varför det fungerar
Själva kärnan i utvecklingen har inte ändrats. Tester, linters och arkitektoniska begränsningar har alltid varit normen.
Något annat har ändrats: hastigheten med vilken kod uppstår.
När ändringar genereras snabbt och i stor volym slutar manuell kontroll att skala. Det som tidigare gick att "fånga med ögat" hinner man helt enkelt inte med att bearbeta längre.
I detta sammanhang slutar automatiska kontroller att vara "best practice" och blir en grundläggande nödvändighet — helt enkelt för att hålla systemet i ett stabilt tillstånd.
Sammanfattning
Jag ser inte detta tillvägagångssätt som universellt eller obligatoriskt för alla.
Men i mitt fall gav det en ganska kännbar effekt — främst genom att minska den kognitiva belastningen.
Jag behöver inte längre:
- hålla alla begränsningar i huvudet;
- manuellt läsa igenom varje ändring;
- analysera enorma loggar;
- ständigt dubbelkolla grundläggande saker.
Systemet tar hand om det, och jag kopplas in där det verkligen behövs.
Från de praktiska fördelarna jag noterat:
- stabilare iterationer utan slumpmässiga regressionsfel;
- förutsägbart beteende hos agenten genom feedback;
- snabbare åtgärdscykel;
- mindre brus vid granskning;
- enklare att skala användningen av agenten.
Det löser inte alla problem, men gör arbetsprocessen med agenten märkbart mer hanterbar — åtminstone i min erfarenhet.
Användbara länkar
- Konsist — aktuell dokumentation om arkitektonisk lint för Kotlin.
- Spotless — verktyg för automatisk kodformatering.
- detekt — statisk kodanalysator för Kotlin.
- Mercurial (hg) — versionshanteringssystem.
- Maestro — plattform för automatisering av UI-tester för mobilappar.
I nästa artikel går jag igenom hur jag lade till automatisk genomgång av UI-scenarier och kontroll av gränssnittet via MCP Mobile och Maestro (ett ramverk för enkel och deklarativ UI-testning av mobilappar).