Menü
Instrumentation oder Untergang: Wie ich Gemini CLI in meinem Android-Projekt zähmte

Instrumentation oder Untergang: Wie ich Gemini CLI in meinem Android-Projekt zähmte

von Nikolay Vlasov

Code zu generieren ist zu einfach geworden.

Ich hatte eine prägende Erfahrung: In einem Pet-Projekt probierte ich „Vibe-Coding“ fast ohne Review aus – nach dem Prinzip „was der Agent schreibt, wird übernommen“. Zuerst fühlte es sich schnell und bequem an. Doch schon bald begann das Projekt auseinanderzufallen: Der Code wurde sperrig, schwer wartbar, Teile der Funktionalität brachen ab, es gab Verzögerungen, Speicherlecks und im Grunde die ganze Palette an Problemen, die sich normalerweise über Monate ansammeln – hier entstanden sie fast sofort.

Irgendwann wurde klar: Das Problem ist nicht, dass der Agent „schlechten“ Code schreibt. Das Problem ist die Geschwindigkeit, mit der sich dieser Code ohne Kontrolle anhäuft.


Was bei diesem Ansatz schiefgeht

Wenn man die manuelle Kontrolle entfernt, treten typische Muster zutage:

  • Umgehung von Architektur-Layern;
  • Duplizierung von Logik;
  • verschwommene Modulgrenzen;
  • zufällige Abhängigkeiten;
  • unnötige Code-Komplexität;
  • schrittweise Verschlechterung der Performance.

Wichtig ist, dass dies nicht alles auf einmal passiert. Jeder einzelne Schritt sieht „normal“ aus, aber in der Summe gerät das System schnell außer Kontrolle.


Warum ein einzelner Prompt nicht ausreichte

Mein erster Versuch war naheliegend – ich erstellte eine umfangreiche rules.md mit:

  • Architektur-Beschränkungen;
  • Naming Conventions;
  • Layer-Regeln;
  • lokalen Projektvereinbarungen.

Das half teilweise, löste das Problem aber nicht.

In der Praxis zeigte sich:

  • ein langer Kontext arbeitet instabil;
  • ein Teil der Regeln wird mit der Zeit ignoriert;
  • das Modell wendet Beschränkungen nicht immer konsistent an;
  • Kosten und Antwortzeit steigen mit der Größe des Prompts.

Schließlich kam ich zu einem pragmatischeren Schluss: Wichtige Regeln müssen überprüft werden, es reicht nicht, sie nur zu beschreiben.


1. Konsist: Architektur als ausführbarer Vertrag

Architektureinschränkungen und KI-Kontrolle in der Android-Entwicklung
Architektur-Einschränkungen (Guardrails) schaffen einen sicheren Rahmen für die Arbeit von KI-Agenten

Ein Agent wählt standardmäßig den einfachsten Weg der Implementierung. Wenn er eine Ebene umgehen kann, wird er es tun.

Um dies einzuschränken, begann ich, die Architektur über Tests mit Konsist zu beschreiben (ein Tool zur Überprüfung von Architekturregeln in Kotlin-Code via Unit-Tests).

Beispiel:

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

Hier sind für mich zwei Dinge entscheidend:

  1. Die Prüfung registriert die Verletzung;
  2. Die Fehlermeldung gibt eine klare Anweisung zur Behebung.

Der Agent arbeitet dadurch nicht mehr „im luftleeren Raum“, sondern in einer Feedbackschleife.


2. Log-Kompression: Weniger Rauschen – schnellere Iterationen

Eines der Probleme, auf die ich stieß, war das Volumen der Logs.

Wenn man dem Agenten die vollständige Gradle- oder JUnit-Ausgabe übergibt, verliert er sich in der Menge. Der Kontext füllt sich mit Rauschen statt mit Signalen.

Deshalb habe ich eine einfache Kompressionsschicht eingebaut:

  • nur fehlgeschlagene Tests werden beibehalten;
  • eine kurze Fehlermeldung wird extrahiert;
  • der Stacktrace wird begrenzt;
  • alles Unnötige wird entfernt.
Filterung und Kompression von Logs für effiziente LLM-Kontextfenster-Nutzung
Log-Kompression: Das Filtern von „Rauschen“ ermöglicht es dem Modell, sich auf echte Fehler zu konzentrieren

Beispiel:

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

Danach wurde der Zyklus „Fehler → Fix“ spürbar schneller und vorhersagbarer.


3. Spotless und detekt: Basishygiene ohne menschliches Zutun

Die nächste Ebene ist die automatische Qualitätsprüfung:

  • Spotless (ein universelles Tool zur automatischen Codeformatierung);
  • detekt (ein statischer Analysator für Kotlin, um potenzielle Fehler und komplexe Konstrukte zu finden).

