image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image
Fernando Araujo
Fernando Araujo26/10/2023 13:49
Compartilhe

Refatoração - Como obter um Código Limpo

  • #Arquitetura de Sistemas

Olá, dev!     

Este artigo vai tratar da refatoração de código, mas não aquela mostrada no famoso livro Código Limpo. Aqui serão focados dois livros anteriores, os quais considero os melhores que li durante a minha formação em analista de sistemas na universidade.

           Estes livros são Programming Pearls, 2ª edição, de Jon Bentley e Refactoring – Improving the Design of Existing Code, 2ª edição, de Martin Fowler e Kent Beck.

Sumário 

1.   Introdução 

2.   Como estruturar bem o projeto do seu sistema

3.   Como estruturar melhor seu código

4.   Exemplos

5.   Considerações Finais 

6.   Referências 

 

1 – Introdução

Atualmente, desde o lançamento do livro Código Limpo, de Martin Fowler, está na moda falar de melhora do código fonte dos programas. É claro que é uma ótima decisão para fazer nossos códigos mais legíveis e bem estruturados.

Antes dele, o livro Padrões de Projeto (Design Patterns), de Kent Beck, também mostrava como escrever um código melhor, com base em padrões de codificação que ocorrem com frequência nos códigos dos programas.

Só que esse tema é muito mais antigo do que estes livros apontaram. Desde os anos 70, já havia livros que tratavam do assunto.

Dois dos melhores livros que eu li no início da minha carreira de analista de sistemas datavam dessa época e, acredito que foram as bases de técnicas que hoje estão consagradas por novos livros e enfoques maia atuais.

Para quem pensa que os livros antigos só tratavam de COBOL e FORTRAN, linguagens procedurais e não estruturadas nos seus primórdios, os livros que eu vou citar aqui tiveram seus códigos implementados na linguagem Smalltalk, uma das primeiras linguagens orientadas a objeto.

Naquela época já existia uma preocupação com a estruturação de projetos de software e com a legibilidade e manutenção de códigos mal escritos e desestruturados, principalmente para códigos muito longos e grandes sistemas.

Este artigo vai tratar das técnicas propostas para se escrever projetos de sistemas bem estruturados e mais legíveis, apropriados para uma manutenção facilitada, com base em livros bem antigos, mas que ainda hoje têm suas recomendações válidas, mesmo para linguagens que nem existiam naquela época.

2 – Como estruturar bem o projeto do seu sistema

Segundo Bentley [1], um bom programa não começa com a tentativa do programador de escrever o código para a ideia de problema relatado pelo cliente, que pode estar muito simplificada e enviesada pela forma que o cliente pensou na solução.

A melhor solução aparece quando o programador perde tempo pensando nos detalhes do problema real em si e de como cada parte dele poderia ser implementada de forma simples e direta, usando as ferramentas e técnicas que ele, o programador, conhece.

           Muitas vezes, o programador tem insights que podem reduzir o tamanho do código ou o tempo de processamento, apenas trocando uma técnica por outra. É um compromisso frequente entre o tamanho do código e o desempenho.

           O escritor francês Antoine de Saint-Exupéry, autor do livro “O Pequeno Príncipe”, também era designer aeronáutico e disse: "Um designer sabe que chegou à perfeição não quando não há mais nada a acrescentar, mas quando não há mais nada a tirar."

image

        Bentley diz que os programadores deveriam julgar mais seu trabalho por este critério, pois programas simples geralmente são mais confiáveis, seguros, robustos e eficientes do que programas complexos, e mais fáceis de construir e manter.

            Segundo o autor, o primeiro passo para programar é descobrir o real problema que o cliente quer resolver, não o que ele descreveu. Depois, selecionar o algoritmo que melhor represente o modelo do problema que imaginamos para resolvê-lo.

           Muitas vezes, um algoritmo específico faz a solução do problema parecer trivial. Outras vezes, é a escolha correta de uma estrutura de dados mais adequada à solução que deverá ser implementada.

           Finalmente, ele diz que estes insights só vêm com a experiência do programador. Quanto mais ele acumular ferramentas e se debruçar sobre os problemas e suas soluções, ficará apto a encontrar uma solução trivial para cada problema. O programador pode até se deparar com um pequeno problema, diz ele, mas de difícil solução.

 

