Meny
Instrumentering eller døden: Hvordan jeg temmet Gemini CLI i et Android-prosjekt

Instrumentering eller døden: Hvordan jeg temmet Gemini CLI i et Android-prosjekt

av Nikolay Vlasov

Å generere kode har blitt altfor enkelt.

Jeg hadde en interessant opplevelse: i et hobbyprosjekt testet jeg "vibe-coding" nesten helt uten gjennomgang — etter prinsippet "vi godtar det agenten skriver". Til å begynne med virket dette raskt og smidig. Men ganske snart begynte prosjektet å falle fra hverandre: koden ble tungvint og vanskelig å vedlikeholde, deler av funksjonaliteten ble ødelagt, det oppstod forsinkelser og minnelekkasjer — hele spekteret av problemer som vanligvis tar måneder å bygge opp dukket opp nesten umiddelbart.

På et visst tidspunkt ble det åpenbart: problemet er ikke at agenten skriver "dårlig" kode. Problemet var hastigheten koden ble akkumulert med uten kontroll.


Hva som går galt med denne tilnærmingen

Når man fjerner den manuelle kontrollen, begynner typiske mønstre å vise seg:

  • arkitektoniske lag blir omgått;
  • logikk blir duplisert;
  • modulgrenser viskes ut;
  • tilfeldige avhengigheter oppstår;
  • koden kompliseres unødig;
  • ytelsen blir gradvis dårligere.

Det viktige er at alt dette ikke skjer på en gang. Hvert enkelt steg ser "normalt" ut, men samlet sett går systemet raskt ut av kontroll.


Hvorfor én enkelt prompt ikke var nok

Mitt første forsøk var ganske åpenbart — jeg satte sammen en stor rules.md som inneholdt:

  • arkitektoniske begrensninger;
  • navngivningskonvensjoner;
  • regler for lagdeling;
  • prosjektets lokale avtaler.

Dette hjalp delvis, men løste ikke problemet.

I praksis viste det seg at:

  • lang kontekst fungerer ustabilt;
  • enkelte regler blir ignorert over tid;
  • modellen bruker ikke alltid begrensninger konsekvent;
  • kostnad og svartid øker med størrelsen på prompten.

Til slutt kom jeg til en pragmatisk konklusjon: Viktige regler må kontrolleres, ikke bare beskrives.


1. Konsist: Arkitektur som en kjørbar kontrakt

Arkitektoniske begrensninger og AI-kontroll i Android-utvikling
Arkitektoniske begrensninger (guardrails) skaper en trygg arbeidssone for AI-agenten

Agenten velger som standard den enkleste veien for implementering. Hvis det er mulig å gå rundt et lag — vil den gjøre det.

For å begrense dette begynte jeg å beskrive arkitekturen gjennom tester ved hjelp av Konsist (et verktøy for å kontrollere arkitektoniske regler i Kotlin-kode via tester).

Eksempel:

@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)
    }
}

Her er to ting viktige for meg:

  1. Kontrollen fanger opp bruddet;
  2. Meldingen gir en tydelig retning for hvordan det skal fikses.

Agenten begynner å jobbe, ikke i et vakuum, men i en feedback-sløyfe.


2. Loggkomprimering: mindre støy — raskere iterasjoner

Et av problemene jeg støtte på var volumet på logger.

Hvis man gir hele Gradle- eller JUnit-outputen til agenten, mister den oversikten i mengden. Konteksten fylles med støy i stedet for signal.

Derfor laget jeg et enkelt komprimeringslag:

  • jeg beholder bare de feilede testene;
  • jeg henter en kort feilmelding;
  • jeg begrenser stacktrace;
  • jeg fjerner alt overflødig.
Loggfiltrering og komprimering for effektiv bruk av LLMs kontekstvindu
Loggkomprimering: å filtrere bort "støy" gjør at modellen kan fokusere på de faktiske feilene

Eksempel:

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

Etter dette ble syklusen "det knakk → det ble fikset" merkbart raskere og mer forutsigbar.


3. Spotless og detekt: grunnleggende hygiene uten menneskelig medvirkning

Neste lag er automatiske kvalitetskontroller:

  • Spotless (et universelt verktøy for automatisk kodestatisk formatering);
  • detekt (statisk analyse for å finne potensielle problemer og komplekse konstruksjoner i Kotlin).

