5 pontos por GN⁺ 2025-06-18 | 1 comentários | Compartilhar no WhatsApp
  • 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

 
GN⁺ 2025-06-18
Comentários no Hacker News
  • Eu valorizo tanto um bom depurador que isso nem dá para medir em pedra; na prática, acho ainda mais incrível. Tanto em startups pequenas quanto em equipes famosas de big tech, muitas vezes eu era o único do time que usava depurador. Na prática, vi muita gente que ainda depura com 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 vez
    • Eu realmente queria usar um depurador de verdade, mas, do ponto de vista de quem só trabalhou em grandes empresas, isso era algo praticamente impossível. Em uma arquitetura de malha de microsserviços, não dá para rodar nada localmente, e na maioria dos casos o ambiente de teste é configurado para que você nem consiga conectar um depurador passo a passo. Então depuração com print é 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í nem print dá para usar
    • Houve uma boa discussão sobre esse tema anos atrás. Tem uma citação famosa de Brian Kernighan e Rob Pike, e nenhum dos dois é exatamente um desenvolvedor jovem. "Nós não usamos depuradores para muito além de verificar stack traces ou alguns valores de variáveis. Estruturas de dados e fluxos de controle complexos fazem você ficar preso nos detalhes com facilidade. É mais produtivo pensar melhor sobre o programa por conta própria e, no meio do caminho, inserir print e código de autoverificação. Colocar print é muito mais rápido do que entrar passo a passo com um depurador. Além disso, o código com print fica no programa, enquanto a sessão de depuração desaparece." Eu também concordo com isso. Na maior parte do desenvolvimento, o loop print-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 o print mostra uma saída errada, na maioria das vezes eu intuo rapidamente o que está acontecendo. Link relacionado: The unreasonable effectiveness of print debugging
    • Em ambientes Linux, a razão de printf debugging 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 com print entrou 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
    • No meu próprio código é fácil usar depurador, e eu realmente gosto disso. Mas, quando o depurador entra fundo demais no interior de uma biblioteca ou framework, mais do que no código que eu escrevi, eu também me perco imediatamente e passo a odiar. Esses frameworks e bibliotecas foram construídos com centenas de milhares de horas de trabalho, então para o meu nível isso ultrapassa rapidamente o limite do que consigo compreender
  • Professor Carson, se por acaso estiver lendo isto, queria agradecer de coração. Na faculdade, eu não entendia por que estávamos aprendendo HTMX nem por que o senhor era tão apaixonado por isso, mas alguns anos depois finalmente entendi de verdade. HTML over the wire é tudo. Trabalhando como Staff Ruby on Rails Engineer, já vi seu trabalho várias vezes também no Hotwire, e às vezes me espanta ver o senhor ativo no GitHub ou no Hacker News. O senhor é sempre uma luz para a comunidade de programação. Tenho profundo respeito e gratidão
    • Não sou só eu que fico emocionado com isso, é comovente
    • HTMX não era só um meme? Por causa da Lei de Poe, fico sem saber se isso é sério ou não
  • Há muitas frases memoráveis neste texto, mas a parte de microsserviços foi a minha favorita: "grug não entende por que cérebro grande acha difícil decompor um sistema direito e ainda decide adicionar chamadas de rede por cima disso"
    • Algumas pessoas só conhecem uma forma de dividir um sistema em partes: transformar tudo em APIs. Se algo não é exposto como API, elas veem aquilo apenas como código opaco, impossível de entender e de reutilizar
    • É uma pena que microsserviços sejam usados porque, por vários motivos, às vezes eles realmente são práticos
    • Eu continuo vendo equipes minúsculas de desenvolvimento, com duas pessoas, pegarem um webapp trivial com cinco formulários e o transformarem numa estrutura de “microsserviços” complicada demais (banco de dados compartilhado, gerenciamento de API, jobs em lote via fila, notificações por e-mail, uma plataforma própria de observabilidade etc.). E no fim até os formulários comuns viram SPA porque “é mais fácil”. Agora eu entendo que “arquitetura” e “padrões” servem para criar trabalho para desenvolvedores inúteis. Se isso não existisse, seriam as mesmas pessoas na rua com uma placa dizendo “uso JavaScript por um sanduíche”
    • Minha teoria da conspiração é que esse resultado veio porque fornecedores de nuvem empurraram o padrão de microsserviços. - Fazem tudo depender de orquestradores como K8S para sequer funcionar, o que facilita vender nuvem gerenciada - Mais tráfego de rede e uso de CPU geram mais cobrança - Compartilhar estado em grande escala fica difícil, então você passa a precisar de banco de dados gerenciado e fila de eventos gerenciada - Fica difícil rodar localmente, então até o ambiente de desenvolvimento vira custo de nuvem - Você fica preso a abordagens específicas da nuvem e sair depois é complicado. Antigamente anunciavam a nuvem como uma forma de economizar custos de TI, o que é hilário. Desde os anos 2000 já dava para ver que isso era uma ilusão, e no fim só ficou tudo mais caro
  • A frase "complexidade versus encarar um tiranossauro cara a cara, grug escolhe o tiranossauro: pelo menos o tiranossauro dá para ver" me marcou tanto que penso nela pelo menos uma vez por semana
    • Citação: "Mesmo caindo, Leyster não soltou a pá. Em meio ao pânico, ele esqueceu esse fato. Então, desesperadamente, balançou a pá contra a perna do filhote de tiranossauro...". É uma cena que descreve vividamente uma luta extrema de sobrevivência contra um tiranossauro. No fim, a colega Tamara supera a crise ao enfiar corajosamente uma lança bem no meio do rosto do tiranossauro. O combate, a tensão e o silêncio da cena são marcantes
    • grug claramente nunca lutou contra um tiranossauro “invisível”. Eu ainda estou num duelo 1 contra 1 com um tiranossauro invisível, está realmente sendo duro
  • O admirável neste artigo é que o autor até é capaz de fazer coisas “mais complexas”, mas escolhe não seguir esse caminho com base na experiência. Claro que há hora e lugar para abstração e complexidade, mas a filosofia grug diz que não há valor intrínseco nisso. Acho essa parte muito convincente. Também sinto que IA funciona melhor com código consistente e baseado em dados
    • Quando usar complexidade e abstração, que seja quando isso fizer o código ficar mais fácil de entender do que antes. Vale lembrar sempre a premissa: “quando não for preciso um curso especial extra só para entender”. (Depende do contexto)
    • "Tudo deve ser feito o mais simples possível, mas não mais simples do que isso"
  • É difícil acreditar que este texto seja de 2022. A sensação é de que eu já o tinha lido há 10 anos e já o considerava um “clássico”
  • Este ensaio é o meu texto favorito sobre construir software. O estilo também tem muito charme (embora algumas pessoas possam rejeitá-lo), e a essência continua sempre válida
  • O trecho de código "triste mas verdade: aprender a dizer 'sim', aprender a culpar outro grug quando falhar, melhor estratégia de carreira" é a realidade. No começo eu achava, por engano, que a causa na empresa era simplesmente um problema de comunicação da equipe técnica, mas com o tempo aprendi (como grug) que realmente é assim
  • Este artigo traz a melhor explicação de visitor pattern que já vi até hoje
    • Eu não trabalho em uma codebase OO típica, então nunca entendi direito o que era visitor pattern, mas quero recomendar o livro "Crafting Interpreters", sobre criação de interpretadores/VMs. O livro mostra como visitor pattern é usado na prática. Tentei ler para entender por que ele introduzia tanta complexidade, mas no fim acabei substituindo por tagged union. Talvez eu só seja fraco em OO, mas o ponto do artigo do grug é esse mesmo: quando não há necessidade de abraçar complexidade e indireção, existem formas mais intuitivas
    • Eu sou sensível a naming, e não gosto do nome visitor pattern porque é vago demais. Na prática, nunca criei algo chamado Visitor. Por exemplo, num exercício com árvore sintática (AST), nomes como AstWalker, AstItem::dispatch(AstWalker) e AstWalker::process(AstItem) são muito mais concretos do que Visitor. “Visitor” no sentido de “visita” é abstrato e sem significado demais. O certo varia conforme o contexto, e basta deixar explícito em comentário que se trata de ‘visitor pattern’ para não haver problema de reconhecimento. No passado, quando precisei comparar/importar dados entre duas árvores de objetos, usei o nome AbstractImporter. Era mais específico, o processo e o papel ficavam mais claros. Não era um visitor pattern típico
    • Fui pesquisar e, de fato, tinha uma avaliação como “Bad”. kkk
  • Compartilhando posts relacionados. Alguém tem mais opiniões ou textos complementares?<br/><i>The Grug Brained Developer (2022)</i> - https://news.ycombinator.com/item?id=38076886 - outubro de 2023 (192 comentários)<br/><i>The Grug Brained Developer</i> - https://news.ycombinator.com/item?id=31840331 - junho de 2022 (374 comentários)