Menu
Instrumentasi atau Mati: Cara Saya Menjinakkan Gemini CLI dalam Proyek Android

Instrumentasi atau Mati: Cara Saya Menjinakkan Gemini CLI dalam Proyek Android

oleh Nikolay Vlasov

Menghasilkan kode sekarang menjadi sangat mudah.

Saya memiliki pengalaman yang menarik: dalam sebuah proyek pribadi (pet project), saya mencoba melakukan "vibe-coding" yang hampir tanpa tinjauan—dengan prinsip "apa yang ditulis agen, itulah yang kita terima". Awalnya, cara ini terasa sangat cepat dan nyaman. Namun, tak lama kemudian proyek tersebut mulai berantakan: kodenya menjadi berat dan sulit dipelihara, beberapa fungsionalitas rusak, terjadi kelambatan, kebocoran memori, dan secara keseluruhan seluruh rentetan masalah yang biasanya menumpuk selama berbulan-bulan—di sini muncul hampir seketika.

Pada titik tertentu, menjadi jelas: masalahnya bukan pada agen yang menulis kode "buruk". Masalahnya adalah kecepatan di mana kode tersebut menumpuk tanpa adanya kontrol.


Apa yang Rusak dengan Pendekatan Ini

Saat Anda menghapus kontrol manual, pola-pola tipikal mulai muncul:

  • Mengabaikan lapisan arsitektur (architectural layers);
  • Duplikasi logika;
  • Batas antar modul yang kabur;
  • Dependensi yang tidak disengaja (accidental dependencies);
  • Membuat kode menjadi rumit tanpa alasan;
  • Penurunan performa secara bertahap.

Hal yang penting adalah bahwa semua ini tidak terjadi secara bersamaan. Setiap langkah tunggal terlihat "normal", namun secara keseluruhan sistem dengan cepat menjadi tidak terkendali.


Mengapa Satu Prompt Saja Tidak Cukup

Upaya pertama saya cukup jelas—saya menyusun file rules.md besar yang berisi:

  • Batasan arsitektur;
  • Konvensi penamaan (naming conventions);
  • Aturan tentang lapisan-lapisan (layers);
  • Kesepakatan lokal dalam proyek.

Ini membantu sebagian, namun tidak menyelesaikan masalah.

Secara praktis ternyata:

  • Konteks yang panjang (long context) bekerja secara tidak stabil;
  • Sebagian aturan diabaikan seiring berjalannya waktu;
  • Model tidak selalu menerapkan batasan secara konsisten;
  • Biaya dan waktu respon meningkat seiring dengan besarnya prompt.

Pada akhirnya, saya sampai pada kesimpulan pragmatis: Aturan-aturan penting harus diperiksa, bukan hanya dijelaskan.


1. Konsist: Arsitektur Sebagai Kontrak yang Dapat Dijalankan

Batasan arsitektur dan kontrol AI dalam pengembangan Android
Guardrail arsitektur menciptakan area kerja yang aman bagi agen AI

Agen secara default akan memilih jalur implementasi yang paling mudah. Jika ada cara untuk mengabaikan suatu lapisan—ia akan melakukannya.

Untuk membatasi hal tersebut, saya mulai mendeskripsikan arsitektur melalui pengujian dengan bantuan Konsist (sebuah alat untuk memeriksa aturan arsitektur kode Kotlin melalui pengujian).

Contoh:

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

Di sini ada dua hal yang penting bagi saya:

  1. Pemeriksaan mendeteksi adanya pelanggaran;
  2. Pesan yang diberikan memberikan arahan yang jelas tentang cara memperbaikinya.

Agen mulai bekerja bukan "dalam kehampaan", melainkan dalam sebuah siklus umpan balik (feedback loop).


2. Kompresi Log: Lebih Sedikit Gangguan — Iterasi Lebih Cepat

Salah satu masalah yang saya hadapi adalah volume log.

Jika Anda memberikan seluruh output Gradle atau JUnit kepada agen, ia akan tersesat di tengah volume tersebut. Konteksnya menjadi penuh dengan gangguan (noise), bukan sinyal (signal).

Oleh karena itu, saya membuat lapisan kompresi sederhana:

  • Hanya menyisakan pengujian yang gagal;
  • Mengambil pesan error yang singkat;
  • Membatasi stacktrace;
  • Menghapus semua yang tidak perlu.
Penyaringan dan kompresi log untuk penggunaan jendela konteks LLM yang efisien
Kompresi log: menyaring "noise" memungkinkan model untuk fokus pada kesalahan yang sebenarnya

Contoh:

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

Setelah ini, siklus "rusak → diperbaiki" menjadi jauh lebih cepat dan lebih dapat diprediksi.


3. Spotless dan detekt: Kebersihan Dasar Tanpa Campur Tangan Manusia

Lapisan selanjutnya adalah pemeriksaan kualitas otomatis:

  • Spotless (alat serbaguna untuk pemformatan kode secara otomatis);
  • detekt (penganalisis statis untuk menemukan potensi masalah dan konstruksi yang rumit dalam Kotlin).

Saya berhenti menganggap ini sebagai "alat tambahan". Ini hanyalah bagian dari proses.

