- A complexidade é o elemento mais perigoso no desenvolvimento
- A verdadeira eficiência vem de uma abordagem pragmática que evita a complexidade, como a "solução 80/20"
- É importante manter uma postura equilibrada e flexível em relação a testes e refatoração
- Enfatiza o uso de ferramentas e a adoção do hábito de escrever código fácil de ler e manter
- Alerta contra abstração excessiva e modismos, recomendando uma postura voltada à simplicidade
Introdução
- Este texto é uma coletânea de pensamentos do desenvolvedor cérebro Grug, organizando lições aprendidas com a experiência ao longo de muitos anos desenvolvendo software
- O desenvolvedor cérebro Grug não se considera inteligente, mas aprendeu muita coisa após programar por muito tempo
- Compartilha suas percepções de forma simples e engraçada, esperando que outras pessoas aprendam com os erros
- A complexidade é, de longe, o maior inimigo da vida de desenvolvimento
- A complexidade se infiltra silenciosamente no codebase e faz com que até um código que no começo era fácil de entender acabe, aos poucos, se tornando impossível de modificar
Lidando com o demônio da complexidade
- A complexidade se espalha sem fazer barulho, como um espírito invisível, e muitas vezes gerentes de projeto e desenvolvedores não-Grug não percebem isso direito
- A melhor maneira de bloquear a complexidade é dizer "não"
- "Não vou criar esta funcionalidade"
- "Não vou introduzir esta abstração"
- Claro, para a carreira pode ser mais vantajoso sair dizendo "sim", mas o desenvolvedor cérebro Grug valoriza fazer escolhas honestas consigo mesmo
- Dependendo da situação, também é preciso fazer concessões ("ok"), e nesses casos prefere resolver o problema de forma simples com uma solução 80/20 (aplicando o princípio de Pareto)
- Também é uma estratégia esperta não contar tudo ao gerente de projeto e, na prática, resolver com a abordagem 80/20
Estrutura de código e abstração
- A unidade adequada do código (cutpoint) se revela naturalmente com o tempo, então é melhor evitar abstração no início
- Um bom cutpoint idealmente tem uma interface estreita com o restante do sistema
- Tentativas de abstração precoce costumam falhar, e desenvolvedores experientes tentam estruturar o código aos poucos depois que ele já tomou alguma forma
- Desenvolvedores menos experientes ou de “big brain” tentam abstrair demais no começo do projeto e acabam deixando um peso de manutenção
Estratégia de testes
- É importante ter equilíbrio, sem obsessão, em relação a testes
- Prefere escrever testes depois da prototipagem, quando o código já está relativamente estabilizado
- Testes unitários são usados no início, mas na prática o maior efeito costuma vir da camada intermediária (testes de integração)
- Testes end-to-end também são necessários, mas se houver muitos eles se tornam impossíveis de manter, então é melhor limitar a poucos fluxos realmente essenciais
- Quando surge um bug report, sempre adiciona um teste de reprodução antes de corrigir o bug
Processo, ágil e refatoração
- Ágil não é ruim para o desenvolvedor Grug e também não é o pior cenário, mas esperar demais de “xamãs do ágil” é perigoso
- Prototipagem, ferramentas e bons colegas são fatores de sucesso mais importantes na prática
- Refatoração também é um bom hábito, mas refatorações grandes e forçadas são arriscadas
- Introduzir abstrações complexas à força pode acabar levando o projeto ao fracasso
Manutenção, perfeccionismo e humildade
- É arriscado desmontar sistemas existentes sem motivo, e sair removendo estruturas só porque “não se sabe por que estão ali” é um mau hábito
- O idealismo de sonhar com código perfeito, na prática, costuma causar problemas
- Quanto mais experiência se ganha, mais se sente na pele que é preciso “respeitar o código que funciona”
Ferramentas e produtividade
- Boas ferramentas de desenvolvimento (autocompletar da IDE, depurador etc.) aumentam muito a produtividade, e é importante conhecê-las a fundo
- Enfatiza que o valor real do sistema de tipos está em “autocompletar” e na prevenção de erros, e que abstrações excessivas e genéricos podem ser perigosos
Estilo de código e repetição
- Recomenda um estilo como dividir expressões condicionais em várias linhas para tornar o código mais fácil de ler e depurar
- Respeita o princípio DRY (Don’t Repeat Yourself), mas enfatiza que o importante é o equilíbrio, em vez de eliminar repetição a qualquer custo
- Em muitos casos, repetição simples é melhor do que uma implementação DRY complexa
Princípios de design de software
- Em vez do princípio SoC (separação de preocupações), prefere a localidade do comportamento, defendendo que “o código que realiza aquele comportamento deve estar naquele objeto para facilitar a manutenção”
- Alerta para usar callbacks/closures, sistema de tipos, genéricos e abstrações apenas em pequenas doses e de forma apropriada
- O abuso de closures pode criar “callback hell” em JavaScript
Logging e operação
- Logging é muito importante: deve ser deixado em cada ramificação principal e, em ambientes de nuvem, estruturado para permitir rastreamento com ID de requisição e similares
- Se for possível usar nível de log dinâmico e logs por usuário, isso ajuda muito a rastrear problemas em produção
Concorrência e otimização
- Em concorrência, confia apenas em modelos o mais simples possível (requisições web sem estado, filas de workers separadas etc.)
- Recomenda fazer otimização real apenas depois de obter dados reais de profiling de desempenho
- É preciso tomar cuidado com custos ocultos, como I/O de rede; olhar apenas para a complexidade de CPU é perigoso
Design de API
- Uma boa API deve ser fácil de usar, e designs ou abstrações complexas demais prejudicam a experiência do desenvolvedor
- Recomenda uma estrutura de “API simples para casos de uso comuns” e “API em camadas que também permita implementar casos complexos”
Desenvolvimento de parser
- Parsers de descida recursiva são subestimados na academia, mas são o método mais adequado e mais fácil de entender em código de produção real
- Na maior parte das experiências com desenvolvimento de parser, parsers gerados por ferramentas acabam produzindo resultados complexos demais e atrapalham mais do que ajudam na solução de problemas
- Aponta "Crafting Interpreters" como o melhor livro recomendado, com muitos conselhos práticos
Frontend e modismos
- Frontend moderno (React, SPA, GraphQL etc.) frequentemente invoca ainda mais o demônio da complexidade e, em muitos casos, é desnecessário
- O próprio Grug prefere reduzir a complexidade usando ferramentas simples como htmx e hyperscript
- Embora haja tentativas novas o tempo todo no frontend, é preciso notar que muitas vezes se trata apenas da repetição de ideias antigas
Fatores psicológicos e síndrome do impostor
- A maioria dos desenvolvedores muitas vezes sente que “não sabe o que está fazendo”, e é preciso se libertar do fenômeno FOLD (Fear Of Looking Dumb)
- Quando um desenvolvedor sênior diz publicamente “isso também é difícil para mim, é complexo demais”, o desenvolvedor júnior também pode aliviar essa pressão
- A síndrome do impostor é um sentimento comum, e o texto encoraja a ideia de que é possível continuar aprendendo e crescer
Conclusão
- Na programação, a complexidade deve estar sempre sob vigilância, e manter a simplicidade é o núcleo de um desenvolvimento bem-sucedido
- Experiência, uso eficaz de ferramentas, humildade e respeito pelo código que realmente funciona levam, no longo prazo, a um desenvolvimento mais eficiente e valioso
- "Complexidade é muito, muito ruim" — esta frase deve ser sempre lembrada
1 comentários
Comentários no Hacker News
print. Mesmo quando tento mostrar meu fluxo de trabalho aos colegas, não há reação. Concordo que o melhor ponto de partida para entender um sistema é justamente o depurador. Parar em uma linha de código interessante durante um teste e olhar a stack é muito mais fácil do que tentar seguir o código só na cabeça. Aprender a usar um depurador é ganhar um superpoder pequeno, mas de verdade. Se puder, recomendo muito tentar aplicar isso pelo menos uma vezprinté a única opção viável. E se até o sistema de logs der problema, ou o programa simplesmente cair antes de emitir logs, aí nemprintdá para usarprinte código de autoverificação. Colocarprinté muito mais rápido do que entrar passo a passo com um depurador. Além disso, o código comprintfica no programa, enquanto a sessão de depuração desaparece." Eu também concordo com isso. Na maior parte do desenvolvimento, o loopprint-hipótese-execução oferece uma resolução de problemas muito mais rápida. Não é que eu “execute” o código mentalmente; é que eu já tenho um modelo operacional do fluxo do código, então quando oprintmostra uma saída errada, na maioria das vezes eu intuo rapidamente o que está acontecendo. Link relacionado: The unreasonable effectiveness of print debuggingprintfdebugging sempre ter sido comum é que o ambiente não permitia confiar em depuradores com GUI. As GUIs no Linux muitas vezes são instáveis demais para serem confiáveis. No meu caso, só comecei a usar depurador de forma adequada quando (1) no Windows a GUI funcionava bem, mas a CLI às vezes quebrava, e (2) depois de várias vezes em que código de depuração comprintentrou por engano em alguma versão e causou problemas. Depois disso, vivi várias aventuras com depuradores de CLI, e senti que o processo de usar Junit+depurador (baseado em IDE, como no Eclipse) para experimentar código e já deixá-lo registrado como teste era tão conveniente quanto um REPL de Python. Claro, isso exige um investimento inicial para configurar o depurador de acordo com o ambiente