Clean Code: Em direção a um trabalho legível

Clean Code: Em direção a um trabalho legível

No começo da minha jornada como programador, minha principal preocupação era ver o código funcionando conforme especificado. A sensação ainda é indescritível. No entanto, além de funcionar corretamente, o código precisa ser legível.

A maioria dos projetos de software são construídos em equipe, portanto, é essencial que todas as pessoas envolvidas compreendam o código previamente escrito. Inclusive, depois de um tempo, a própria pessoa que escreveu o código pode esquecer detalhes da implementação e precisar ler e compreender o código para incluir alguma melhoria ou fazer manutenção. Por estas e outras razões, este artigo se propõe a demonstrar algumas técnicas que contribuem para melhorar a legibilidade do código.

Sobre aprendizado contínuo

As implementações que serão refatoradas neste artigo foram realizadas à medida que eu aprendia conceitos básicos, portanto, apresentam muitas oportunidades de melhoria em aspectos como legibilidade, performance e escalabilidade, por exemplo. A identificação destas oportunidades reflete a experiência adquirida quase dois anos após a implementação original. Tenho certeza que daqui a dois anos conseguirei pensar em melhorias ainda mais significativas.

Contexto e Regras de Negócio

O código refatorado faz parte de um projeto que construí durante um bootcamp. Trata-se de uma rede social voltada para amantes de vinhos. Todas as interações eram realizadas por meio de confrarias, que permitiam criar postagens, curtidas e agendar encontros presenciais. Vamos refatorar as implementações das funcionalidades de criar confrarias e de adicionar pessoas novas.

Antes de vermos a implementação, vamos falar um pouco sobre as regras de negócio para a criação de confrarias:

  • A pessoa tem a opção de adicionar um ou mais participantes, informando os respectivos emails;
  • A adição de mais participantes  é opcional;
  • Por padrão, quem cria a confraria tem status de chanceler, o qual permite adicionar, excluir pessoas e moderar conteúdo.

A Bagunça

Escrito em JavaScript, utilizando NodeJS e Sequelize, o projeto foi construído utilizando a arquitetura MVCS (Model View Controller + Services). O código que trabalharemos a seguir se refere à implementação do método brotherhoodCreator do BrotherhoodController e ao método addMembers do BrotherhoodService e podem ser vistos abaixo:

Controller brotherhoodCreator

https://codepen.io/jairocket/pen/gOjBEMz

Service addMembers

https://codepen.io/jairocket/pen/mdjzoRx

Confuso, não? Em primeiro lugar, a maior parte da implementação de brotherhoodCreator está replicada em addMembers. Os longos blocos de if e else aninhados dificultam a compreensão da lógica algorítmica. O array fmembers não possui um nome intuitivo, e, por fim, algumas constantes estão declaradas como se fossem variáveis. Muito trabalho, hein?

Don't Repeat Yourself

Este princípio, também conhecido como o acrônimo DRY (ou Não Se Repita), nesse contexto se refere à recomendação de evitar repetição de código. Uncle Bob afirma no Código Limpo que esta prática "pode ser a raiz de todo mal no software".

O fato é que esta prática deixa o código amontoado e dificulta a legibilidade. Em nosso exemplo, a implementação da funcionalidade de adicionar participantes às confrarias está duplicada. Para solucionar este problema, nos casos em que o controller brotherhoodCreator deve adicionar pessoas, deverá fazê-lo chamando o service addMembers.

O problema é que, analisando a implementação original, verificamos que no controller o id da confraria é obtido a partir da confraria criada, enquanto que no service, esta informação é obtida por meio da propriedade params, do objeto req. Para tornar o método reutilizável, tornamos o id da confraria um parâmetro. Desta forma, a informação passa a ser obtida de forma uniformizada. Após as alterações e correção das declarações das constantes, veja como a implementação do método brotherhoodCreator se tornou mais simples e legível:

Agora, facilmente verificamos que o método cria uma nova confraria com base nas informações obtidas do objeto req; registra pessoa que criou a confraria como chanceler; se houver algum e-mail contido em req.body.members, adicionamos à confraria; e, por fim, redireciona a pessoa para a rota da confraria criada.

Quanto ao método do controller responsável por chamar o service addMembers, ficou assim:

