Escrevendo código que seja fácil de apagar
- Toda linha de código traz um custo de manutenção. Reutilizar código torna mudanças mais difíceis.
- Quanto mais consumidores uma API tiver, mais código precisará ser reescrito ao alterá-la.
- Gerenciar as dependências do código é um problema importante em sistemas de grande escala.
Etapa 0: não escrever código
- O número de linhas de código, por si só, não fornece muita informação.
- O código que não foi escrito é o mais fácil de apagar.
Etapa 1: copiar e colar código
- Código reutilizável pode ser escrito mais facilmente depois, com base em exemplos.
- Copiar e colar código é uma forma de evitar dependências e ganhar flexibilidade.
Etapa 2: não copiar e colar código
- Se o código já foi copiado e colado o suficiente, é hora de extraí-lo para uma função.
- Pode ser uma boa ideia criar um diretório
util para guardar vários utilitários em outros arquivos.
Etapa 3: escrever mais boilerplate
- Boilerplate é parecido com copiar e colar código, mas altera o código em lugares diferentes.
- Boilerplate reduz dependências e oferece flexibilidade.
Etapa 4: não escrever boilerplate
- Se houver boilerplate demais, ele deve ser encapsulado por uma biblioteca com opiniões sobre política, fluxo de trabalho e estado.
- A relação entre
requests e urllib3 é um bom exemplo.
Etapa 5: escrever grandes blocos de código
- A lógica de negócio é marcada por casos de exceção sem fim e hacks rápidos e sujos.
- É mais fácil apagar um erro grande do que apagar vários erros pequenos.
Etapa 6: dividir o código em partes
- Grandes blocos de código têm alto custo de manutenção.
- É preciso separar as responsabilidades do código e projetar módulos levando em conta a possibilidade de mudanças.
Etapa 7: continuar escrevendo código
- Deve ser possível escrever código novo de forma independente do código existente, para experimentar novas ideias.
- Feature flags são uma forma de poder mudar de ideia depois.
Resumo do GN⁺
- Este texto explica como escrever código que seja fácil de apagar.
- O ponto principal é reduzir as dependências do código, aumentar a flexibilidade e diminuir o custo de manutenção.
requests e urllib3 são projetos com funções semelhantes mencionados no texto.
- Este texto relembra desenvolvedores de software da importância da gestão e da manutenção de código.
1 comentários
Comentários do Hacker News
Gosto da frase "Simple is robust". Significa que, quanto menor a complexidade do sistema, mais fácil é mudá-lo. O planejamento para o futuro deve ser feito com código intuitivo, e não com código escalável. Por exemplo, abstrair só quando a situação exigir, incentivar duplicação simples, usar um monólito no início e priorizar escala vertical em vez de horizontal. Ao construir vários sistemas 0-1, encontrei esses pontos em comum.
Surpreende não haver menção a testes ou observabilidade. Testes têm custo de manutenção, mas reduzem o risco de problemas ao remover código. Ao expor um serviço para chamadores externos, é preciso um método forte para marcar algumas chamadas como obsoletas e observar se ainda estão sendo usadas. Recentemente removi resolvers de GraphQL de forma semiautomática e usei métricas de frequência de uso para identificar quais resolvers não podiam ser deletados. O GraphQL tem anotações de depreciação, mas no serviço isso não era tratado de forma especial. Adicionando observabilidade para marcar quando uma função obsoleta foi chamada, e depois de rodar isso em produção por tempo suficiente, é possível remover com segurança código exposto externamente.
Acabei promovendo a ideia de "projetar para apagar". No passado, eu achava que dava para planejar todas as situações e criar uma obra que atendesse a todas as necessidades, mas é difícil prever as necessidades futuras. Um dia, aquilo que eu fiz vai se tornar inútil para alguém, e será justificável que essa pessoa o desmonte. Portanto, é preciso se esforçar para tornar a remoção fácil. Isso muitas vezes resulta em menos acoplamento, mas é diferente do impulso de desenvolvedores jovens de separar tudo em frameworks meta-configuráveis. Às vezes, um acoplamento forte é melhor quando é mais fácil de entender logicamente.
Para escrever código fácil de apagar, é preciso repetir para evitar dependências, e não repetir para gerenciar. É preciso dividir o código em camadas e construir APIs simples sobre partes que podem ser simples de implementar, mas incômodas de usar. É preciso separar o código e isolar tanto as partes difíceis de escrever quanto as partes com maior chance de mudar do restante do código e umas das outras. Nem todas as escolhas devem ficar hardcoded; algumas devem poder ser alteradas em runtime. Pela minha experiência, código fácil de apagar é em camadas e modularizado, então também é fácil de expandir.
Eu costumava dizer aos estudantes de física computacional que o melhor cálculo é aquele com o qual você não precisa se preocupar.
Pessoalmente, separo o código em lógica de negócio e implementação real. A lógica de negócio pode ser duplicada por sua própria natureza, mas detalhes técnicos demais não deveriam ser duplicados. Desde que a lógica de negócio não lide diretamente com a aplicação e permaneça independente dela, ela pode ser tão bagunçada quanto você quiser. Se surgir um problema e aquilo não funcionar bem, existe a opção de apagar a implementação inteira, corrigi-la e não ser forçado a procurar a especificação real dentro da implementação.
O erro óbvio do primeiro parágrafo: o problema do reúso de código seria atrapalhar você a mudar de ideia depois. Isso geralmente é uma afirmação errada. Se você mudar de ideia e o código tiver sido copiado e colado em dez lugares, terá de mudar os dez lugares. Se, por outro lado, o código estiver em uma função, você muda uma vez só. Se uma das dez chamadas não deve ser alterada, ainda assim você pode copiar e colar, e também pode tornar a função mais genérica. Assim como não olhar para os lados ao atravessar a rua, copiar e colar quase sempre é uma má ideia.
Há uma excelente correlação: código ruim permanece por muito tempo porque é difícil removê-lo.
Fico me perguntando se isso quer dizer que devemos usar software o mais próximo possível do estado padrão e evitar customizações profundas.