Meu jeito de desenvolver software rapidamente
(evanhahn.com)- Equilibrar perfeição e velocidade não é fácil, mas é importante buscar a qualidade adequada para o contexto e cumprir os prazos
- Uma abordagem eficaz é fazer primeiro um rascunho inicial e depois melhorar a qualidade do código
- Flexibilizar requisitos ou reduzir exigências excessivas pode aumentar a velocidade e a eficiência
- É preciso criar o hábito de evitar distrações, fazer commits frequentes em pequenas unidades e manter o foco no essencial
- Há habilidades concretas que ajudam no desenvolvimento rápido, como leitura de código, modelagem de dados, scripting, depuração e preferência por funções puras
“Quão bom o código precisa ser?” – Critérios de qualidade e escolhas realistas
- No começo, eu queria que todo código fosse perfeito
- Sonhava com código em que todas as funções fossem testadas com rigor, os nomes de variáveis fossem elegantes, as abstrações claras e sem nenhum bug
- Mas, com o tempo, aprendi a realidade de que “não existe resposta certa”
- A qualidade exigida do código varia conforme o contexto
- Game jam de 24 horas: o código final não precisa necessariamente ser limpo nem livre de bugs
- O mais importante é entregar algo funcional dentro do tempo limitado
- Software de marca-passo cardíaco: um único erro pode ameaçar a vida de alguém
- Por isso, o mais alto nível de confiabilidade e segurança é indispensável
- A maioria dos projetos fica entre esses dois extremos
- Algumas empresas exigem entregas rápidas e toleram alguns bugs
- Alguns projetos exigem alta qualidade, mas têm prazos folgados
- No trabalho real, é importante saber identificar esse equilíbrio
- Primeiro, entenda qual é o padrão de “bom o suficiente” da equipe
- Vale alinhar critérios práticos, como o nível de bugs aceitável e quais partes podem não estar perfeitas
- Meu critério pessoal é
- “Atingir qualidade 8/10 dentro do prazo”
- O código cumpre bem seu objetivo, não tem problemas críticos, mas ainda pode restar alguma questão menor
- O mais importante é entregar dentro do prazo
- Claro, esse critério também é ajustado com flexibilidade conforme o contexto do projeto
- Às vezes vale buscar a perfeição mesmo que o cronograma atrase
- Em outras, é mais valioso terminar rápido mesmo com menor grau de acabamento
- “Atingir qualidade 8/10 dentro do prazo”
Rough drafts – Uso prático e vantagens de rascunhos e prototipagem
- Assim como na escrita, no desenvolvimento de software também é muito útil criar um rascunho inicial (rough draft, spike, walking skeleton)
- Eu implemento esse rascunho o mais rápido possível e depois o refino até transformá-lo em uma solução final
- Meu código de rascunho costuma estar cheio de bugs: testes falhando, comentários TODO por toda parte, exceções sem tratamento, uso excessivo de print/log, sem preocupação com performance, mensagens de commit WIP, pacotes desnecessários, código repetido, hardcode, avisos do linter e mais um monte de bagunça
- Isso pode parecer ineficiente, mas o objetivo é chegar ao menos a um estado em que seja possível entender a essência do problema
- Naturalmente, esse código em estado de rascunho não vai para produção; antes de qualquer deploy real, ele é obrigatoriamente refinado
(às vezes a equipe pressiona para enviar o rascunho como está, mas eu tento resistir ao máximo) -
Principais vantagens da abordagem de rascunho
- Revela rapidamente os “unknown unknowns”
- É muito melhor descobrir obstáculos desconhecidos ainda na fase de protótipo do que depois de ter escrito código que será descartado
- Muitos problemas desaparecem naturalmente durante a prototipagem
- Funções lentas ou estruturas erradas muitas vezes deixam de ser necessárias mais tarde, evitando desperdício de tempo
- Não é preciso investir cedo demais em otimização ou testes
- Aumenta o foco
- Evita distrações como refatorações desnecessárias, obsessão com nomes ou correções em outros codebases
e permite mergulhar apenas no problema atual
- Evita distrações como refatorações desnecessárias, obsessão com nomes ou correções em outros codebases
- Evita abstrações prematuras desnecessárias
- Ao tentar produzir rapidamente uma solução funcional, você tende menos a criar abstrações futuras que ainda não são necessárias
- Isso ajuda a focar apenas no problema imediato e evitar designs complexos sem necessidade
- Torna o progresso mais fácil de comunicar
- O rascunho permite prever com mais precisão quanto trabalho ainda falta
- Mostrar primeiro algo funcionando acelera feedbacks dos stakeholders e mudanças de direção
- Revela rapidamente os “unknown unknowns”
-
Como operar esse método na prática
- Decisões difíceis de reverter (binding decisions) devem ser testadas ainda na fase de rascunho
- Ex.: linguagem, framework, schema do banco de dados e outras definições estruturais devem ser verificadas cedo
- Todo paliativo/hack deve ser registrado com comentários TODO ou equivalente
- Na fase de polish, faço uma varredura completa com
git grep TODOe similares para revisar tudo
- Na fase de polish, faço uma varredura completa com
- Desenvolva em ordem top-down
- Comece pelo scaffold da forma de uso, como UI e API; a lógica interna pode ser hardcoded ou temporária
- Na prática, a lógica de baixo nível muitas vezes muda conforme a UI e a experiência de uso se definem, então começar pelas camadas superiores costuma ser vantajoso
- Fazer toda a base perfeitamente primeiro e só depois ajustar o topo costuma ser ineficiente
- Separe mudanças pequenas em patches distintos
- Se durante o rascunho você descobrir a necessidade de melhorar o codebase ou atualizar dependências,
separe essa parte em um PR/commit próprio e aplique rapidamente - Isso reduz a complexidade da mudança total e acelera revisão e integração
- Se durante o rascunho você descobrir a necessidade de melhorar o codebase ou atualizar dependências,
- Decisões difíceis de reverter (binding decisions) devem ser testadas ainda na fase de rascunho
Referência: “Jogue fora o primeiro rascunho do seu código”, “O melhor sistema simples por agora”, “YAGNI(You Aren’t Gonna Need It)”
Tentando mudar os requisitos
- O texto enfatiza o princípio de que fazer menos é mais rápido e mais fácil
- No trabalho real, eu sempre penso se dá para flexibilizar os requisitos da tarefa recebida
- Perguntas de exemplo:
- Dá para juntar várias telas em uma só?
- Precisamos mesmo tratar todos os edge cases mais complicados?
- Se o sistema deve suportar 1000 entradas, será que 10 já não bastam?
- Dá para substituir a versão final por um protótipo?
- Será que essa funcionalidade pode simplesmente ser removida?
- Perguntas de exemplo:
- Essa abordagem aumenta a velocidade e a eficiência do desenvolvimento
- Também tento levar a cultura da organização, aos poucos, para um ritmo mais calmo e racional
- Mudanças bruscas e grandes exigências normalmente não funcionam bem
- Dá para mudar o clima gradualmente com sugestões incrementais e ajustando a forma das discussões
Evitando distrações no código
- Não é só o ambiente externo (notificações, reuniões) que atrapalha; desviar para tarefas aleatórias durante o trabalho no código também é um grande problema
- Eu mesmo, às vezes, começo corrigindo um bug e acabo mexendo em algo totalmente sem relação, enquanto a tarefa original fica adiada
- Duas práticas concretas:
- Definir um timer: estabelecer um limite de tempo para cada tarefa e, quando o alarme tocar, revisar o estado atual
- Isso ajuda a perceber quando algo está levando mais tempo do que o esperado
- Fazer um git commit junto com o alarme também dá uma pequena sensação de progresso
- (Esse método também ajuda a treinar estimativa de tempo)
- Pair programming: trabalhar em dupla reduz desvios desnecessários e ajuda a manter a concentração
- Definir um timer: estabelecer um limite de tempo para cada tarefa e, quando o alarme tocar, revisar o estado atual
- Para alguns desenvolvedores, evitar esse tipo de distração é natural, mas para mim exige foco consciente e hábito
Mudanças pequenas, dividir em partes menores
- No passado, tive um chefe que incentivava patches grandes e mudanças amplas,
mas na prática percebi que isso era muito ineficiente - Tenho a sensação de que diffs pequenos e focados quase sempre são melhores
- Exigem menos esforço para escrever
- Tornam o code review mais fácil e rápido, reduzem o desgaste dos colegas e facilitam identificar meus próprios erros
- Quando surge um problema, o rollback é mais simples e seguro
- Como o escopo de cada alteração é pequeno, o risco de introduzir bugs novos também cai
- Até funcionalidades grandes são concluídas como acúmulo de mudanças pequenas
- Ex.: se for preciso adicionar uma tela, correções de bug, upgrade de dependências e implementação da funcionalidade podem ser separados em patches distintos
- O texto reforça que mudanças pequenas ajudam a desenvolver software com mais velocidade e qualidade
Habilidades concretas que realmente ajudaram no desenvolvimento rápido
O que foi dito acima é um pouco abstrato, mas também existem habilidades práticas que realmente aceleram o desenvolvimento
-
Leitura de código (Reading code): é a habilidade mais importante que adquiri como desenvolvedor
- Se você sabe interpretar bem código existente, o debugging fica muito mais fácil
- Bugs e documentação insuficiente em bibliotecas open source/de terceiros deixam de assustar tanto
- Aprende-se muito lendo o código de outras pessoas, e isso ajuda diretamente na capacidade geral de resolver problemas
-
Modelagem de dados (Data modeling): mesmo que leve tempo, é importante projetar corretamente o modelo de dados
- Um schema de banco de dados mal desenhado causa vários problemas no futuro e torna correções muito mais caras
- Projetar de forma que estados inválidos nem possam ser representados reduz bugs pela raiz
- Quando os dados são armazenados ou trocados com sistemas externos, esse cuidado é ainda mais importante
-
Scripting: a capacidade de escrever scripts curtos rapidamente em Bash, Python e afins maximiza a eficiência de desenvolvimento
- Uso isso várias vezes por semana em automações como ordenar Markdown, limpar dados e encontrar arquivos duplicados
- No Bash, ferramentas como Shellcheck ajudam a evitar erros de sintaxe com antecedência
- Em tarefas que não precisam ser robustas, dá para terminar mais rápido com ajuda de LLMs
-
Uso de debuggers: usar debugger é essencial para diagnosticar problemas rapidamente e entender o fluxo do código, algo que print/log sozinho não consegue oferecer
- A causa raiz de bugs complexos aparece muito mais rápido
-
Saber a hora certa de descansar: criar o hábito de fazer uma pausa sem hesitar quando estiver travado
- É comum sofrer por horas com um problema e resolvê-lo logo após 5 minutos de descanso
- Isso também é importante para manter a eficiência da concentração
-
Preferência por funções puras e dados imutáveis: programação funcional: ao preferir funções puras e immutable data,
- há menos bugs, menos carga para rastrear estado e mais clareza/previsibilidade no código
- muitas vezes isso é mais simples e eficaz do que projetar hierarquias complexas de classes
- Não é sempre possível, claro, mas é a abordagem que considero primeiro por padrão
-
Uso de LLMs (modelos de linguagem de grande porte): LLMs (ex.: ChatGPT etc.) têm desvantagens, mas aumentam muito a velocidade em tarefas de desenvolvimento repetitivas ou automatizáveis
- Depois de entender bem como aplicar LLMs ao meu código e quais são seus limites, passei a usá-los ativamente
- Também acompanho experiências, dicas e casos práticos da comunidade
Todas essas habilidades foram treinadas repetidamente ao longo de muito tempo e se tornaram um grande patrimônio para desenvolver com rapidez
Resumo
- As principais lições que aprendi ao desenvolver software rapidamente são as seguintes
- Entender com clareza o nível de qualidade de código exigido em cada tarefa
- Escrever rapidamente um rough draft (rascunho) para definir a visão geral
- Sempre explorar espaço para flexibilizar requisitos
- Manter o foco sem se deixar levar por distrações
- Fazer mudanças pequenas e frequentes, evitando patches grandes
- Praticar continuamente habilidades concretas (leitura de código, modelagem de dados, debugging, scripting etc.)
- Tudo isso parece óbvio demais, mas na prática levei muito tempo para chegar a essas lições
2 comentários
Tem muita coisa com a qual dá para se identificar aí.
Os comentários também são bons, mas quando alguém organiza isso e coloca a discussão na mesa, tenho a sensação de que o resultado fica mais completo, passando por contrapontos, apoio e complementos.
P.S.: Tenho visto com frequência ultimamente a expressão "tecnologia entediante", que em inglês é boring technology.
Opinião no Hacker News
Nos últimos anos, aprendi a construir sistemas rápidos e suficientemente robustos
Aprendi que é importante dominar profundamente uma ferramenta. Algo que eu conheço bem é muito mais eficiente do que uma ferramenta que parece mais adequada só na superfície. Na prática, em muitos projetos, Django acaba sendo a escolha certa
Às vezes comecei projetos preocupado se Django não seria pesado demais, mas no fim eles cresceram muito além da intenção inicial. Por exemplo, criei um app de página de status e logo percebi que tentar contornar as limitações do Django era ineficiente
Na maioria dos apps que se encaixam no modelo do Django, o modelo de dados é o núcleo. Mesmo em protótipos, adiar a refatoração do modelo de dados faz com que custo e dificuldade cresçam exponencialmente depois
A maioria dos apps não precisa de SPA nem de frameworks pesados de frontend. Alguns podem precisar, mas em 80% das páginas, views tradicionais do Django bastam. Para o restante, vale considerar AlpineJS ou HTMX
Na maioria dos casos, desenvolver por conta própria é mais fácil. Dá para criar rapidamente com Django funções como CRM, página de status, sistema de suporte, processo comercial etc. É muito mais rápido do que integrar um CRM comercial
Escolha tecnologias tão comuns que cheguem a ser sem graça. A combinação Python/Django/Postgres resolve quase tudo. Dá para esquecer Kubernetes, Redis, RabbitMQ, Celery etc. Alpine/HTMX são exceções, porque permitem evitar a maior parte da stack JS
Para mim, Redis e Kubernetes são as “tecnologias sem graça” de 2025. Ambos são extremamente estáveis, têm casos de uso bem definidos, e suas desvantagens já são bem conhecidas, então passam muita confiança. Pessoalmente, sou fã dos dois. Confio bastante porque fazem exatamente o que eu quero
Eu também gosto muito de Django. Dá para começar e colocar um projeto em produção muito rápido
Se for mesmo escolher “tecnologia sem graça”, talvez valha repensar até mesmo o Postgres
Eu uso Celery com bastante frequência em projetos Django. Não gosto da complexidade, mas em ambientes PaaS ele acaba sendo a opção menos dolorosa
A afirmação de que “a maioria dos apps não precisa de SPA ou de frameworks pesados de frontend” parece entrar em conflito com o conselho de “domine profundamente uma única ferramenta”
Quando você deixa código como rascunho bruto, com frequência a gerência acaba publicando aquilo como “versão final”
Por isso, eu já escrevo código robusto desde o começo. Até o harness de testes eu costumo fazer quase no nível de produção
O ponto principal é criar módulos de altíssima qualidade. Partes com chance muito baixa de mudança, ou cuja mudança causaria problemas enormes, eu isolo em módulos independentes e importo como dependências
Graças a esses módulos, consigo desenvolver novos apps muito rápido e manter a qualidade sempre alta
Exemplos que já usei diretamente incluem RVS_Checkbox, ambiamara, RVS_Generic_Swift_Toolbox etc.
Tenho uma pergunta: em Swift, usar um padrão de comentário como "* ##################################################################" é algo padrão?
A abordagem muda bastante conforme o tamanho do projeto
Em projetos pessoais ou equipes pequenas, desenvolver “rápido e sem muito refinamento” costuma ser o ideal. Essa é justamente a força do desenvolvimento em pequena escala
Em contextos pequenos, mesmo que apareçam bugs, dá para corrigir rápido, e todos na equipe entendem quase perfeitamente o código inteiro
Quando a escala aumenta, o custo de erros de arquitetura ou de correção de bugs explode. A arquitetura inevitavelmente fica complexa, e grandes refatorações se tornam praticamente impossíveis. Nesse ambiente, a prioridade máxima precisa ser acertar passo a passo
Contexto é realmente importante. “Grande escala” pode significar coisas diferentes, mas pela minha experiência, sempre foi correto alinhar cedo as APIs entre apps para que frontend e backend pudessem trabalhar rápido
Nesses casos, é preciso reduzir o sistema. Todo mundo quer um sistema enorme, mas na prática quase nunca precisa
Existe a fala de que “em game jams de 24 horas não vale a pena se preocupar com qualidade de código”, mas, pela maior parte das hackathons e code reviews que vivi, as equipes com melhor desempenho também cuidavam da qualidade do código e até de um ambiente rudimentar de testes
Essas duas afirmações (para ir rápido é preciso abrir mão da qualidade do código vs. equipes com melhor resultado tendem a ter mais qualidade) na verdade não se contradizem. Equipes boas não necessariamente ficaram obcecadas só com o capricho do código
No caso de game jams, se você se apegar demais à limpeza do código, o resultado final pode até piorar. Sistemas como UE blueprint mostram por que às vezes o resultado deve vir antes da “elegância”
Algumas pessoas avaliam a “limpeza” do código de forma geral, enquanto outras avaliam o custo-benefício detalhado de melhorias de código desnecessárias
Ao contrário da ideia de que “ao prototipar surgem unknown unknowns inesperados”, quando mexo em algo pela primeira vez eu quase sempre só enxergo os pontos positivos e não percebo bem os negativos
Na prática, os problemas de verdade (os unknown unknowns) só aparecem na etapa de completar de fato a funcionalidade: tratar edge cases, mensagens de erro amigáveis ao usuário, remoção de efeitos colaterais etc.
Talvez os unknown unknowns que eu enfrento venham da própria ferramenta/framework/biblioteca, enquanto o autor esteja falando de unknown unknowns do próprio domínio do problema
Também concordo que um rough draft não pode ser bruto demais. Se você relaxa justamente nas partes em que não deveria, os problemas reais explodem depois.
Quando estou criando uma ferramenta para meu próprio uso, dá para fazer algo mais improvisado e ainda assim usar tranquilamente; mesmo cheia de falhas, essa ferramenta feita rápido muitas vezes não chega a causar problemas
No cenário atual, com tantas reestruturações, a indústria de tecnologia é a maior ameaça à qualidade de software e à produtividade de engenharia
O medo de demissão e a pressão por resultados rápidos matam a criatividade e o espírito de experimentação, além de causarem burnout
Todo mundo acaba sendo arrastado pela mentalidade de manada em torno de modas como IA, e o ambiente vira um lugar onde nem se consegue criticar isso
É um problema mais urgente do que codificação automática com LLM
A maior ameaça à qualidade de software sempre foi o fato de os consumidores não pagarem por qualidade
O vendor lock-in no nível da programação é, na prática, muito mais destrutivo do que o lock-in de SaaS
Em ciclos rápidos como game jams de 24 horas, sinto que código ruim é ainda mais fatal
Quanto mais limpo o código, menos erros acontecem, menor é a carga sobre a memória de trabalho, e muito mais fácil fica fazer mudanças desejadas, adicionar recursos ou corrigir problemas no final
O que mais costuma arruinar projetos de 24 horas não é escrever código devagar, mas sim se encurralar ou sair dos trilhos por causa de problemas imprevisíveis
Isso não quer dizer que seja preciso corrigir todos os bugs. Mas, quando a qualidade básica é baixa, a experiência do projeto como um todo fica muito mais difícil
Esse princípio também vale para projetos com mais tempo. Ter mais tempo não significa que escrever de qualquer jeito seja melhor
Se você transforma escrever bom código em hábito, consegue garantir qualidade sem custo adicional. E, mesmo quando custa mais tempo, ainda assim vale a pena
Penso da mesma forma. Já participei de várias game jams, e “código bagunçado” só é aceitável nas últimas 1 ou 2 horas antes do prazo, em arquivos que ninguém mais vai mexer
Para escrever código rápido e bom, no fim a resposta é simples: escrever muito
Quando a urgência aperta, não fico me preocupando com algo sofisticado como um asset loader; resolvo com arquivos estáticos mesmo
Acho equivocado esse entendimento de que “escrever bom código leva mais tempo”. Para atingir um certo nível mínimo de exigência, bom código não é um obstáculo à velocidade
O critério de “o que é good enough” varia muito de equipe para equipe, e isso foi a maior fonte de conflito na minha carreira
Pessoas vindas de big tech reclamam da falta de testes, enquanto quem vem de startup reclama da lentidão
Seria útil deixar por escrito e compartilhar com a equipe um critério claro do que significa “bom o suficiente”
Isso é exatamente um team charter, ou seja, um documento sobre “como trabalhamos”
Um fator importante que o texto não menciona é a queda da velocidade de desenvolvimento ao longo do tempo
Conforme o projeto e a equipe crescem, a velocidade de desenvolvimento naturalmente diminui
Ou seja, mesmo aceitando uma pequena perda de velocidade imediata, é preciso preparar desde cedo testes, documentação, logs de decisão, reuniões de Agile etc. para que a desaceleração no longo prazo seja menor
Se você não prepara antes coisas como observability ou uma estrutura de código fácil de testar, o impacto negativo depois é enorme
Sou desenvolvedor solo, mas sinto na prática a importância de três coisas: log de decisões, testes e documentação
É um padrão familiar para mim também. Começo com um rough draft, ou com um pequeno código que junta outra linguagem de script ou execuções manuais para validar uma ideia