O autor dá alguns conselhos para seguir uma boa codificação:

·        Esqueleto (scaffold) - O melhor tipo de esqueleto (protótipo, mock) é geralmente aquele mais fácil de construir. Para algumas tarefas, o mais fácil consiste em uma interface gráfica de usuário implementada em uma linguagem como Visual Basic ou Java. Porém, para muitas tarefas algorítmicas, é mais fácil usar linha de comando, mais simples (e mais portátil);

·        Codificação – É mais fácil rascunhar uma função difícil em um pseudocódigo de alto nível, e, depois, traduzí-la para a linguagem de implementação;

·        Testes - É mais fácil e profundo testar um componente em protótipos do que em um sistema grande;

·        Depuração. A depuração é difícil quando um programa está isolado em seu protótipo e muito mais difícil ainda quando está incorporado em seu ambiente real;

·        Tempo - Se o tempo de execução não importa, é melhor usar uma técnica mais simples (busca linear), mas se o tempo de execução for importante o suficiente, podemos implementar uma complexidade adicional (pesquisa binária).

            O objetivo final do programador é entregar um programa simples e poderoso que encanta seus usuários e não incomoda seus construtores. Programas ineficientes irritam os usuários com longas esperas e oportunidades perdidas. Existem vários caminhos para o desempenho.

            A eficiência pode ser medida, pois se um programa é 2 vezes mais rápido do que outro, não há o que discutir, já as discussões sobre interfaces de usuário, por exemplo, muitas vezes ficam amarradas em gostos pessoais.

           O autor diz que estas etapas são essenciais no processo de busca de melhor desempenho

·        Algoritmos e estruturas de dados

·        Ajuste de algoritmo

·        Reorganização da Estrutura de Dados

·        Ajuste de código independente do sistema

·        Ajuste de código dependente do sistema

·        Hardware

 

3 – Como estruturar melhor seu código

 Segundo Fowler [2], a “refatoração” surgiu quando se procurava melhorar os códigos da linguagem Smalltalk, mas logo foi aplicada a outras linguagens de programação.

 Ela aparece quando os programadores refinam suas hierarquias de classe ou se gabam de quantas linhas de código conseguiram excluir. Nós sabemos que uma estrutura não estará certa na primeira vez, mas ela deve evoluir à medida que eles ganham experiência.

 É fato que um código será lido e modificado com mais frequência do que será escrito e a chave para manter o código legível e modificável é a refatoração de software em geral.

           A refatoração pode ser definida como o processo de alterar um sistema de software de forma a não alterar o comportamento externo do código e melhorar sua estrutura interna. Podemos dizer que é uma maneira disciplinada de limpar código, minimizando as chances de introdução de bugs.

No entanto, refatorar é arriscado, pois exige mudanças ao código que podem introduzir bugs sutis e, se não for feita corretamente, pode atrasar em muitos dias a sua entrega.

A refatoração deve ser feita de forma sistemática. Ela se torna mais arriscada quando praticada informalmente (ad hoc). Você começa a cavar no código e logo descobre novos oportunidades de mudança e continua cavando. Chega uma hora que você está em um buraco do qual você não consegue mais sair.

O livro Design Patterns mostra que os padrões de projeto fornecem alvos para refatorações. No entanto, identificar o alvo é apenas uma parte do problema; transformando seu código para chegar lá é outro desafio.

O autor diz que seu livro explica os princípios e as melhores práticas de refatoração e indica quando e onde você deve começar a pesquisar seu código para melhorá-lo. Ele apresenta um catálogo abrangente de refatorações, cada uma descrevendo a motivação e a mecânica de uma transformação de código comprovada.