Ich betrachte dies nicht mehr als „zusätzliche Tools“. Es ist einfach Teil des Prozesses.

Wenn Code diese Prüfungen nicht besteht, gilt er nicht als fertig. Der Agent kehrt selbstständig zurück und korrigiert ihn.

Das eliminiert:

  • mühsame Kleinstkorrekturen im Review;
  • Diskussionen über den Stil;
  • die schrittweise Verschlechterung der Lesbarkeit.

4. Übersetzungen: Eliminierung mechanischer Fehler

strings.xml erwies sich als unerwarteter Problempunkt.

LLMs machen regelmäßig Fehler bei:

  • Apostrophen;
  • Anführungszeichen;
  • Escape-Sequenzen.

Anstatt zu versuchen, dem Modell dies mühsam via Prompt beizubringen, war es einfacher, Folgendes hinzuzufügen:

  • eine Validierung;
  • automatische Korrekturen;
  • Verarbeitung durch XML-Parsing.

Wichtig dabei: Keine groben globalen Ersetzungen – nur gezielte Korrekturen.


5. Performance und Größe

Ein weiterer Effekt, der sich über die Zeit zeigte, war die schleichende Verschlechterung des Produkts.

Ein Agent kann eine Aufgabe lösen, dabei aber:

  • eine schwere Abhängigkeit einbauen;
  • den Code unnötig verkomplizieren;
  • die Build-Größe erhöhen;
  • die Performance beeinträchtigen.

Um dies zu überwachen, habe ich hinzugefügt:

  • Basis-Benchmarks;
  • Kontrolle der Build-Größe.
Überwachung von Performance und Größe einer Android-App
Die ständige Überwachung von Metriken verhindert die „schleichende“ Verschlechterung des Produkts

Es bietet keine vollständige Garantie, erlaubt es aber zumindest, Abweichungen rechtzeitig zu erkennen.


6. Pre-commit: Schneller Filter (Mercurial/hg oder Git)

Ein Teil der Prüfungen habe ich in den Pre-commit-Hook ausgelagert:

  • Auto-Formatierung;
  • punktueller Linting;
  • Architekturprüfungen für kritische Module.

Die Idee ist nicht „alles zu verbieten“, sondern:

  • Basisprobleme schnell abzufangen;
  • sie gar nicht erst in das Repository zu lassen;
  • das Review nicht mit unnötigem Rauschen zu belasten.

Warum das funktioniert

Das Wesen der Softwareentwicklung hat sich nicht geändert. Tests, Linter und Architekturregeln waren schon immer der Standard.

Was sich geändert hat, ist die Geschwindigkeit, mit der Code entsteht.

Wenn Änderungen schnell und in großen Mengen generiert werden, skaliert manuelle Kontrolle nicht mehr. Dinge, die man früher „mit dem Auge“ finden konnte, lassen sich heute zeitlich einfach nicht mehr erfassen.

In diesem Kontext sind automatisierte Prüfungen keine „Best Practice“ mehr, sondern eine grundlegende Notwendigkeit – schlichtweg, um das System stabil zu halten.


Fazit

Ich betrachte diesen Ansatz nicht als universell oder verpflichtend für jeden.

In meinem Fall hatte er jedoch einen spürbaren Effekt – vor allem durch die Reduzierung der kognitiven Last.

Ich muss nicht mehr:

  • alle Beschränkungen im Kopf behalten;
  • jede Änderung manuell bis ins Detail prüfen;
  • riesige Logs analysieren;
  • ständig Basics doppelt checken.

Das System erledigt das, und ich klinke mich dort ein, wo es wirklich sinnvoll ist.

Die praktischen Vorteile, die ich bemerkt habe:

  • stabilere Iterationen ohne zufällige Regressionen;
  • vorhersagbares Verhalten des Agenten durch Feedback;
  • ein deutlich schnellerer Korrekturzyklus;
  • weniger Rauschen im Review;
  • die Nutzung von Agenten lässt sich einfacher skalieren.

Das löst nicht alle Probleme, macht den Prozess der Arbeit mit einem Agenten jedoch spürbar steuerbarer – zumindest nach meiner Erfahrung.


Nützliche Links

  • Konsist – Aktuelle Dokumentation zum Architektur-Linting für Kotlin.
  • Spotless – Tool zur automatischen Codeformatierung.
  • detekt – Statischer Code-Analysator für Kotlin.
  • Mercurial (hg) – Versionskontrollsystem.
  • Maestro – Plattform zur Automatisierung von UI-Tests für mobile Apps.

In der nächsten Artikel-Serie werde ich darauf eingehen, wie ich automatisierte UI-Szenarien und Interface-Prüfungen via MCP Mobile und Maestro (ein Framework für einfache und deklarative UI-Tests mobiler Apps) hinzugefügt habe.