Menu
Instrumentação ou morte: Como domei o Gemini CLI no meu projeto Android

Instrumentação ou morte: Como domei o Gemini CLI no meu projeto Android

por Nikolay Vlasov

Gerar código tornou-se demasiado fácil.

Tive uma experiência reveladora: num projeto pessoal, experimentei o "vibe-coding" quase sem revisão — seguindo o princípio "o que o agente escrever, aceitamos". No início, parecia rápido e conveniente. Mas, em pouco tempo, o projeto começou a desmoronar: o código tornou-se pesado, de difícil manutenção, parte da funcionalidade quebrava, surgiram atrasos, fugas de memória e, no fundo, todo o conjunto de problemas que costumam acumular-se ao longo de meses — aqui surgiram quase de imediato.

A certa altura, tornou-se óbvio: o problema não é o agente escrever código "mau". O problema é a velocidade com que esse código se acumula sem controlo.


O que falha com esta abordagem

Quando removemos o controlo manual, começam a manifestar-se padrões típicos:

  • evasão das camadas arquiteturais;
  • duplicação da lógica;
  • limites de módulos difusos;
  • dependências aleatórias;
  • complexificação desnecessária do código;
  • degradação gradual do desempenho.

O importante é que tudo isto não acontece num instante. Cada passo individual parece "normal", mas, no conjunto, o sistema sai rapidamente de controlo.


Por que um único prompt não foi suficiente

A minha primeira tentativa foi bastante óbvia — reuni um grande rules.md onde incluí:

  • restrições arquiteturais;
  • naming conventions;
  • regras de camadas;
  • acordos locais do projeto.

Isto ajudou parcialmente, mas não resolveu o problema.

Na prática, verificou-se que:

  • contextos longos funcionam de forma instável;
  • parte das regras acaba por ser ignorada com o tempo;
  • o modelo nem sempre aplica as restrições de forma consistente;
  • o custo e o tempo de resposta crescem com o tamanho do prompt.

No final, cheguei a uma conclusão mais pragmática: As regras importantes devem ser verificadas, e não apenas descritas.


1. Konsist: a arquitetura como um contrato executável

Restrições arquiteturais e controlo de IA no desenvolvimento Android
Os guardrails arquiteturais criam um perímetro de segurança para o trabalho do agente de IA

Por padrão, o agente escolhe o caminho de implementação mais simples. Se puder ignorar uma camada — ele fá-lo-á.

Para limitar isto, comecei a descrever a arquitetura através de testes com o Konsist (uma ferramenta para verificar regras arquiteturais de código Kotlin através de testes unitários).

Exemplo:

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

Aqui, duas coisas são fundamentais para mim:

  1. A verificação deteta a violação;
  2. A mensagem fornece uma direção clara sobre como corrigi-la.

O agente deixa de trabalhar "no vácuo" e passa a trabalhar dentro de um ciclo de feedback.


2. Compressão de logs: menos ruído — iterações mais rápidas

Um dos problemas com que me deparei foi o volume dos logs.

Se dermos ao agente o output completo do Gradle ou do JUnit, ele simplesmente perde-se em todo esse volume. O contexto enche-se de ruído em vez de sinal.

Por isso, criei uma camada simples de compressão:

  • mantenho apenas os testes que falharam;
  • extraio uma mensagem de erro curta;
  • limito o stacktrace;
  • removo tudo o que é desnecessário.
Filtragem e compressão de logs para uma utilização eficiente da janela de contexto dos LLM
Compressão de logs: filtrar o "ruído" permite ao modelo focar-se nos erros reais

Exemplo:

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

Depois disso, o ciclo "quebrou → reparou" tornou-se visivelmente mais rápido e previsível.


3. Spotless e detekt: higiene básica sem intervenção humana

A camada seguinte é a verificação automática de qualidade:

  • Spotless (uma ferramenta universal para formatação automática de código);
  • detekt (analisador estático para Kotlin para encontrar problemas potenciais e estruturas complexas).

