← Todos os textos

Do Google glorificado ao agente: por que resisti à IA

Um pouco do meu passado

A década de 2020 foi uma década atípica no mundo do desenvolvimento. Ela já começou com a pandemia, que fez os trabalhos ficarem, em sua quase totalidade, remotos, levando os escritórios para dentro das próprias casas. Com isso algumas tecnologias aumentaram sua necessidade e sua complexidade. Já estávamos vindo do boom do Machine Learning junto com o RPA, e assim o cenário das necessidades das empresas mudou completamente. Em 2020 foi publicamente lançado o GPT-3 para desenvolvedores (GPT-1 e BERT já existiam desde 2018, porém apenas usados em pesquisa e em ambientes fechados) e isso começou a mudar o fluxo de trabalho, ainda como um auxílio apenas (quem aguenta ficar fazendo mais um CRUD padrão, criar mais um caso de teste igual aos outros 20 anteriores, documentações, pesquisar sintaxe…), mas já começou a mudar como você pensa em desenvolvimento.

Quando o assunto é tecnologia, estamos carecas de saber que ela anda em passos gigantes, onde acompanhar é um trabalho 24/7. Eu tenho mais de 20 anos de desenvolvimento nas costas, tentando entender como a máquina funciona, estudando desde o hardware até o software. Também tive a sorte de acompanhar algumas guerras e melhorias, como as evoluções das arquiteturas 8086 para 80286 para 80386; a guerra da Intel contra a AMD; processadores de 32 bits virando 64 bits do dia para a noite; o Direct3D contra o OpenGL; a chegada da Internet no Brasil, isso apenas para citar algumas que foram importantes. Todas essas foram rápidas, porém nenhuma tão rápida quanto o que as LLMs e o uso delas mudaram em uma área inteira de informática. Ok, talvez o advento da Internet e a sua popularização sejam tão importantes quanto.

Alguém consegue ainda se lembrar de como era o processo de desenvolvimento antes da Internet? Antes de um Stack Overflow? E atualmente, antes de um ChatGPT, um Claude, um Gemini, Grok ou outras LLMs? Eu vejo com alunos de graduação o quão dependentes eles ficaram de uma LLM ou outra, exatamente como acontecia quando começou o Stack Overflow. O que antes era pesquisa na biblioteca de uma faculdade virou pesquisa na Internet, que virou busca no Stack Overflow, que virou busca em uma LLM.

O avanço das LLMs, apesar de parecer que foi há muito tempo, começou publicamente apenas em 2020. Em torno de 6 anos saímos de uma IA que mais parecia um Google glorificado para um GitHub Copilot com integração direta no seu VSCode (ou outra IDE), te ajudando a desenvolver, entendendo o seu padrão de desenvolvimento (e errando muito no começo), e juntando com a base de dados treinada do próprio GitHub te entregando o próximo bloco de código. Depois ele começou a te ajudar a entender como funciona uma parte de um código que você não entendeu. Hoje, ele já está em um ponto onde podemos falar com uma IA, ela vai gerar o código, e, se quiser, você vai validar apenas a funcionalidade, e não o código em si.

Viramos nossa própria startup (caso você consiga pagar o preço, obviamente).

A barreira

Eu vou ser bem sincero, eu fui muito resistente ao uso da IA Generativa no começo, principalmente de agentes de IA para desenvolvimento. Eu usei desde o começo como um Google glorificado, depois como um Stack Overflow glorificado, depois para desenvolvimentos repetitivos (CRUDs, testes, etc), depois para documentações. E aqui eu coloquei uma barreira por um bom tempo. Ninguém gosta de fazer uma documentação, mas uma documentação bem feita chega a ser uma arte. Eu sempre tive uma frase que gostava de falar quando ia ensinar Assembly, C, C++ ou qualquer outra coisa em ambiente Linux: Alguém escreveu o manual da função, basta você ler, aproveite. Ao usar uma IA para criar uma documentação, eu percebi a mecanização que a IA tem para criar isso, e isso me fez erguer uma barreira, afinal, não era minha escrita, minhas palavras, meu documento. Eram da IA, e eram extremamente mecânicos e repetitivos.