Algumas das refatorações, como Extract Method ou Move Field, podem parecer óbvias, mas compreender a mecânica delas é a chave para refatorar de maneira disciplinada. É importante refatorar seu código uma etapa de cada vez, com alterações simples e pequenas, reduzindo os riscos de evolução do seu projeto. E sempre testando após cada refatoração.

No final, a sua confiança no código resultante vai aumentar bem como você vai se sentir menos estressado.

     Vale a pena destacar que a refatoração deve ser feita para o código parecer melhor, não para adicionar alguma funcionalidade nova que ele ainda não tenha. Ela deve ser usada para melhorar a qualidade do software.

      Resumindo, quando você refatora, está melhorando o projeto do código depois que ele foi escrito. No passado, as pessoas acreditavam que se deveria projetar o software primeiro e, só depois de concluído, deveriam codifica-lo

      No entanto, com o tempo, o código será modificado e a integridade do sistema (sua estrutura de acordo com projeto inicial) gradualmente desaparece.

       O código passa da engenharia para o hacking. A refatoração é o oposto dessa prática. Com a refatoração, podemos criar um design ruim, caótico, projetá-lo e retrabalhá-lo em um código bem estruturado.

        Cada passo é simples (até simplista). É mover um campo de uma classe para outra, extrair algum código de um método para transformá-lo em seu próprio método ou enviar algum código para cima ou para baixo em uma hierarquia.

         No entanto, o efeito cumulativo destas pequenas mudanças pode melhorar radicalmente o projeto. É exatamente o inverso da noção de decadência de software.

         Com a refatoração, ao invés de um projeto ser fechado antecipadamente, ele se modifica continuamente durante o desenvolvimento, resultando em um programa cujo projeto permanece bom à medida que o desenvolvimento continua.

4 – Exemplos

    Como exemplo da aplicação de técnicas de refatoração, vamos ver um trecho de código apresentado em [2]:

(...)

for (let perf of invoice.performances) {
   const play = plays[perf.playID];
   let thisAmount = 0;
   switch (play.type) {
       case "tragedy":
            thisAmount = 40000;
            if (perf.audience > 30) {   
                 thisAmount += 1000 * (perf.audience - 30);
            }
            break;
       case "comedy":
            thisAmount = 30000;
                 if (perf.audience > 20) {
                      thisAmount += 10000 + 500 * (perf.audience - 20);
                 }
                 thisAmount += 300 * perf.audience;
            break;

       default:
            throw new Error(`unknown type: ${play.type}`);
   }

   // add volume credits
   
(...) 

 Refatorando o trecho destacado abaixo, com o código do comando switch, é criada uma nova função amountFor( ) - que faz exatamente a mesma coisa:

switch (play.type) {
  case "tragedy":
      thisAmount = 40000;
      if (perf.audience > 30) {   
          thisAmount += 1000 * (perf.audience - 30);
      }
      break;

  case "comedy":
      thisAmount = 30000;
      if (perf.audience > 20) {
          thisAmount += 10000 + 500 * (perf.audience - 20);
      }
      thisAmount += 300 * perf.audience;
      break;

  default:
      throw new Error(`unknown type: ${play.type}`);
}

Função criada amountFor( ):

function amountFor(perf, play) {
let thisAmount = 0;
switch (play.type) {
  case "tragedy":
      thisAmount = 40000;
      if (perf.audience > 30) {
           thisAmount += 1000 * (perf.audience - 30);
      }
      break;
 
      case "comedy":
      thisAmount = 30000;
      if (perf.audience > 20) {
           thisAmount += 10000 + 500 * (perf.audience - 20);
      }
      thisAmount += 300 * perf.audience;
      break;
      default:
           throw new Error(`unknown type: ${play.type}`);
  }
return thisAmount;
}

No programa principal, a chamada a ela fica assim, na terceira linha de código:

(...)
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = amountFor(perf, play);
// add volume credits 
(...)

Exemplo 2 - Refatoração pela técnica Extract Function

Código original

function printOwing(invoice) {
  let outstanding = 0;
  console.log("***********************");
  console.log("**** Customer Owes ****");
  console.log("***********************");
  // calculate outstanding
  for (const o of invoice.orders) {
      outstanding += o.amount;
  }
  
  // record due date
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(),     today.getDate() + 30);
  
  //print details
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
  console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}