Jeg sluttet å se på dette som "ekstra verktøy". Det er rett og slett en del av prosessen.

Hvis koden ikke passerer disse kontrollene, regnes den ikke som ferdig. Agenten går selv tilbake og fikser det.

Dette fjerner:

  • smårettinger ved gjennomgang;
  • diskusjoner om stil;
  • gradvis forringelse av lesbarheten.

4. Oversettelser: eliminering av mekaniske feil

strings.xml viste seg å være en uventet kilde til problemer.

LLM-er gjør regelmessig feil med:

  • apostrofer;
  • anførselstegn;
  • escape-sekvenser.

Jeg prøvde ikke å "lære" modellen dette via tekst. Det viste seg enklere å legge til:

  • kontroll;
  • automatisk retting;
  • arbeid via XML-parsing.

Viktig: ingen grove globale utskiftninger — bare punktvise rettinger.


5. Ytelse og størrelse

En separat effekt som viste seg med tiden var den usynlige, langsomme degraderingen av produktet.

Agenten kan løse oppgaven, men samtidig:

  • legge til en tung avhengighet;
  • komplisere koden;
  • øke størrelsen på bygget;
  • påvirke ytelsen.

For å følge med på dette la jeg til:

  • grunnleggende benchmarks;
  • kontroll av byggstørrelse.
Overvåking av ytelse og størrelse for Android-appen
Løpende overvåking av måltall forhindrer en "stille" degradering av produktet

Det gir ingen fullstendig garanti, men gjør det mulig å i det minste se avvik i tide.


6. Pre-commit: raskt filter (Mercurial/hg eller Git)

Enkelte kontroller flyttet jeg til pre-commit:

  • autoformatering;
  • punktvis linting;
  • arkitektoniske kontroller for kritiske moduler.

ID-en er ikke å "forby alt", men å:

  • raskt fange opp grunnleggende problemer;
  • ikke dra dem inn i repoet;
  • ikke belaste gjennomganger med unødvendig støy.

Hvorfor det fungerer

Selve kjernen i utviklingen har ikke endret seg. Tester, lintere og arkitektoniske begrensninger har alltid vært normen.

Noe annet har endret seg: hastigheten koden oppstår med.

Når endringer genereres raskt og i stor mengde, slutter manuell kontroll å skalere. Det man tidligere kunne "se med egne øyne", rekker man rett og slett ikke å bearbeide lenger.

I denne sammenhengen slutter automatiske kontroller å være "best practice" og blir en grunnleggende nødvendighet — rett og slett for å holde systemet i en stabil tilstand.


Oppsummering

Jeg ser ikke på denne tilnærmingen som universell eller obligatorisk for alle.

Men i mitt tilfelle ga det en ganske merkbar effekt — først og fremst ved å redusere den kognitive belastningen.

Jeg trenger ikke lenger:

  • holde alle begrensninger i hodet;
  • lese gjennom hver eneste endring manuelt;
  • analysere enorme logger;
  • stadig dobbeltsjekke grunnleggende ting.

Systemet tar seg av det, og jeg kobler meg på der det virkelig gir mening.

Av de praktiske fordelene jeg har merket:

  • stabilere iterasjoner uten tilfeldige regresjoner;
  • forutsigbar oppførsel fra agenten gjennom feedback;
  • raskere rettingssyklus;
  • mindre støy ved gjennomgang;
  • enklere å skalere bruken av agenten.

Det løser ikke alle problemer, men gjør prosessen med å jobbe med agenten merkbart mer håndterbar — i hvert fall i min erfaring.


Nyttige lenker

  • Konsist — oppdatert dokumentasjon om arkitektonisk lint for Kotlin.
  • Spotless — verktøy for automatisk kodingformatering.
  • detekt — statisk kodeanalyse for Kotlin.
  • Mercurial (hg) — versjonskontrollsystem.
  • Maestro — plattform for automatisering av UI-tester for mobilapper.

I neste artikkel vil jeg gå gjennom hvordan jeg la til automatisk gjennomgang av UI-scenarioer og kontroll av grensesnittet via MCP Mobile og Maestro (et rammeverk for enkel og deklarativ UI-testing av mobilapper).