Como deixar seu código mais limpo
Publicados: 2020-06-24Os desenvolvedores passam a maior parte do tempo lendo e mantendo o código existente. Tentamos entender o que ele faz e como mudá-lo ou ampliá-lo. Então, por que focar apenas no desempenho?
A legibilidade deve ser tão importante ao construir um código para durar.
Ao desenvolver software de longa duração, é vital ter uma ótima legibilidade de código. Seja mantendo o código antigo, adicionando novas funcionalidades ou alterando o antigo, o código legível torna o trabalho muito mais fácil .
Outro benefício é quando você está na posição de integrar novos desenvolvedores . A integração bem-sucedida depende da legibilidade do código existente.
A realidade é que quando você começa a trabalhar em um produto que vem sendo desenvolvido há anos, você terá que lidar com código legado em algum momento. Muita frustração se acumula quando você é inundado com milhares de linhas de código ilegível que você não escreveu. Mantê-lo e construí-lo só piora isso.
No Mediatoolkit, estamos desenvolvendo nosso produto a longo prazo . Por um lado, isso significa que evitamos correções de curto prazo e decisões de negócios arriscadas. Por outro, exige que a equipe de engenharia coloque um pouco de esforço extra na legibilidade do código.
A verdade é que não colocamos tanta ênfase na legibilidade do código até alguns anos atrás. Você adivinhou, foi quando surgiram alguns problemas sérios com a integração de novos desenvolvedores e a atualização de recursos.
Espero que os outros não repitam nossos erros e comecem a limpar a tempo . Aqui estão algumas diretrizes sobre legibilidade de código que seguimos no Mediatoolkit.
A legibilidade é importante
Ao trabalhar em um produto existente, os desenvolvedores passam a maior parte do tempo lendo e mantendo o código existente. Se algo levou anos para se desenvolver, é certo que levará mais de um minuto para descobrir o que faz e como mudá-lo.
Apesar da quantidade de tempo que os desenvolvedores gastam lendo o código existente, mais foco é colocado na escrita do novo código com melhor desempenho. O tempo necessário para mantê-lo raramente é levado em consideração. Então, o que focar em vez disso?
Comece com o básico . Estes são os primeiros passos que você pode tomar para melhorar seu código e torná-lo mais limpo:
- Use nomes significativos
- Melhorar funções
- Prefira a imutabilidade
- Prefira programação declarativa a imperativa
- Mantenha-o simples estúpido - KISS
- Não se repita – SECA
- Você não vai precisar disso - YAGNI
Use nomes significativos
Você nomeia tudo no código, então certifique-se de fazê-lo bem. Nomes significativos fornecem um contexto do que está acontecendo. Os nomes devem informar a intenção do código – a razão pela qual algo está lá, o que faz e como é usado.
Leve o seu tempo para chegar a bons nomes. Tentar lembrar o que você queria expressar com rabiscos aleatórios consome mais de suas horas.
Código sem nomes significativos
Código com nomes significativos
Quando possível, os nomes devem vir de um domínio problemático . Não use nomes técnicos se houver um nome melhor no domínio.
Classes e objetos devem ter substantivos ou frases nominais como seus nomes, como customer , emailSender ou htmlParser .
Mas, seguindo essa regra, tente evitar nomes que contenham substantivos genéricos como data , info , manager , processor , controller …, já que na maioria das vezes não trazem muito significado.
O termo geral não lhe diz qual é o seu domínio. Apenas lhe diz o que contém no sentido mais amplo possível.
Por exemplo, um objeto chamado info é muito mais difícil de ler do que userInfo . Além disso, você pode ter vários infos , controllers ou managers em todo o código, então é mais fácil seguir a lógica do código quando esses nomes são distintos de domínio.
Os métodos devem ser nomeados usando verbos ou frases verbais como postPayment , deletePage ou insert . É bom evitar usar nomes abreviados , pois geralmente são confusos e intuitivos apenas durante o desenvolvimento inicial.
Em vez disso, use nomes pronunciáveis . Esses são mais fáceis de entender e memorizar durante a leitura e compreensão do código.
Um nome ruim sem comentários não dá nenhum significado:
Um nome auto-explicativo melhor:
Escolha um termo por conceito e fique com ele
É confuso ter fetch , retrieve e get como métodos equivalentes de classes diferentes.
Usar nomes diferentes para coisas iguais ou semelhantes confunde o leitor e abre questões como como essas coisas nomeadas diferem, uma vez que são nomeadas de maneira diferente.
Melhorar funções
A primeira coisa que você pode fazer para melhorar as funções é torná-las pequenas . Quando digo pequeno quero dizer abaixo de 50 linhas, de preferência abaixo de 10-15. Ao manter as funções pequenas, você reduz o contexto em que precisa pensar durante a leitura.
Os recuos nas funções também aumentam a manutenção do contexto . Você deve ter uma boa explicação de por que você tem 2 (ou mais) níveis de recuo em uma função.
Se possível, substitua os blocos de recuo aninhados por chamadas de função .
Uma versão menos recuada de `calculateEmployeeSalary` acima:
Para melhorar a legibilidade, uma função deve fazer apenas uma coisa .
As funções longas geralmente fazem várias coisas, o que as torna longas. Para fazer várias coisas, mantendo o código legível, você deve dividir funções longas em várias funções menores.
A função que chama outras funções ainda deve fazer apenas uma coisa, um pouco mais abstrata.
Introduzir vários níveis de abstração
Extraia trechos de código em funções se você puder descrever o comportamento com um nível mais alto de abstração.
Muitas funções de alto nível de abstração são mais fáceis de entender em comparação com algumas funções de baixo nível. As abstrações devem ser baseadas no comportamento do domínio .
Abstrações arbitrárias introduzidas com o propósito de reutilização de código geralmente pioram as coisas. Tente manter as chamadas de uma função no mesmo nível de abstração . Isso às vezes requer funções de linha única apenas para nivelar a abstração, o que é bom (o desempenho não deve ser um problema, pois as linguagens modernas otimizam isso por inlining).