Função extraída - printBanner( ) :

function printBanner() {
         console.log("***********************");
         console.log("**** Customer Owes ****");
         console.log("***********************");
}

Chamada da função extraída - printBanner(), na terceira linha de código

function printOwing(invoice) {
  let outstanding = 0;
  printBanner();
 
  // calculate outstanding
  for (const o of invoice.orders) {
      outstanding += o.amount;
  }
 
  // record due date
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
  
  //print details
  console.log(`name: ${invoice.customer}`);  
  console.log(`amount: ${outstanding}`);
  console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}

Veja que os comandos da seção print details também poderiam ser extraídos para outra função individual, printDetails( ), usando uma refatoração semelhante.    

5 - Considerações Finais

Este artigo apresentou diversas técnicas de refatoração de código, tornadas populares pelo livro Código Limpo, mas que já estavam sendo estudadas e catalogadas desde os primeiros anos da programação.

 

É claro que nem todas são usadas em todos os projetos, mas é importante que os novos programadores já as incorporem aos seus estudos e práticas para já começarem a escrever códigos mais legíveis e de fácil manutenção.

 

Por que a gente precisa escrever código bem estruturado se os compiladores modernos já otimizam o código gerado? Bem, o computador vai receber um código bem otimizado para aquele processador específico, mas um código binário, executável. Um computador não entende seu código-fonte, nem recebe esse tipo de código.

 

O código-fonte deve ser escrito pensando em quem vai lê-lo, um humano. Outro programador é quem vai ler seu código e dar manutenção. Para isso, ele tem que entender facilmente o que você escreveu lá. Além disso, seu código não deve ter coisas mirabolantes, insights que nem você entenderá porque usou aquela lógica tão complicada quando rever seu próprio código no futuro.

 

Um pouco de estrutura e padronização na codificação também é muito bom para a empresa, pois vários programadores seguirão as mesmas regras consagradas estabelecidas de codificação. Portanto, da próxima vez que codificar, lembre-se que outro humano é quem vai ler seu código-fonte, não o computador!

 

Finalmente, eu gostaria de recomendar mais alguns livros antigos relevantes relacionados ao tema tratado neste artigo:

 

·        Jon Bentley, More Programming Pearls – Confessions of a Coder, Addison Wesley, 1988.

 ·        Steve McConell, Code Complete. Microsoft Press, 2004.

 

6 – Referências

 

[1] Jon Bentley, Programming Pearls, 2nd edition, Addson Wesley, 2000.

 

[] Martin Fowler, Kent Beck, Refactoring – Improving the Design of Existing Code, 2nd edition, Addson Wesley, 2019.

Compartilhe
Recomendados para você
Microsoft 50 Anos - Prompts Inteligentes
Microsoft 50 Anos - GitHub Copilot
Microsoft 50 Anos - Computação em Nuvem com Azure
Comentários (3)
Fernando Araujo
Fernando Araujo - 27/10/2023 09:17

Obrigado, pessoal!

Eu procurei falar do tema, mas fugir um pouco da moda do livro Código Limpo.

É um ótimo livro, essencial à leitura doe qualquer programador, mas eu também acho que todos nós devemos conhecer o que os livros clássicos e autores renomados escreveram sobre o assunto no passado.




Luiz Café
Luiz Café - 26/10/2023 15:32

Olá, Fernando, parabéns pelo seu artigo! Você escreveu muito bem sobre um tema de fundamental importância dentro do universo clean code. As referências e o código ajudam ainda mais a entender e aprimorar as habilidades de escrita de códigos.


Talita Santos
Talita Santos - 27/10/2023 00:31

Nossa, muito bom! Parabéns!