Fundamentos de Manutenção de Software
10 Manutenção Usando IA 🔗
10.1 Introdução 🔗
(em breve)
10.2 Assistentes de Código 🔗
O principal representante desse tipo de ferramenta de IA é o GitHub Copilot, lançado em 2021 pelo GitHub em parceria com a OpenAI. Essa ferramenta se propõe a atuar como um assistente do desenvolvedor, de forma semelhante ao desenvolvedor que desempenha o papel de navegador em uma sessão de programação pareada (pair programming). Daí também o emprego do termo pareamento com IA para o estilo de desenvolvimento proposto pelo GitHub Copilot. De forma concreta, a ferramenta funciona integrada a uma IDE e sugere blocos de código para completar o código que está sendo escrito de forma manual. Em suas primeiras versões, o GitHub Copilot usava um modelo de linguagem chamado Codex, que foi treinado especificamente para geração de código.
Suponha que você começou a implementar um método de ordenação e escreveu a sua assinatura (primeira linha):
void selectionSort(int[] arr) {
Nesse momento, o GitHub Copilot percebe que você está começando a implementar um novo método e sugere o restante do seu código, ou seja:
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
Cabe ao desenvolvedor analisar esse código antes de aceitá-lo. Se não quiser aproveitar o código sugerido, o desenvolvedor pode ignorar a sugestão (ou deletá-la) e continuar escrevendo o código de forma manual.
A implementação de um assistente de código como o GitHub Copilot é tão complexa. Basicamente, ele monitora continuamente a escrita do código e, em paralelo, envia prompts para uma LLM completar as partes pendentes do mesmo. No exemplo anterior, após a escrita da primeira linha do método, o Copilot pode submeter o seguinte prompt para a LLM integrada à ferramenta:
You are a coding assistant. Continue this Java function:
public void selectionSort(int[] arr) {
Na prática, os prompts usados por assistentes de código são mais completos do que esse e, por exemplo, incluem as linhas anteriores à posição atual do cursor na IDE, as linhas que seguem essa posição do cursor, comentários próximos ao cursor e bibliotecas importadas no início do arquivo.
A vantagem desse modelo de pareamento com IA é que o desenvolvedor continua sendo o protagonista da sessão, cabendo a ele analisar e aceitar os trechos de código sugeridos pelo assistente de código. Por outro lado, nesse tipo de ferramenta o modelo de IA não possui autonomia para simultaneamente em múltiplas partes do código.
Antes de concluir, é importante mencionar que, após a sua primeira versão, o GitHub Copilot incorporou novos recursos, tais como um chat para submissão de tarefas e também uma versão baseada em agentes, modelo que será descrito na próxima seção.
10.3 Agentes de Código 🔗
10.3.1 Arquitetura 🔗
Um agente de código é caracterizado pela sua autonomia. Ele recebe uma tarefa (por exemplo, realizar uma refatoração) e então, para realizá-la, ele pode solicitar a execução de ferramentas ou pequenos scripts na máquina do cliente, tais como ler arquivos, realizar mudanças em arquivos, executar testes, criar commits, abrir pull requests, etc.
Normalmente, usamos LLMs da seguinte forma: enviamos um prompt com uma tarefa, o modelo de linguagem realiza tal tarefa e devolve um resultado. No entanto, a implementação de um agente de código é mais sofisticada e consiste, essencialmente, de um loop como mostrado na próxima figura.
Assim como no modelo tradicional, o usuário escreve um prompt solicitando que o agente realize uma determinada tarefa. No entanto, o agente não precisa realizar essa tarefa de uma só vez, como quando LLMs são usadas por meio de uma interface de chat. Em vez disso, o modelo pode pedir para o agente executar primeiro uma ferramenta e enviar o resultado dessa execução para sua análise. Esse loop (modelo → ferramenta → modelo → …) pode ser executado diversas vezes, até que o modelo decida dar uma resposta definitiva para a tarefa que foi inicialmente solicitada. Quando isso acontece, o loop termina.
No contexto de agentes de código e LLMs, o loop descrito no parágrafo anterior é chamada de loop agêntico (agentic loop). Existe ainda o termo infraestrutura do agente (agentic harness), que é usado para denominar todo o código do agente, exceto aquele que é parte do modelo. Logo, o loop que mencionamos, o disparo da execução das ferramentas e até a interface com o usuário do agente fazem parte da sua infraestrutura.
Portanto, em termos arquiteturais, um agente de código possui dois componentes principais: harness e o modelo de linguagem (sendo esse último, na verdade, um componente externo acessado via uma API). O código responsável por tudo que acontece antes e após a chamada do modelo faz parte do componente de harness, conforme ilustrado na próxima figura.
Nessa figura, podemos ver que o módulo de harness fica também responsável por chamar as ferramentas que vão rodar na máquina do desenvolvedor que está usando o agente. Existem pelo menos quatro grupos principais de ferramentas que podem ser chamadas pelo módulo de harness:
Manipulação de arquivos (ler, editar, criar novos arquivos, etc.).
Busca em arquivos (listar arquivos cujo nome segue um padrão, buscar conteúdo de arquivos usando expressões regulares, etc.)
Execução de comandos shell (iniciar servidores, executar testes, comandos git, etc.).
Acesso a Web (pesquisar na web, acessar documentação, buscar mensagens de erro, etc.).
Exemplo: Para ilustrar o funcionamento do loop
agêntico considere a seguinte tarefa de refatoração: substituir o uso do
tipo Vector pelo tipo ArrayList em um sistema.
Apesar de simples, essa tarefa pode exigir sete iterações do loop,
conforme explicado a seguir:
Iteração 1:
- Modelo: solicita buscar arquivos com ocorrências de
Vector. - Harness: executa ferramenta de busca; envia resultados para Modelo.
Iteração 2:
- Modelo: solicita leitura dos arquivos relevantes.
- Harness: executa ferramenta de leitura; envia conteúdo para Modelo.
Iteração 3:
- Modelo: identifica onde
Vectoré usado como tipo; solicita renomeação. - Harness: executa ferramenta de edição para substituir
VectorporArrayList; envia confirmação da mudança para Modelo.
Iteração 4:
- Modelo: solicita execução dos testes.
- Harness: executa ferramenta de testes; envia resultados para Modelo.
Iteração 5:
- Modelo: solicita correções nos testes que falharam.
- Harness: executa ferramenta de edição e reexecuta testes; envia resultados para Modelo.
Iteração 6:
- Modelo: como os testes passaram, solicita criação de commit e PR.
- Harness: executa comandos para commit e abertura de PRs; envia resultados para Modelo.
Iteração 7:
- Modelo: Retorna resposta final
Chamamos de trajetória o logging completo de todas as ações executadas pelo loop agêntico para realizar uma determinada tarefa. Esse logging inclui as ações tomadas pelo modelo, bem como as ferramentas que foram executadas e seus resultados.
Memória do Agente: Uma característica importante de uma loop agêntico é o seu caráter stateful: ao contrário de uma chamada isolada a um LLM, cada iteração inclui todo o histórico da conversa até aquele momento. Na prática, isso significa que, a cada nova chamada ao modelo, o harness envia não apenas o resultado mais recente, mas também o resultado de todas as ferramentas executadas anteriormente e todas as respostas que o modelo produziu. Esse histórico completo, também chamado de memória do agente, permite ao modelo lembrar o que já foi feito, raciocinar sobre os resultados obtidos e decidir qual ferramenta acionar a seguir.
Também faz parte da memória do agente, um prompt de sistema, que o agente repassa para o modelo. Esse prompt descreve o papel do modelo, as ferramentas disponíveis e as restrições que deve respeitar. Então, ele é usado para instruir um modelo genérico a se comportar como um agente de código. Por exemplo, sem esse prompt, o modelo não saberia que pode solicitar a execução de ferramentas, nem quais ferramentas estão à sua disposição. Apenas para ilustrar e ficar mais claro, segue um exemplo bem simples de prompt de sistema:
You are an autonomous coding agent.
Goal: complete the user task using iterative tool calls.
Rules:
- Explore the codebase before modifying it.
- Make small, safe changes.
- Run tests frequently.
- Fix errors before proceeding.
- Stop only when the task is complete.
You can:
- search files
- read files
- edit files
- run commands
Always decide the next best action.
Para concluir, a principal vantagem do modelo de agentes é sua autonomia e capacidade de quebrar um problema complexo em passos menores. Para resolver esses passos, o agente pode precisar de informações locais, que são obtidas por meio da execução de ferramentas. Por outro lado, a principal desvantagem também decorre desse modelo centrado em autonomia: agentes podem gerar uma grande quantidade de código, o que dificulta o entendimento e revisão por humanos. Em outras palavras, com agentes de código, costumam-se produzir código mais rapidamente do que a capacidade humana de compreender esse código, gerando o que chamamos de dívida cognitiva.
10.3.2 Arquivo de Configuração 🔗
Para configurar um agente com regras e políticas específicas de um
projeto, desenvolvedores devem criar um arquivo, no formato markdown,
chamado agents.md. Esse arquivo pode ser usado para
descrever a arquitetura do sistema criado pelo, agente, incluindo
módulos e interfaces. Um exemplo é mostrado no trecho a seguir (extraído
do projeto evstack/ev-node):
## Code Architecture
### Core Package Structure
The project uses a zero-dependency core package pattern:
- core/ - Contains only interfaces and types, no external dependencies
- block/ - Block management, creation, validation, and synchronization
- p2p/ - Networking layer built on libp2p
...
### Key Interfaces
- Executor (core/executor.go) - Handles state transitions
- Sequencer (core/sequencer.go) - Orders transactions
- DA (core/da.go) - Data availability layer abstraction
Em arquivos agents.md, pode-se também documentar
ferramentas específicas que o agente pode chamar, tal como no exemplo a
seguir (extraído do projeto PrefectHQ/marvin:
### Finding Things
- Use `rg` for searching, not grep
...
Como um último exemplo, pode-se também documentar práticas de teste,
como no seguinte exemplo (extraído de
lightdash/lightdash):
## Testing
**Test Structure:**
- Unit tests for core utilities
- Integration tests for dbt operations
- Mock data for warehouse connections
O arquivo agents.md é carregado em todas as sessões. Por
isso, ele deve incluir somente regras cuja ausência possa levar a erros
do modelo. Arquivos muito grandes e detalhados podem ser parcialmente
ignorados pelos modelos. Por isso, recomenda-se analisar periodicamente
esses arquivos, removendo tudo o que não for indispensável. Por exemplo,
uma regra como Siga o guia de estilo PEP 8 costuma ser
desnecessária. O motivo é que LLMs, em geral, já geram código Python
aderente a essa convenção por padrão; incluí-la costuma ser redundante e
não justifica ocupar espaço na memória do agente.
Literatura Científica: Em 2026, junto com Helio
Santos, Vitor Costa e João Eduardo Montandon, realizamos um dos
primeiros estudos sobre a configuração de agentes de código (link). Para isso, analisamos
328 arquivos claude.md (nome do arquivo inicialmente usado
para configurar o agente de código Claude Code). Então, concluímos que
as seções mais comuns desses arquivos tratam de arquitetura (presente em
72% dos arquivos), regras de desenvolvimento (45%), visão geral dos
projetos (39%) e testes (35%).
10.4 Boas Práticas para Escrita de Prompts 🔗
Evite prompts genéricos; para isso, sempre descreva a tarefa de forma específica, incluindo critérios claros de verificação, mensagens de erro ou sintomas observados, possíveis locais no código e referências a fontes relevantes. Sempre que possível, indique exemplos de entrada e saída esperadas e deixe explícito o que caracteriza uma solução correta, priorizando a correção da causa raiz do problema, e não apenas de seus sintomas.
Seguem então alguns exemplos de prompts para tarefas de manutenção de software. Mostramos um prompt que não segue as recomendações acima e depois um prompt que segue.
Correção de bugs:
Prompt ruim: o sistema está com um bug no carrinho de compras.
Prompt bom: “a totalização do valor do carrinho de compras está incorreta quando há múltiplos itens em um carrinho. Exemplo: 2×R$10 + 1×R$5 deveria ser R$25, mas está mostrando R$20. Verifique o cálculo em
CartService.calculateTotal. Escreva um teste que reproduza o bug e que depois possa ser usado para verificar sua correção.
Refatoração:
Prompt ruim: refatore a classe
ShoppingCartPrompt bom: refatore o método
calculateTotalemCartService, que está longo e com múltiplas responsabilidades. Por exemplo, extraia funções para cálculo de subtotal, descontos e frete. Elimine duplicações e garanta que os testes continuam passando.”
Implementação de Novas Funcionalidades:
Prompt: Implemente uma função para aplicar descontos percentuais em um carrinho de compras
Prompt bom: Implemente uma função
applyCoupon(Cart cart, String couponCode)que aplique desconto percentual no total de um carrinho de compras. Exemplo: cupom ‘10OFF’ → 10% de desconto. Se for informado um cupom inválido, não alterar o total. Inclua testes para cupons válidos e inválidos.”
Atualização de recursos de linguagens de programação:
Prompt ruim: “passe a usar o nome recurso de
Optionalde Java.”Prompt bom: “refatore o sistema para substituir verificações manuais de
nullporOptionalnos pontos em que um valor pode estar ausente. Revise especialmente operações de busca e recuperação de dados em módulos como catálogo de produtos, carrinho, checkout, pagamentos, cupons e entregas, em métodos comofindProductById,findCouponByCode,findOrderByNumber,findPaymentByIde similares. Elimine potenciaisNullPointerException, torne explícitos os casos de ausência de valor e preserve o comportamento atual do sistema. Adapte os testes existentes e crie testes cobrindo tanto cenários em que os dados existem quanto cenários em que estão ausentes.”
10.5 Integração com Serviços Externos 🔗
(em brveve)
10.6 Documentação para Agentes de Código 🔗
Quando tratamos de comentários no Capítulo 2, mencionamos que eles
podem ser escritos tendo diferentes leitores em mente: (1)
desenvolvedores que vão usar ou chamar seu código (esses comentários são
chamados de comentários públicos ou de referência); (2) desenvolvedores
que vão manter seu código (esses comentários são chamados de comentários
privados ou inline). No entanto, agora devemos ter um terceiro
leitor
em mente: agentes de código que vão inserir chamadas para
sua API no código que eles estão gerando de forma automática.
Para ilustrar, suponha que você está desenvolvendo uma API para prover dados sobre o mercado financeiro, tais como cotações de moedas e de ações. Como sua API é um serviço pago, pretende-se que ela seja amplamente usada por terceiros, incluindo aplicações de bancos, corretoras e de agências de notícias. Porém, cada vez mais, essas empresas estão usando agentes de código para gerar suas aplicações. Consequentemente, você deve escrever a documentação de sua API considerando não apenas que ela será consumida por desenvolvedores humanos, mas também por agentes de código baseados em LLMs. A seguir listamos algumas recomendações para documentação de APIs visando esse novo uso:
1. Deve-se disponibilizar a documentação também em markdown, que é um formato que LLMs processam melhor. Por exemplo, atualmente, algumas documentações de APIs já incluem um link para mostrar o mesmo conteúdo em markdown.
2. Deve-se incluir exemplos de código para cada endpoint da API. Na verdade, exemplos já são recomendados quando a documentação é escrita para consulta de humanos. Mas eles se tornaram mais importantes quando agentes de código também passam a utilizar essa documentação.
3. Em vez de documentar um endpoint apenas como seu propósito, deve-se também descrever quando ele deve ser usado. Por exemplo, não se deve usar uma frase simples e resumida como a seguinte:
GET /quotes/currency
Retorna a cotação de uma moeda.
Em vez disso, deve-se dar contexto sobre quando o endpoint deve ser usado (e quando ele não deve ser usado), como abaixo:
GET /quotes/currency
Retorna a cotação atual de um par de moedas (ex: USD/BRL).
Use quando o usuário precisar converter valores entre moedas em tempo
real ou exibir o câmbio vigente. Não use para análises de períodos
passados, prefira /quotes/history nesses casos.
4. No estágio atual da tecnologia, algumas organizações também
incluem recomendações que ajudam LLMs a usar suas APIs de forma mais
eficiente em um arquivo chamado llms.txt. Por exemplo,
atualmente, a Stripe, um conhecido serviço de pagamento, disponibiliza
em sua página raiz um arquivo llms.txt com a seguinte
seção:
## Instructions for Large Language Model Agents: Best Practices for
integrating Stripe
As a Large Language Model (LLM) Agent, when designing an integration,
always prefer the documentation in [links for the key API docs].
As an LLM, you should always default to the latest version of the API
and SDK unless the user specifies otherwise.
Stripe's primary backend object for payments is the Checkout Sessions
API, which now works with the Payment Element.
Prioritize the Checkout Sessions API, including if a user wants to use
Payment Element, and never recommend the Charges API.
...
Esse documento contém instruções para orientar agentes baseados em LLMs sobre como se integrar com a API da Stripe. Em particular, ele orienta que os agentes devem consultar a documentação oficial nos links indicados, usar sempre a versão mais recente da API e dos SDKs e priorizar o uso de uma determinada API (Checkout Sessions).