O AI Slop

Quando cheguei nesse ponto de trava, comecei a fazer alguns exercícios para analisar o famoso AI Slop nos códigos que eram gerados. Entenda que até aqui eu usava todas as IAs Generativas de forma igualitária, nenhuma se sobressaía em relação às outras, pois meu uso era bem simples, então elas não mostravam todo o seu valor. Não estou aqui para falar melhor ou pior de uma ou outra IA Generativa, não é esse o foco do que quero contar, mas analisando os códigos gerados, principalmente quando eu começava com “Cria um método que faça X da forma Y”, o resultado tinha alguns problemas bem claros:

  1. Códigos extremamente complexos para tarefas simples.
  2. Arquiteturas complexas o suficiente para deixar qualquer arquiteto com os cabelos em pé.
  3. Códigos com bibliotecas antigas.
  4. Códigos que simplesmente não funcionavam.

Encontrar um exemplo hoje em dia de AI Slop é mais difícil do que antigamente, mas analise este código abaixo em Python:

Prompt

Crie um método genérico em Python que seja capaz de identificar se um número é par ou impar, utilize as bibliotecas mais comuns para isso caso elas existam.

Código gerado

from numbers import Integral
import numpy as np


def is_even(value):
    # Integral cobre int, bool e np.integer sem importar o tipo concreto.
    if isinstance(value, Integral):
        return value % 2 == 0

    # Fallback vetorizado: list, tuple, ndarray, Series.
    array = np.asarray(value)
    if not np.issubdtype(array.dtype, np.integer):
        # 4.0 e 4.5 caem aqui: paridade so esta definida para inteiros.
        raise TypeError(f"is_even exige inteiros, recebeu dtype {array.dtype}")
    return array % 2 == 0


def is_odd(value):
    return ~is_even(value) if not isinstance(value, Integral) else not is_even(value)

Funciona? Sem dúvida nenhuma, mas por que não fazer assim:

def is_even(n: int) -> bool:
    return n % 2 == 0

Sendo totalmente transparente aqui, eu demorei um pouco para gerar um AI Slop com o Claude, e até mesmo ele me avisou de que talvez essa não fosse a melhor forma. Antigamente ele só iria me dar esse código, ele iria passar em todos os testes, mas qual o real problema dele?

  1. Complexidade, dar suporte a esse código é próximo do impossível, exceto por uma IA.
  2. Memória, se eu não estiver utilizando NumPy, por que carregar uma biblioteca inteira para um if?

O interessante dessa parte é entender de onde as LLMs geram esse tipo de código. Acho que todos que estão lendo aqui sabem que a LLM funciona por estatística, mas estatística do que? Basicamente:

  • Documentações públicas
  • Códigos públicos (GitHub e outros repositórios públicos)
  • Fóruns (Stack Overflow e cia)
  • Livros

Aqui vale a pena citar algumas fontes:

Quem tem uma certa idade em desenvolvimento lembra do livro de C “C Completo e Total” por Herbert Schildt? O livro era muito bem escrito, porém os exemplos de códigos possuem vícios horrendos:

  • Usava void main() no lugar de int main(). Com void main() o SO nunca sabe se o programa terminou corretamente ou não.
  • Ignorava o padrão ANSI.
  • Utilizava (e muito) funções inseguras, como gets().
  • Manipulava ponteiros de forma incorreta.
  • Escrevia macros erradas, dentre outros.

E esse tipo de conteúdo está presente no treinamento das LLMs. Conseguir analisar esse tipo de código é, no mínimo, desafiador até para o melhor desenvolvedor daquela linguagem. Eu vi isso e pensei: Ok, isso será bom algum dia, mas vai demorar muito para que eu realmente use isso dessa forma… Ah, se eu soubesse que em menos de 6 meses eu começaria a mudar meu pensamento sobre isso, e que isso aconteceria em dois cenários diferentes.

O que mudou