Jika kode tidak lolos pemeriksaan ini, maka kode tersebut belum dianggap siap. Agen akan kembali sendiri dan memperbaikinya.

Hal ini menghilangkan:

  • Perbaikan kecil selama peninjauan (review);
  • Perselisihan tentang gaya penulisan kode;
  • Degradasi keterbacaan (readability) secara bertahap.

4. Terjemahan: Menghilangkan Kesalahan Mekanis

strings.xml menjadi titik masalah yang tidak terduga.

LLM seringkali salah dalam hal-hal berikut:

  • Apostrof (apostrophes);
  • Tanda kutip (quotes);
  • Urutan pelepasan (escape sequences).

Saya tidak mencoba "mengajar" model tentang hal ini melalui teks. Lebih mudah ternyata untuk menambahkan:

  • Mekanisme pemeriksaan;
  • Koreksi otomatis;
  • Bekerja melalui parsing XML.

Penting: tidak ada penggantian global yang kasar—hanya perbaikan yang spesifik pada titik masalah.


5. Performa dan Ukuran

Efek terpisah yang muncul seiring waktu adalah degradasi produk yang tidak terlihat secara perlahan.

Agen dapat menyelesaikan tugasnya, namun pada saat yang sama:

  • Menambahkan dependensi yang berat;
  • Membuat kode menjadi rumit;
  • Menambah ukuran build;
  • Mempengaruhi performa.

Untuk memantau hal ini, saya menambahkan:

  • Tolok ukur (benchmarks) dasar;
  • Pemeriksaan ukuran build.
Pemantauan performa dan ukuran aplikasi Android
Pemantauan berkelanjutan terhadap metrik mencegah degradasi produk yang terjadi "dalam diam"

Ini tidak memberikan jaminan penuh, namun setidaknya memungkinkan untuk melihat adanya penyimpangan tepat waktu.


6. Pre-commit: Filter Cepat (Mercurial/hg atau Git)

Beberapa pemeriksaan saya pindahkan ke tahap pre-commit:

  • Pemformatan otomatis (autoformatting);
  • Linting pada bagian-bagian tertentu;
  • Pemeriksaan arsitektur untuk modul-modul kritis.

Idenya bukanlah untuk "melarang segala sesuatu", melainkan untuk:

  • Menangkap masalah dasar dengan cepat;
  • Tidak memasukannya ke dalam repositori;
  • Tidak membebani peninjau (review) dengan kebisingan (noise) yang tidak perlu.

Mengapa Ini Berhasil

Inti dari pengembangan itu sendiri sebenarnya tidak berubah. Pengujian, linter, dan batasan arsitektur selalu menjadi standar.

Yang berubah adalah hal lain: kecepatan di mana kode itu muncul.

Saat perubahan dihasilkan dengan cepat dan dalam volume besar, kontrol manual tidak lagi dapat ditingkatkan skalanya. Apa yang dulunya bisa "ditangkap dengan mata telanjang" kini tidak lagi sempat untuk diproses.

Dalam konteks ini, pemeriksaan otomatis berhenti menjadi "praktik yang baik" (good practice) dan menjadi kebutuhan mendasar—hanya untuk menjaga sistem dalam keadaan stabil.


Kesimpulan

Saya tidak menganggap pendekatan ini universal atau wajib bagi semua orang.

Namun dalam kasus saya, pendekatan ini memberikan dampak yang cukup terasa—terutama dengan mengurangi beban kognitif (cognitive load).

Saya tidak perlu lagi:

  • Mengingat seluruh batasan dalam pikiran;
  • Membaca secara manual setiap perubahan;
  • Menganalisis log yang sangat besar;
  • Terus-menerus memeriksa ulang hal-hal dasar.

Sistem mengambil alih tanggung jawab tersebut, dan saya hanya akan terlibat pada bagian yang benar-benar masuk akal.

Dari keuntungan praktis yang saya catat sendiri:

  • Iterasi yang lebih stabil tanpa adanya regresi (pemunduran) yang tidak disengaja;
  • Perilaku agen yang dapat diprediksi melalui umpan balik;
  • Siklus perbaikan yang lebih cepat;
  • Lebih sedikit kebisingan (noise) selama peninjauan;
  • Lebih mudah untuk meningkatkan skala penggunaan agen.

Ini tidak menyelesaikan semua masalah, namun membuat proses bekerja dengan agen menjadi jauh lebih terkendali—setidaknya dalam pengalaman saya.


Tautan Bermanfaat

  • Konsist — dokumentasi terbaru tentang lint arsitektur untuk Kotlin.
  • Spotless — alat untuk pemformatan kode secara otomatis.
  • detekt — penganalisis kode statis untuk Kotlin.
  • Mercurial (hg) — sistem kontrol versi.
  • Maestro — platform otomatisasi pengujian UI untuk aplikasi seluler.

Dalam artikel berikutnya saya akan membahas bagaimana saya menambahkan otomatisasi skenario UI dan pemeriksaan antarmuka melalui MCP Mobile dan Maestro (framework untuk pengujian UI aplikasi seluler yang sederhana dan deklaratif).