Desenvolvedor Grug Brained (2022)
(grugbrain.dev)- Com base na experiência de um desenvolvedor de “cérebro pequeno” que programa há muito tempo, o texto enfatiza que o maior inimigo do desenvolvimento de software é a complexidade e que a atitude de reduzi-la deve estar no centro do trabalho prático
- A arma básica para impedir a complexidade é dizer “não”, e, quando é preciso fazer concessões, busca-se uma solução prática com abordagem 80/20, protótipos, pequenas refatorações e abstração tardia
- Em testes, depois que o código está um pouco mais consolidado, o foco deve ficar em testes de integração, com testes unitários e um pequeno número de testes end-to-end como apoio; bugs devem ser primeiro reproduzidos com testes de regressão antes de serem corrigidos
- Ferramentas, sistema de tipos, logging, depurador e APIs simples reduzem a carga de memória e raciocínio do desenvolvedor, mas genéricos em excesso, callbacks, microsserviços e frameworks de frontend podem aumentar a complexidade
- É preciso abordar com humildade o código existente e os processos organizacionais, não remover levianamente código que você ainda não entende e criar uma cultura em que seja possível dizer “isso está complexo demais”
Complexidade é a inimiga eterna do desenvolvedor
- No desenvolvimento de software, o inimigo mais perigoso é a complexidade
- A complexidade entra aos poucos na base de código e faz com que mudar uma parte quebre até áreas que pareciam não ter relação
- Grug compara isso a um “demônio” invisível, uma entidade que o desenvolvedor não consegue ver diretamente nem derrotar facilmente
- A complexidade também pode entrar pelas mãos de desenvolvedores ou gerentes de projeto bem-intencionados
- Quanto mais aumentam funcionalidades, abstrações e processos, mais difícil o código fica de entender
- O próprio Grug admite que às vezes ele também foi responsável por introduzir complexidade
Como usar “no” e “ok”
- A arma mais forte contra a complexidade é o “no”
- Não criar funcionalidades desnecessárias
- Não criar abstrações desnecessárias
- Mas, embora “no” seja um bom conselho de engenharia, nem sempre é vantajoso para a carreira
- O “yes” pode levar a mais recompensas ou a cargos de gestão
- Ainda assim, para ser fiel a si mesmo como desenvolvedor, o “no” é importante
- Quando é preciso fazer concessões, responde-se “ok” e depois se busca uma solução 80/20
- A 80/20 solution é uma abordagem que atende 80% dos requisitos com 20% do código
- Mesmo sem todas as funcionalidades cosméticas, ela entrega a maior parte do valor e contém a complexidade
- Às vezes, considera-se melhor não explicar em detalhes ao gerente de projeto toda a forma de implementação e simplesmente resolver do jeito mais simples
Estruturação de código e refatoração
- No início de um projeto, não se deve dividir a aplicação rápido demais
- No começo, a forma do sistema ainda não está clara e ainda não se entende completamente o que está sendo construído
- Com o tempo, bons pontos de corte (cut point) acabam aparecendo
- Um bom ponto de corte tem uma interface estreita com o resto do sistema
- Ele esconde a complexidade interna com um número pequeno de funções ou abstrações
- Grug compara isso a trancar o demônio da complexidade dentro de um recinto
- Quando um desenvolvedor de cérebro grande quer criar muitas abstrações logo no começo do projeto, é preciso um jeito de reduzir o dano
- Dá para redirecionar isso para entregáveis que não causem dano direto ao código, como diagramas UML
- Exigir “uma demo funcionando amanhã” faz a pessoa encarar mais rápido o código real e a realidade
- Essa abordagem de demo pode ser chamada de protótipo
- Refatoração é útil mais para o fim do projeto, quando o código já se consolidou
- Quanto maior a refatoração, maior a chance de fracasso
- Há preferência por avançar em passos pequenos, mantendo o sistema sempre funcionando
- Testes end-to-end viram uma linha de vida durante a refatoração, embora, quando quebram, possa ser difícil entender a causa
- Abstração excessiva pode levar ao fracasso da refatoração e do sistema
- J2EE aparece como exemplo de abstração excessiva
- A adoção de OSGi era uma tentativa de reduzir complexidade, mas acabou criando uma complexidade ainda maior e exigiu vários anos de retrabalho
Estratégia de testes
- Testes economizam muito tempo, mas há ceticismo em relação ao “testar sempre primeiro”
- Quando ainda não se entende o domínio, é difícil saber o que testar
- Grug prefere escrever a maior parte dos testes depois do protótipo, quando o código começa a se estabilizar
- Mesmo escrevendo testes depois, é preciso disciplina
- Não se deve pular testes porque “na minha máquina funciona”
- Não há garantia de que vá funcionar em outra máquina, nem na mesma máquina no futuro
- Os papéis de testes unitários, testes de integração e testes end-to-end são distintos
- unit tests ajudam no início do projeto, mas quebram com facilidade quando a implementação muda e podem dificultar a refatoração
- Testes end to end mostram o funcionamento do sistema inteiro, mas quando quebram é difícil identificar a causa, e, se quebrarem com frequência, podem acabar sendo ignorados
- integration tests ficam perto do “ponto ideal”: altos o bastante para verificar a correção do sistema e baixos o bastante para permitir ver a causa no depurador
- A forma ideal da composição de testes seria:
- No início, alguns testes unitários
- À medida que surgem pontos de corte e o sistema se estabiliza, testes de integração fortes
- Um conjunto pequeno e bem mantido de testes end-to-end cobrindo apenas as funcionalidades de UI mais comuns e os casos de borda importantes
- Prefere-se usar mocking raramente e, quando necessário, apenas em unidades maiores, como os limites do sistema
- Ao corrigir bugs, faz-se uma exceção: primeiro um teste de regressão para reproduzir o bug, depois a correção
Processos, ágil e atitude diante do código existente
- Ágil não é o pior cenário possível, mas também é difícil dizer que seja simplesmente bom
- Como forma de organizar o desenvolvimento, pode funcionar razoavelmente
- Há cautela com especialistas em ágil que, sempre que algo falha, dizem que “o ágil não foi feito direito”
- Para o sucesso, protótipos, ferramentas e contratar bons desenvolvedores são mais importantes
- Processos ágeis podem ajudar, mas levá-los a sério demais pode fazer mal
- no silver club: não existe bala de prata que resolva todos os problemas de software
- Chesterton’s Fence é usado como alerta contra remover código existente
- Se você não sabe para que serve uma cerca, primeiro deve entendê-la; não deve simplesmente derrubá-la
- Mesmo código feio merece respeito se está funcionando hoje
- Especialmente em sistemas grandes, é melhor entender primeiro e melhorar depois
- Testes podem servir como pista para entender por que uma certa “cerca” existe
Ferramentas e sistema de tipos
- Ferramentas reduzem a carga do que o desenvolvedor precisa lembrar e deduzir por conta própria
- Ao entrar em um ambiente novo, gastar tempo aprendendo as ferramentas ao redor pode aumentar a produtividade
- Sem documentação, talvez seja preciso perguntar a outros desenvolvedores para entender como tudo funciona
- O autocompletar de código da IDE evita a necessidade de memorizar toda a API
- Diz-se que programar em Java é quase impossível sem autocompletar
- Um bom depurador é extremamente importante
- É preciso aprender a fundo recursos como breakpoint condicional, avaliação de expressões e navegação pela pilha
- Para desenvolvedores novos, aprender a usar o depurador pode ensinar mais sobre computadores do que muitas aulas da faculdade
- O maior valor do sistema de tipos é “apertar ponto (
.) e ver o que dá para fazer”- Correção de tipos é boa, mas, para Grug, suporte de ferramentas e autocompletar têm valor ainda maior
- Abstrações de tipos e genéricos em excesso podem dificultar o código do trabalho real
- Prefere-se limitar o uso de genéricos principalmente a classes contêiner
Expressões, DRY e separação de responsabilidades
- Há preferência por código fácil de depurar em vez de código curto
- Em vez de colocar condições complexas em uma linha, separar em variáveis intermediárias facilita ver o resultado e o significado de cada expressão
- Mesmo com mais linhas, entender e depurar condicionais fica mais fácil
- DRY é um bom conselho, mas exige equilíbrio
- Duplicação simples e clara pode ser melhor do que callbacks, closures ou modelos de objetos complexos
- Se remover duplicação aumenta a complexidade, o prejuízo pode ser maior que o ganho
- A visão sobre Separation of Concern é mais crítica
- Um exemplo típico no desenvolvimento web é separar CSS, HTML e JavaScript
- Como alternativa, Grug prefere locality of behavior
- Se o código relacionado fica perto do elemento que se comporta, ao olhar para esse elemento já se entende o que ele faz
- Closures são úteis quando usadas da forma certa, como na abstração de operações sobre coleções
- Mas, como sal, sistema de tipos ou genéricos, um pouco já basta; em excesso, fazem mal
- O “callback hell” do JavaScript aparece como exemplo de abuso de closures
Logging, concorrência e otimização
- Logging é muito importante, especialmente em ambientes de implantação na nuvem
- Registra-se log em cada ramificação importante da lógica
- Quando uma requisição passa por várias máquinas, coloca-se request ID em todos os logs para permitir agrupamento
- Sempre que possível, ajusta-se o nível de log dinamicamente
- Se possível, ajusta-se também o nível de log por usuário
- Bibliotecas de logging em Java podem ser complexas, mas investir direito na infraestrutura de logging traz grande retorno depois
- Concorrência é algo a ser temido
- Sempre que possível, usam-se modelos simples, como handlers de requisição web sem estado e filas independentes de trabalhos remotos
- optimistic concurrency é vista como algo que funciona bem no contexto web
- thread local variable é usada ocasionalmente, principalmente ao escrever código de framework
- Mesmo estruturas concorrentes como ConcurrentHashMap em Java ainda exigem muito cuidado
- Otimização deve começar quando há um perfil de desempenho real
- Há concordância com “premature optimization is the root of all evil”
- O gargalo real pode ser diferente do que se imagina
- Não se deve olhar só para CPU; chamadas de rede podem equivaler a milhões de ciclos de CPU, então é melhor reduzi-las quando possível
API, parsing e visitor pattern
- Uma boa API evita que o desenvolvedor tenha que pensar demais
- APIs ruins costumam surgir quando são projetadas em torno dos detalhes internos da implementação ou do modelo de domínio, ou quando ficam abstratas demais
- Para casos simples, prefere-se oferecer uma API simples; para casos complexos, uma API mais complexa, em camadas
- Em APIs orientadas a objetos, considera-se melhor anexar a funcionalidade ao objeto-alvo
- Em Java, é citado como exemplo ruim o fluxo de converter uma lista em Stream para filtrar e depois coletar de volta em List
- Para uma operação comum como
filter(), entende-se que ela deveria estar na list e retornar uma list
- Parsers recursive descent são vistos como uma forma divertida e bonita
- Geradores de parser são criticados por serem difíceis de entender e de depurar
- Afirma-se que quase todos os parsers de produção reais acabam sendo de descida recursiva
- Recomenda-se Crafting Interpreters, de Bob Nystrom
- Dá para ler gratuitamente online, mas também se recomenda comprar o livro
- Ainda assim, o visitor pattern é descrito como uma armadilha
- A avaliação do Visitor pattern é curta: “bad”
Frontend, microsserviços e modas
- Microsserviços são perigosos porque “adicionam chamadas de rede ao problema mais difícil, que é dividir corretamente um sistema”
- No desenvolvimento frontend, a complexidade é especialmente forte
- Critica-se o uso de bibliotecas SPA, APIs JSON com GraphQL e backends HTTP até para salvar formulários simples ou sites institucionais
- Separar frontend e backend cria duas moradias para a complexidade
- Grug afirma ter criado htmx e hyperscript para reduzir a complexidade
- Há preferência por manter HTML simples e evitar muito JavaScript
- Reconhece-se que React pode ser melhor para emprego e para certos tipos de aplicação
- O desenvolvimento tem muitas modas, especialmente no frontend
- O backend ficou mais entediante, e muitas ideias ruins já foram tentadas
- Recomenda-se tratar novas abordagens revolucionárias com cautela, como quem põe uma pitada de sal
- Muitas ideias já foram testadas alguma vez, e ideias ruins recicladas podem desperdiçar tempo
Medo e síndrome do impostor
- É positivo quando desenvolvedores sêniores dizem publicamente “isso está complexo demais”
- Por causa do Fear Of Looking Dumb (FOLD), muitos desenvolvedores têm dificuldade de admitir que não entenderam algo
- Quando o sênior reconhece isso primeiro, o júnior também consegue falar sobre a complexidade e sobre o que não entendeu
- O FOLD é, especialmente para desenvolvedores jovens, uma das principais fontes de poder da complexidade
- Considera-se que senso de humor e a lembrança de fracassos passados ajudam
- Síndrome do impostor também é comum no desenvolvimento
- Grug diz que vive entre a sensação de dominar tudo e a sensação de não saber o que está fazendo
- Mesmo com o sucesso open source de htmx e _hyperscript, ele ainda teme erros e fracassos
- Se todo mundo sente síndrome do impostor, talvez seja melhor aceitar que ninguém é realmente um impostor de verdade
Leituras recomendadas e conclusão
- Leituras recomendadas
- A conclusão final é simples
- Complexidade é muito, muito ruim
1 comentários
Comentários do Hacker News
Se o professor Carson estiver lendo os comentários, gostaria de agradecer sinceramente por todas as contribuições que fez ao longo do tempo.
Na faculdade, eu não entendia por que estávamos aprendendo HTMX, nem por que o senhor ficava tão empolgado com isso, mas alguns anos depois agora entendo. Era tudo sobre HTML over the wire.
Trabalhando como Staff Ruby on Rails Engineer, vi seu trabalho no Hotwire, e também foi bacana vê-lo aparecer de vez em quando no Hacker News ou conversar com desenvolvedores do Hotwire no GitHub. O senhor é muito respeitado e apreciado como uma luz na comunidade de programação.
Concordo com a frase “um bom depurador vale tanto quanto uma pedra brilhante — na verdade, vale mais”.
Já passei por startups pequenas e por equipes “de elite” em big techs, mas quase sempre eu era a única pessoa da equipe que usava um depurador. No mundo real, pelo menos no lado de tecnologias web, parece que a maioria depura com instruções
print.Parar em uma linha de código interessante durante a execução dos testes e ver a pilha de chamadas que levou até ali é muito mais fácil do que tentar executar o código mentalmente para a frente. Para um grug jovem, essa técnica é um pequeno superpoder; se possível, vale a pena investir tempo para fazê-la funcionar na sua própria base de código.
Eles diziam que não usam muito depuradores além de ver stack traces ou o valor de uma ou duas variáveis, e que é fácil se perder nos detalhes de estruturas de dados complexas e fluxos de controle; para eles, é mais produtivo inserir instruções de saída e código de autoverificação em pontos-chave.
Em geral, concordo. Em quase tudo que faço, o ciclo hipótese-log-execução me leva à resposta muito mais rápido. Não é que eu esteja tentando executar o código na cabeça; já tenho um modelo de trabalho de como o código roda, sei que saída deveria aparecer se esse modelo estiver correto, e consigo intuir rapidamente a situação real a partir de uma saída incorreta.
[0] The unreasonable effectiveness of print debugging (349 points, 354 comments) April 2021 https://news.ycombinator.com/item?id=26925570
Em uma arquitetura de malha de microsserviços, é difícil até executar algo corretamente localmente, e muitas vezes o ambiente de testes não está configurado para permitir anexar um depurador passo a passo. No fim, sobra apenas a depuração com
print; e, se o próprio sistema de logging for o problema, ou se o programa morrer antes de fazer flush dos logs, nem isso dá para usar.Logo isso acaba sendo necessário. Mas, mesmo em colaboração, fiquei surpreso ao ver pessoas que simplesmente não sabem usar um depurador. Eu dizia “coloque um breakpoint ali”, “agora entre na função e veja o estado das variáveis”, “isso pode passar por cima”, e toda vez recebia um olhar perdido de volta.
Tecnicamente, não fico colocando e removendo instruções
print; coloco código de logging que pretendo deixar ali. Se for uma interface importante, geralmente começo registrando, em nível INFO, a entrada/saída da função e os valores dos parâmetros; à medida que uso o sistema e vejo pontos que precisam ser investigados melhor, adiciono logs mais detalhados.Também dedico bastante cuidado ao formato dos logs. Quando trabalhava com sistemas distribuídos, ajustar corretamente o prefixo de cada linha de log era muito útil. Eu colocava ID do nó, pid e timestamp todos com largura fixa; ao baixar e ordenar os logs do cluster inteiro, o comportamento de vários nós aparecia intercalado em um único arquivo.
Mas, no momento em que o depurador entra fundo em uma biblioteca ou framework que estou usando, eu me perco e passo a detestar. Aquele framework ou biblioteca acumula dezenas de milhares de horas-pessoa de trabalho, então tenho a sensação de que está muito além do meu nível.
Há muitas frases preciosas aqui, mas esta sobre microsserviços é a minha favorita: “grug se pergunta por que pessoas inteligentes pegam o problema mais difícil, o de dividir um sistema corretamente, e ainda colocam chamadas de rede no meio”
Elas topam pegar uma webapp trivial com umas cinco telas de formulário, dividi-la em “microsserviços”, fazê-los compartilhar o mesmo banco de dados, adicionar uma camada de gerenciamento de API, uma fila para trabalhos em lote grandes (megabytes), um sistema de notificações por e-mail e até uma plataforma de observabilidade feita em casa; depois, dizendo que “assim é mais fácil”, transformam formulários web simples em uma SPA
Agora entendo que “arquitetura” e “padrões” são um programa de geração de empregos para desenvolvedores inúteis. Se não fosse isso, estariam na rua com uma placa dizendo “escrevo JavaScript em troca de sanduíche”
Para elas, se algo não for exposto como uma chamada de API, é só um bloco opaco de código que não conseguem entender nem reutilizar
Vários pacotes podem cooperar internamente, mas expor apenas uma API pequena para o restante da base de código. O fato de haver rede obriga os módulos a trocar apenas dados, não callbacks ou comportamentos, e pressiona para que a interface evolua de modo retrocompatível. Assim dá para fazer “hot reload” de módulos diferentes em momentos diferentes sem tudo explodir
Acho que seria possível obter a maior parte disso sem um salto de rede real, mas ainda não vi uma tentativa séria
Sem orquestradores como K8S, elas não rodam; são difíceis de instalar e operar, o que favorece a venda de cloud gerenciada. Fazem usar mais largura de banda de rede e mais CPU, e ambos são cobrados
Dificultam compartilhar e manter estados complexos ou grandes dentro da aplicação, levando ao uso de bancos de dados gerenciados ou serviços de fila de eventos como substitutos. Em um monólito, você usaria uma fila ou um canal; em microsserviços, acaba querendo uma fera enorme como Kafka
Rodar localmente também fica difícil, então você precisa de ambientes de desenvolvimento na cloud, e talvez de vários ambientes de desenvolvimento e teste. A dependência de características específicas de uma cloud, como a forma de rede dela, aumenta o vendor lock-in de cloud
Não sei se lembram da época em que cloud era vendida como algo que reduziria os custos de TI. Era ridículo; desde os anos 2000 eu sabia que era papo furado e achava que no fim só aumentaria todos os custos
Porque esse modelo é mais fácil de gerenciar; é mais uma questão de organização do desenvolvimento do que uma decisão técnica. Se a alternativa for monorepo, pessoalmente acho ainda pior
“Se tiver que escolher entre complexidade e um 1:1 contra um tiranossauro, grug escolhe o tiranossauro. Pelo menos grug consegue ver o tiranossauro”
Penso nessa frase pelo menos uma vez por semana
Este grug está num 1:1 contra um tiranossauro transparente, e está amaldiçoado
Um dos valores deste texto está no fato de que alguém capaz de fazer coisas mais sofisticadas e complexas evita isso deliberadamente por experiência
Há, sem dúvida, momentos e lugares em que sofisticação e abstrações mais elevadas são necessárias. Mas a filosofia grug diz que não há valor intrínseco em fazer esse tipo de coisa por si só, e isso me parece um conselho bastante sensato
Também vi que assistência de IA funciona melhor com código consistente, comum e centrado em dados. Pode depender do contexto
O desenvolvedor iniciante escreve código simples, o intermediário escreve código complexo, e o especialista volta a escrever código simples
Quais palestras dá para pressupor que todo mundo já assistiu depende do contexto
Uma das ironias do desenvolvimento de software moderno é que introduzimos complexidade achando que “no fim, isso vai economizar tempo”
Às vezes isso está certo e de fato economiza tempo, mas nem sempre — e talvez nem com frequência
DRY às vezes leva a abstrações prematuras. No momento em que você pensa “esse padrão também vai ser usado em outros lugares, então vou extrair a parte comum”, o demônio da complexidade entra
Queremos pegar o máximo possível de bugs em tempo de compilação, mas, para isso, o compilador precisa saber mais sobre o que estamos realmente tentando fazer, e acabamos criando tipos complexos que corroem a compreensibilidade
Para evitar boilerplate, criamos macros complexas ou DSLs, mas, por causa da lei das abstrações vazadas, no momento em que precisamos conhecer a implementação real, a cabeça explode
O difícil é que todos esses exemplos às vezes são boas ideias. Considero que conseguir julgar quando introduzir complexidade resulta em simplificação é uma marca de um bom engenheiro de software
A lógica de negócio, idealmente, deve ser definida em um só lugar, mas outras coisas podem ser duplicadas quando necessário, e isso não é ruim por si só
Para moderar o DRY, também enfatizo a “regra do 3”. Código copiado/colado até três vezes é aceitável; depois disso, vale pensar em abstração. Claro que não existe uma regra prática que sirva para todos os casos, e esse senso é difícil de ensinar
Normalmente crio web apps com frameworks no estilo Sinatra, como Flask, escrevendo funções que respondem a padrões de URL. Uma “tela” pode consistir em uma ou mais funções que trabalham juntas e nos templates HTML correspondentes
Se a aplicação configura coisas como conexão com banco de dados, localização de arquivos e cabeçalho/rodapé HTML, fora isso há muito pouco acoplamento entre as telas. Se preciso de uma tela nova, posso copiar uma tela existente e ajustá-la, ou pedir a um LLM para criar a tela ou o endpoint; se o resultado ficar ruim, faço de novo
Em um emprego anterior, eu estava criando um framework chamado Themis para montar conjuntos de treinamento de ML, usando microsserviços, React, Docker etc. O requisito real era continuar adicionando novas tarefas e criar rapidamente, para cada tarefa, telas simples, mas muito otimizadas. Quando você precisa fazer 20 mil julgamentos, já é difícil clicar uma vez em cada um; se forem quatro cliques por item, viram 80 mil e você desiste
Na arquitetura da época, era preciso escrever endpoints de API de uma aplicação JAXB e componentes em um app React monolítico, depois esperar 20 minutos enquanto TypeScript, Docker e javac rodavam. Com sorte, subia; se não, era começar de novo do zero
Escrevi uma crítica ao Themis e projetei o Nemesis com o objetivo de desenvolver novas tarefas rapidamente, mas esse foi um caminho não escolhido no emprego anterior. Ainda assim, desde então, Nemesis e eu temos processado milhões de instâncias de tarefas
Não significa que você deve extrair um helper usado em apenas um lugar. Mesmo que haja um monte de lógica dentro de uma função, classe, arquivo etc., se ela não foi copiada, ainda é DRY
Abstração prematura existe de verdade. Também não ajuda que as aulas de CS, em geral, ensinem a fazer isso. Se você der um banco de dados MySQL a um recém-contratado, talvez a primeira coisa que ele tente fazer seja abstrair o MySQL
Seja complexo ou simples, se o resultado não agrega valor, não importa. Primeiro foque em agregar mais valor do que remover; preocupe-se com complexidade depois
Um dos meus jeitos favoritos de usar LLM é colocar este ensaio nele e pedir que comente o problema que estou tratando agora na persona de um grug-brained developer. É ótimo para aliviar o estresse
Será que basta começar o prompt com algo como “aja como o Grug Brained Developer deste ensaio”?
“Complexidade é muito ruim” é realmente uma afirmação correta
Em todos os anos em que trabalhei com engenharia de software, essa ideia foi um dos poucos princípios que se mostrou consistentemente verdadeiro em todas as situações. Alguns problemas são complexos por natureza, mas, mesmo nesses casos, é muito melhor dedicar tempo para chegar à solução mais simples
Meus trabalhos mais eficazes vieram depois de questionar abordagens anteriores e simplificá-las de forma ousada. Você pode perder um pouco de flexibilidade potencial, mas, na maior parte das vezes, eu não precisava de tanta flexibilidade quanto achava
Por exemplo, desde que surgiram LLMs razoavelmente bons e capazes de agir de forma agentiva, passei a evitar tipos TypeScript excessivamente complexos, frágeis e difíceis de depurar, e a escrever código em forma de especificação para então pedir ao LLM que gere estaticamente o código baseado nisso
A dependência de ESLint do projeto continuava quebrando após atualizações de versão, muitas regras não eram sofisticadas o suficiente para evitar falsos positivos, e mantê-la funcionando direito com TypeScript e VSCode também era complexo. Ao trocar para Biome.js, ficou mais simples e eficaz o suficiente, mas ultimamente ele também vem apresentando bugs. Ainda assim, percebi que linting é algo bom de se ter, não algo que deva exigir tempo demais para cuidar; por isso, removi do toolchain de build, e também não preciso mais deixá-lo sempre ligado no VSCode. Basta rodar o Biome de vez em quando para verificar apenas o estilo e a formatação do código
Ao criar uma ferramenta personalizada de migração de dados para o projeto, considerei que migrações para frente eram necessárias, mas que migrações reversas não valiam o tempo e a complexidade para implementar. Se for preciso fazer rollback de um banco de dados com dados, basta restaurar um backup; se não houver dados ou se não for um banco de produção, dá para começar de um estado limpo com um script de inicialização versionado
Também não entendo bem como o terceiro é mais simples. Com uma cabeça matemática, dá para criar facilmente um espaço bijetivo. Talvez seja até mais difícil simular migração reversa por outros meios. Claro, depende dos detalhes, então não é uma regra geral
O inteligente Rich disse que complect é amarrar coisas umas às outras, e concordo com isso. Rich disse que complexidade é ruim, mas disso eu discordo. Amarrar coisas é necessário. Se elas não estiverem conectadas entre si, não dá para resolver o problema
É difícil acreditar que este texto seja de 2022
Eu diria com confiança que o li há 10 anos e que ele já era um clássico na época
Triste, mas verdadeiro: aprender a dizer “yes” e depois, quando falhar, aprender a jogar a culpa em outro grug é o conselho de carreira ideal
Quando entrei no mundo corporativo pela primeira vez, achei que isso não era verdade e que era só falta de comunicação das equipes técnicas. Eu estava errado, e o grug estava certo