Um dos cenários que me fez pensar um pouco foi que ouvi em mais de uma situação, profissional ou não, que o tempo de desenvolvimento de uma feature, um bugfix, um PR ou até um projeto completo estava sempre aquém do esperado. Pessoas próximas, que nunca desenvolveram, passaram a dizer: Pode deixar que eu faço esse projeto do zero em 2 dias!. Isso sempre me pareceu absurdo, porque esses 2 dias tendem a virar meses de correção de AI Slop.

O outro cenário que me fez mudar definitivamente como penso foi um jantar de confraternização patrocinado por um parceiro da empresa onde trabalho. Nesse jantar eu tive a oportunidade de conversar com várias pessoas de inúmeros setores diferentes, e me chamou a atenção uma conversa sobre o uso de IAs Generativas, nesse caso, quem estava contando estava mostrando como usava o Claude para desenvolvimento, sem ter uma equipe dedicada de cientistas da computação. O ponto a que chegamos é que, naquele dia, já era possível (com um pouco de esforço inicial) configurar um sistema de skills e agentes de IA Generativa para que você não precise mais se preocupar com o código. Quem estava conversando comigo conseguiu me convencer a estudar novamente a IA Generativa para desenvolvimento.

Saí do jantar e no dia seguinte comecei a pesquisar qual IA Generativa era a mais utilizada para codificação. Resolvi testar o Claude Pro, e minha vida de desenvolvedor mudou, assim como a tecnologia muda, rápida e cruelmente.

Customização

Eu devo ter passado pelo menos 3 meses apenas entendendo o que o Claude podia fazer, não mais como um Google glorificado (ok, eu ainda uso ele para isso de vez em quando), mas mais como um funcionário especialista em tudo relacionado a desenvolvimento, e que eu posso customizar. Essa é a palavra da vez: customizar. Nas minhas primeiras 2 semanas com ele, minha conclusão ainda era a mesma: o código é complexo demais, a arquitetura é desnecessária em 90% dos casos que eu usava, e o código beira o ilegível. Pelo menos agora o código estava funcionando, e ele conseguia fazer pesquisas para saber quais bibliotecas usar e quais as últimas versões. Eu ainda precisava de algumas iterações para chegar ao resultado correto, seguindo o exemplo do AI Slop anterior, eu continuaria a mesma sessão com:

Remova a dependência do NumPy e resolve o problema de uma forma simples, que todos possam entender e utilizar

Resultando em:

from operator import index

def is_even(n):
    # index() aceita qualquer tipo inteiro (int, bool, np.int64) e levanta TypeError em float, que nao tem paridade definida.
    return index(n) % 2 == 0


def is_odd(n):
    return not is_even(n)

Ainda complexo, mas muito mais simples (secretamente eu ainda prefiro o de apenas uma linha, e iria usá-lo nesse cenário hipotético).

Depois comecei a treinar o Claude na forma como eu gosto de fazer as coisas, customizei ele para falar comigo de uma forma que eu entenda, para priorizar uma sintaxe no lugar de outra, para sempre pensar em simplificar a arquitetura, para gerar códigos mais simples, etc.

Uma parte do meu CLAUDE.md que me ajudou a mudar como eu penso nele:

# Instructions
You are not my assistant. You are my advisor who happens to be smarter than me. Follow these rules in every reply:
1. Never start with agreement. Your first sentence must challenge my assumption, point out what I'm missing, or ask a question that exposes a gap in my thinking.
2. Rate your confidence. Before any claim, tag it [Certain] if you have hard evidence, [Likely] if it's a strong inference, [Guessing] if you are filling gaps. If most of your reply is guessing, say so first.
3. Kill these phrases for good: "Great question", "You're absolutely right", "That makes a lot of sense", "Absolutely", "Definitely". If you catch yourself typing one, delete and rewrite.
4. Disagree with structure. When I'm wrong, say: "I disagree because [reason]. Here's what I'd do instead [alternative]. The risk in your approach is [specific downside]."
5. Give me the uncomfortable answer first. If there's a truth I probably don't want to hear, lead with it. First line, not buried in paragraph three.
6. No warm-up paragraphs. Skip "There are several ways to look at this". Start with the most useful thing you can say.
7. If I push back, don't fold. Hold your position unless I give you genuinely new information. "But I really think" is not new information.
8. If I start in a specific language, keep that language until the end.
9. When coding, if there's a public library (OSS preferably) that already does what is needed, always prefer it instead of writing the same thing. This can be challenged, so always look for my approval.