Organizar funções hierarquicamente por níveis faz com que o código conte uma história comportamental em vez de uma receita passo a passo.
As funções devem ter o menor número de argumentos possível . Mais de 2 raramente devem ser usados.
Muitos argumentos tornam o código mais difícil de entender , especialmente ao usar argumentos como saída (argumentos que são modificados após a conclusão da função).
Os argumentos de saída geralmente são inesperados e não naturais, portanto, evite-os.
NOTA : o exemplo anterior usa três argumentos para permanecer o mais semelhante possível com um exemplo relacionado, mas deve ser evitado.
Uma função deve ser um comando que executa uma ação (efeito colateral) ou uma consulta que retorna dados ao chamador , mas não ambos.
Seu “papel” deve ser enfatizado pelo nome. Nunca deve haver um mal-entendido se uma função é um comando ou uma consulta por seu nome.
Exemplo de nome de comando que modifica seu estado de argumentos:
Exemplo de nome de consulta que extrai valores sem modificar o estado dos argumentos:
Os efeitos colaterais devem ser sempre explícitos. Eles nunca devem ser inesperados. Se desnecessário, evite-os. Tente manter as funções “puras”, ou seja, sem estado.
Prefira a imutabilidade
O que “imutabilidade” realmente significa na programação?
É uma propriedade que afirma que um objeto/variável não pode ser modificado após ter sido totalmente criado . O código imutável em vez de um estado mutante de um objeto cria uma cópia com um estado modificado.
Código mutável:
Mesmo comportamento em código imutável:
A imutabilidade torna o código mais simples e fácil de seguir .
Sabendo que um objeto é imutável, você não precisa se preocupar se a função para a qual você está passando o objeto irá modificá-lo. A programação simultânea também se torna mais fácil e limpa.
Objetos imutáveis são inerentemente thread-safe , o que significa que não há necessidade de blocos de sincronização, pois você está sempre trabalhando com um estado consistente imutável.
É claro que, às vezes, a lógica também pode ser artificial, complicada ou de baixo desempenho, e ainda ser expressa como imutável. Nesse caso, você ainda pode usar a mutabilidade, mas tente reduzir seu escopo o máximo possível.
Prefira programação declarativa a imperativa
Uma parte importante da escrita de código que seus herdeiros lerão facilmente é escolher a programação declarativa em vez da imperativa . O que é imperativo e o que é programação declarativa?
A programação imperativa diz como fazer as coisas . Seu foco está na mecânica passo a passo, como instruções condicionais, loops, mutações.
A programação declarativa, por outro lado, diz o que fazer em vez de como fazê-lo . Seu foco está no que deve ser feito, significando ações inteiras em vez de uma explicação passo a passo. A programação declarativa é construída em cima de um estilo passo a passo de programação imperativa.
Tente abstrair o código imperativo para declarativo .
Com o estilo declarativo, a lógica/intenção de negócios se torna muito mais óbvia . Ver a intenção do código à primeira vista torna a manutenção e a correção do código muito mais fáceis de entender. Ele nos permite identificar a parte problemática e depois mergulhar nos detalhes , em vez de ler todo o código primeiro apenas para decifrar sua intenção.
O comportamento é mais óbvio no estilo declarativo, como visto abaixo:
Mantenha-o simples estúpido - KISS
Os desenvolvedores geralmente apresentam soluções “ inteligentes ” para um determinado problema.
Essas soluções, embora pareçam uma dádiva de Deus em determinados momentos, não devem ser preferidas, apesar dos impulsos do ego que podem nos dar. Na maioria das vezes, eles são difíceis de entender e seguir e, por isso, ainda mais difíceis de manter ou atualizar.
A simplicidade deve ser um objetivo chave no design . Evite complexidade desnecessária.
Não se repita – SECA
A duplicação é ruim, especialmente a duplicação de conhecimento. Você deve ter como objetivo ter uma única representação do conhecimento em seu sistema. Se o conhecimento muda, você tem que mudá-lo em cada lugar que for duplicado.
Se você perder uma duplicata, há uma boa chance de que ela se transforme em um bug .
A manutenção torna-se facilmente um pesadelo com muitas duplicatas.
É uma prática comum copiar e colar durante a prototipagem ao trabalhar em um problema complexo. Não há nada de errado com isso, pois nos ajuda a identificar abstrações de conhecimento adequadas, o que leva a expressar melhor a intenção.
Mas, assim como fomos ensinados quando crianças, depois do recreio, é hora de limpar. Essas duplicatas serviram ao seu propósito. Agora é hora de removê-los.
Você não vai precisar disso - YAGNI
Não implemente algo que você “pode” precisar no futuro, implemente apenas o que você precisa . O que “podemos” precisar com muita frequência não é algo que mais tarde realmente precisamos.
A implementação prematura de coisas que “podemos” precisar fecha muitos caminhos pelos quais nosso design pode seguir. Isso nos força com um design não otimizado para as necessidades reais do nosso produto , o que por sua vez nos leva a um design geral pobre e complexo, difícil de manter.
Próximos passos
Os próximos passos que você pode tomar para melhorar são os princípios SOLID .
São cinco princípios de design destinados a tornar os designs de software ainda mais compreensíveis, flexíveis e sustentáveis :
- Princípio de responsabilidade única - SRP
- Princípio aberto-fechado – OCP
- Princípio da substituição de Liskov – LSP
- Princípio de segregação de interface - ISP
- Princípio de inversão de dependência - DIP
Seguindo esses (entre alguns outros) princípios, conseguimos manter a maior parte do código e design de domínio legível, de fácil manutenção e atualizável .
A razão pela qual fizemos isso é para nos preparar para receber novos membros de nossa equipe, principalmente tornando o processo de integração mais eficiente e confortável possível.
Falando em novos membros, confira nossa vaga para Desenvolvedor Java Sênior ou quaisquer outras vagas em nossa página de Carreiras.