Deixei de ver isto como "ferramentas adicionais". É simplesmente parte do processo.

Se o código não passar nestas verificações — não é considerado pronto. O próprio agente volta atrás e corrige.

Isto elimina:

  • pequenas correções nas revisões;
  • discussões sobre o estilo;
  • degradação gradual da legibilidade.

4. Traduções: eliminação de erros mecânicos

O ficheiro strings.xml revelou-se um ponto de problemas inesperado.

Os LLMs erram regularmente com:

  • apóstrofos;
  • aspas;
  • sequências de escape.

Não tentei "ensinar" isto ao modelo através de texto. Revelou-se mais simples adicionar:

  • uma verificação;
  • correção automática;
  • trabalho através de parsing XML.

Importante: sem alterações globais bruscas — apenas correções pontuais.


5. Desempenho e tamanho

Um outro efeito que se manifestou ao longo do tempo foi a degradação invisível do produto.

O agente pode resolver a tarefa, mas, ao mesmo tempo:

  • adicionar uma dependência pesada;
  • complexificar o código;
  • aumentar o tamanho da build;
  • afetar o desempenho.

Para monitorizar isto, adicionei:

  • benchmarks básicos;
  • controlo do tamanho da build.
Monitorização de desempenho e tamanho de uma aplicação Android
A monitorização constante de métricas evita a degradação "silenciosa" do produto

Isto não dá uma garantia total, mas permite, pelo menos, notar desvios a tempo.


6. Pre-commit: filtro rápido (Mercurial/hg ou Git)

Movi parte das verificações para o pre-commit:

  • auto-formatação;
  • linting pontual;
  • verificações arquiteturais para módulos críticos.

A ideia não é "proibir tudo", mas sim:

  • detetar rapidamente problemas básicos;
  • não os levar para o repositório;
  • não sobrecarregar a revisão com ruído desnecessário.

Por que funciona

A essência do desenvolvimento não mudou. Testes, linters e restrições arquiteturais sempre foram a norma.

O que mudou foi outra coisa: a velocidade a que o código aparece.

Quando as alterações são geradas rapidamente e em grandes volumes, o controlo manual deixa de ser escalável. O que antes se conseguia "detetar a olho", agora simplesmente não há tempo para processar.

Neste contexto, as verificações automáticas deixam de ser uma "boa prática" e tornam-se numa necessidade básica — simplesmente para manter o sistema num estado estável.


Conclusão

Não vejo esta abordagem como universal ou obrigatória para todos.

Mas no meu caso, deu um efeito bastante percetível — em primeiro lugar devida à redução da carga cognitiva.

Já não preciso de:

  • manter todas as restrições na cabeça;
  • rever manualmente cada alteração;
  • analisar logs enormes;
  • reconfirmar constantemente as bases.

O sistema encarrega-se disso, e eu intervenho onde realmente faz sentido.

Dos benefícios práticos que notei:

  • iterações mais estáveis sem regressões aleatórias;
  • comportamento do agente previsível através do feedback;
  • ciclo de correção mais rápido;
  • menos ruído na revisão;
  • mais fácil escalar a utilização de agentes.

Isto não resolve todos os problemas, mas torna o processo de trabalho com um agente sensivelmente mais gerível — pelo menos na minha experiência.


Links úteis

  • Konsist — documentação atualizada sobre o lint arquitetural para Kotlin.
  • Spotless — ferramenta para formatação automática de código.
  • detekt — analisador estático de código para Kotlin.
  • Mercurial (hg) — sistema de controlo de versões.
  • Maestro — plataforma para a automatização de testes de interface (UI) móvel.

No próximo artigo, explicarei como adicionei a isto a execução automática de cenários de interface e a verificação da interface através do MCP Mobile e do Maestro (framework para testes de UI móvel simples e declarativos).