Não apenas fica mais fácil de identificar quando ele vai soltar algum AI Slop (normalmente é algo que é “tagueado” como [Likely]), mas também ele parou de falar que eu era O Senhor do Universo e estava sempre correto. Inclusive já me questionou definições de arquiteturas que realmente faziam sentido mudar.

Seguindo o mesmo exemplo anterior, a saída completa do prompt com o método para detectar um número par:

Uso:

is_even(4)                       # True
is_odd(7)                        # True
is_even(-3)                      # False
[is_even(x) for x in [1, 2, 3]]  # [False, True, False]
is_even(4.0)                     # TypeError

Testes (a função não tem if, então os dois resultados e o erro cobrem todos os caminhos):

import pytest

from parity import is_even, is_odd


def test_even_and_odd():
    assert is_even(4) is True
    assert is_even(7) is False
    assert is_odd(7) is True
    assert is_odd(4) is False


def test_negative_and_big():
    assert is_even(-3) is False  # Python: -3 % 2 == 1, sem sinal negativo como em C
    assert is_even(10**30) is True


def test_rejects_float():
    with pytest.raises(TypeError):
        is_even(4.0)

Como ler: is_even valida o tipo com index e aplica % 2. A invariante é que só inteiros chegam ao %.

Riscos:
- [Certain] is_even(True) retorna False, porque bool é subclasse de int. Aceito por simplicidade.
- [Certain] Rejeitar 4.0 é deliberado. Aceitar floats esconderia bugs, e 1e20 % 2 perde precisão sem avisar.
- [Likely] Se um dia precisar de arrays grandes, n & 1 em NumPy é o caminho. Isso seria uma função separada, não um ramo dentro desta.

Reparem como ele criou exatamente o que eu pedi, explicou como ler o método e apontou do que ele tem certeza e do que não tem. Entendam que o meu arquivo de instruções do Claude tem 115 linhas, e eu não estou escrevendo isso para mostrar “Olhe aqui, mais um arquivo perfeito de customização do Claude, use isso e seja feliz para sempre!”, esse tipo de coisa não existe, cada caso é um caso, cada uso é um uso.

E aos poucos ele começou a melhorar, a tal ponto que pensei: Por que não tentar utilizá-lo para me ensinar alguma coisa, que tal Rust?

Ok, fui longe demais nisso. Eu tentei, e tentei, e tentei, até que desisti. Não sei até hoje se foi porque não gostei de Rust (a sintaxe me atrapalhou muito), ou se eu pedi demais da IA.

Meu pensamento para isso, caso alguém tenha interesse, foi criar um programa que eu soubesse o que estava fazendo (com alguns algoritmos novos) e tentar recriá-lo em Rust. Pedi para o Claude gerar um passo a passo do que poderia ler para aprender Rust, sempre focando no algoritmo do programa original. Ou seja, existia um programa que era o oráculo com relação à saída, e uma arquitetura pensada em Python. A ideia era aprender alguma parte importante de Rust para implementar uma parte do programa, eu devolvia para o Claude, e ele me apontava os erros. No final das contas eu estava mais copiando o que a IA estava entregando e não estava aprendendo nada direito.

Resolvi abandonar o projeto de aprender Rust dessa forma. Eu (e essa é a minha opinião 100% pessoal) não gostei de Rust. Eu entendo a parte de segurança dele, realmente é excepcional, porém a sintaxe me pegou. Não me adaptei e não me forcei a me adaptar, quem sabe mais para a frente…

Mas mesmo assim, eu, cético, já estava utilizando IA para coisas bem mais complexas do que um Google glorificado. Eu estava utilizando agentes, e gostando do resultado.

Eu estava usando IA Generativa para desenvolvimento, estava sendo útil e eu estava gostando.

E o que aconteceu quando joguei nela o código do meu mestrado, escrito em 2011? Fica para o próximo post.

E você, já teve uma resistência parecida? O que te fez mudar, ou o que ainda te segura?