Destaco, ainda, a criação de services específicos para a funcionalidade que cria uma nova confraria no banco e para a que dá a pessoa responsável pela criação da confraria o status de chanceler (ou Chancellor):

AddMembers

Agora podemos lidar com o service addMembers. Antes de adicionar mais pessoas na confraria, é preciso realizar os seguintes passos:

  • Verificar se o e-mail informado pertence a alguém já cadastrado naquela confraria;
  • Se todos os emails informados já fazem parte da confraria, nenhuma ação deve ser executada;
  • Se existirem e-mails informados que ainda não faz(em) parte da confraria, é necessário verificar se estes e-mails estão cadastrados no aplicativo;
  • Aos e-mails não cadastrados, será enviado um convite;
  • Os perfis associados aos e-mails cadastrados serão adicionados à confraria, sem status de chanceler.

Como vimos, é possível tentar adicionar um ou mais e-mails de cada vez. Isto implica que o valor de req.params.members pode conter um e-mail (string) ou uma lista de e-mails (array de strings). Na implementação original, lidei com esta questão utilizando um bloco if/else: Se a propriedade contiver um array de e-mails, por meio de um loop for in, o algoritmo acima é executado para cada e-mail contido no array. Caso contrário, o mesmo algoritmo será executado apenas para a string recebida.

Como praticamente o mesmo de código é executado nos blocos de if e else, podemos eliminar o bloco else com a declaração a seguir:


Assim, garantimos que, a partir desta declaração, trabalharemos sempre com array de strings.

Múltiplas Responsabilidades

Outro problema é que o service addMembers tem muitas responsabilidades. O ideal é que cada método realize uma ação específica, evitando efeitos colaterais. Deste modo, podemos criar services específicos para cada uma dessas ações, tornando o código mais legível, escalável e simples de dar manutenção.

Note que ao criar services específicos para cada passo do do algoritmo, começamos a aplicar conceitos de programação declarativa, quando informamos O QUE deve ser feito, sem expor o COMO. Ou seja, no controller informamos que o próximo passo, por exemplo, é adicionar pessoas, mas os detalhes de como estas pessoas serão adicionadas ficarão sob responsabilidade do service e não serão explicitados no controller.

O primeiro service extraído chamaremos de filterNewMembers. Ele recebe uma lista de e-mails e o id de uma confraria qualquer e retorna quais e-mails da lista não estão cadastrados na confraria informada. Tentei usar nomes que sugerem o que a função faz. Se você tiver uma sugestão de nome, adoraria ver nos comentários.

O segundo service chamaremos de inviteNewMembers. Este, por sua vez, recebe um e-mail e um id de usuário(a). Não retorna dados, mas envia um convite para o e-mail informado em nome do(a) usuário(a) correspondente ao id passado como parâmetro.

O terceiro chamarei de fetchUserByEmail, que busca no banco de dados o perfil associado ao email passado como parâmetro:

O quarto e último chamei de addRegularMember, que adiciona um perfil a uma confraria, sem perfil de chanceler:

Deste modo, a implementação final do método addMembers ficou assim:

Recapitulando

O método filterNewMembers filtra os e-mails que pertencem às pessoas que não fazem parte da confraria. Se todos os e-mails informados pertencerem às pessoas que já fazem parte da confraria, a pessoa é redirecionada para página da confraria, sem adicionar ninguém. Havendo algum e-mail não cadastrado, verificamos se aquele e-mail está cadastrado na plataforma. Se sim, a pessoa é adicionada, caso contrário, o e-mail convite é enviado.

Ufa. Neste artigo refatoramos a implementação de código bastante confuso com o objetivo de torná-lo muito mais legível. Para tanto, renomeamos constantes e eliminamos estruturas condicionais aninhadas. Além disso, ao verificar que os métodos realizavam diversas ações além daquela que se propunham, extraímos a implementação destes efeitos colaterais, criando métodos próprios, nomeando-os de forma descritiva.

Para aprofundar no tema, recomendo o livro Código Limpo, do Robert C. Martin e pesquisar sobre Programação Declarativa. Agradeço por ler este artigo até aqui. Espero que te ajude a produzir código mais limpo nos seus projetos. Finalizo citando o Uncle Bob: "se todos deixássemos nosso código mais limpo do que quando o começamos, ele simplesmente não se degradaria".

Saudações.

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.