Melhorando a manutenibilidade das aplicações
Autores: Jefferson Carlos Martins e Manoel Flávio Leal
1. Introdução
Martin Fowler descreve a técnica de programação chamada Refactoring como sendo um caminho disciplinado de reestruturação de código-fonte, seja em programação estruturada ou Orientada a Objetos [1]. A principal idéia do Refactoring é fazer pequenas alterações no código com o objetivo de melhorar sua manutenibilidade, aumentando desta forma sua simplicidade, flexibilidade, clareza e desempenho. É importante ter sempre em mente que Refactoring não se trata de adicionar novo código ou novas funcionalidades, mas sim de melhorar os já existentes. Quando aplica-se as técnicas de Refactoring deve-se tomar alguns cuidados, pois ao alterar o código-fonte de um programa há um grande risco de introduzir novos erros, principalmente se estiver realizando uma manutenção não-estruturada, onde o único elemento disponível é o código-fonte e a atividade de manutenção inicia-se com uma penosa avaliação do código, freqüentemente complicada pela documentação interna ruim [2].
Também deve-se tomar cuidado para não confundir Refactoring com Design Pattern.
2. Médodos de Refactoring
Existem noventa e três métodos de Refactoring catalogados por Martin Fowler, e neste artigo serão demonstradas as principais e mais utilizadas técnicas de Refactoring [3].
2.1. Extract Method (extração de métodos)
Este é o principal método de Refactoring, pois é amplamente utilizado por desenvolvedores que começaram suas carreiras na era da programação estruturada e que tiveram de se adequar às técnicas de Orientação a Objetos para reduzir o tempo gasto no desenvolvimento dos projetos, facilitar a manutenibilidade dos programas e aumentar a reusabilidade de partes do código. O Extract Method nada mais é que a transformação de parte do código em um novo método, para aumentar a usabilidade de uma parte ou para aumentar a legibilidade das funções/procedimentos.
Este método deve ser aplicado quando ocorrerem duplicação de linhas de código ou quando as funções/procedimentos forem muito longos e/ou de difícil entendimento, exigindo muitos comentários no código. Mas lembrem-se de sempre colocar um nome para o novo método que explique sua utilidade.
Dicas para a aplicabilidade deste método:
1. Analise seu código periodicamente;
2. Quando encontrar partes de código que possuam grande semelhança entre eles, transforme-o em um método dinâmico (com parâmetros);
3. Quando copiar o código para dentro do novo método, procure por variáveis locais e parâmetros utilizados pelo código extraído;
4. Quando o código extraído utilizar variáveis locais do método de onde ele foi extraído, passe essas variáveis como parâmetros ao novo método;
5. Sempre execute testes em seu código para verificação de possíveis erros gerados.
2.2. Extract Class (extração de classes)
Este método é semelhante ao Extract Method, exceto pelo fato de que ele trata apenas com classes. O Extract Class parte do pressuposto: se uma classe for muito grande, deve-se transformá-la em duas ou mais classes.
Dicas para a aplicabilidade deste método:
1. Somente aplique este método para classes que não fazem parte de nenhum Design Pattern de seu projeto;
2. Não confunda o método Extract Class do Refactoring com Design Pattern;
3. Utilize este método para criação de classes abstratas que podem ser generalizadas ou especializadas por outras classes.
2.3. Inline Method (método na linha)
Este método é o inverso do Extract Method, pois ele prevê a substituição da chamada de uma função/procedimento pela sua implementação, ou seja, pelo código da função/procedimento.
O Inline Method é utilizado para eliminação de chamadas desnecessárias de funções/procedimentos, aumentando assim a performance da aplicação.
Dicas para a aplicabilidade deste método:
1. Quando sua aplicação possuir funções/procedimentos mal organizados dentro do código, aplique o Inline Method em todos eles, depois aplique o Extract Method para melhorar a legibilidade do código;
2. Verifique se o método não é polimórfico ou se as suas subclasses o especializam;
3. Aplique este método para eliminar funções pequenas que não sejam reutilizadas em outras partes do código.
2.4. Replace Inheritance With Delegation (substitua herança por instância)
Este método é utilizado quando uma subclasse usa apenas parte das funcionalidades da sua superclasse ou não necessite herdar dados da mesma, então cria-se um campo para a superclasse e ajusta-se os métodos apropriados para esta, removendo-se a herança.
Deve-se ter em mente que herança é uma técnica excelente, mas não se deve descuidar ao realizar heranças de uma classe, pois em muitos casos pode-se notar que utilizamos apenas alguns métodos ou atributos da superclasse.
Dicas para a aplicabilidade deste método:
1. Crie um campo na subclasse que se refere a uma instância da superclasse e remova a herança;
2. Mude cada método na subclasse para que use a instância da superclasse;
3. Verifique as chamadas de métodos da superclasse.
2.5. Replace Conditional With Polymorphism (substitua condições por polimorfismo)
Este método consiste em dividir uma função/procedimento que possua muitas condições dentro da mesma em duas ou mais funções/procedimentos.
Dicas para a aplicabilidade deste método:
1. Caso exista uma função/procedimento que possua várias condições para funcionamento, divida-a em outras funções/procedimentos, aumentando, assim, sua legibilidade.
3. Aplicando Técnicas de Refactoring
As técnicas de Refactoring devem ser aplicadas durante o ciclo de desenvolvimento de uma aplicação para que o software gerado no fim desse processo seja de fácil manutenibilidade e legibilidade, mas é aconselhável que se utilize essas técnicas na fase de manutenção, onde o desenvolvedor tem a oportunidade de: melhorar o código existente por meio de revisões do código-fonte, refazer parte de um programa para melhorar sua legibilidade e eliminar códigos duplicados para facilitar sua manutenibilidade.
Apesar das técnicas de Refactoring aumentarem a manutenibilidade do software gerado não devemos aplicá-las quando um código estiver muito complexo e o recomendável seja recomeçar do zero, ou quando o projeto estiver muito próximo de sua entrega final, nesse caso é recomendável se terminar o software e nas futuras versões aplicar as técnicas de Refactoring.
A maneira mais simples para a aplicabilidade das técnicas de Refactoring é renomear as variáveis e métodos para nomes que sejam auto-explicativos como, por exemplo, eliminar variáveis com o nome i, j, c, x, y, muito utilizadas para controle/contadores, pelos nomes cont (de contador) ou pos (de posição).
Outra forma seria a limpeza e rearranjo do código, ou seja, dividir grandes partes de códigos em funções/procedimentos e criar variáveis temporárias para a simplificação de expressões complexas.
3.1. Exemplo Prático
Em seguida é apresentado um exemplo de utilização das técnicas de Refactoring, onde foi utilizado parte de um código-fonte de um sistema Web, que antes de se aplicar as técnicas de Refactoring o mesmo possuía 2.710 linhas de código-fonte, sendo que ainda faltavam adicionar algumas funcionalidades e realizar manutenções em pequenos procedimentos. Após a aplicabilidade do Extract Method e do Inline Method, seguindo as orientações de como aplicá-las, esse código ficou com 1.587 linhas e isso depois de adicionar algumas funcionalidades e realizar as manutenções que faltavam.
Exemplo:
Código-fonte antes da aplicabilidade das técnicas de Refactoring
<script language=”JavaScript”>
function validarDados() {
…
valor1590 = docuent.frm.PercRetido400.value;
valor800 = document.frm.PercRetido200.value;
valor100 = document.frm.PercRetido100.value;
valor050 = document.frm.PercRetido050.value;
valor025 = document.frm.PercRetido025.value;
valor0125 = document.frm.PercRetido0125.value;
valor0062 = document.frm.PercRetido0062.value;
valorNR = document.frm.PercRetidoNR.value;
if (valor1590 == “”) {
alert (“Entrada obrigatória para o Diâmetro 15,90.”);
document.frm.PercRetido1590.focus();
return (false); }
if (valor800 == “”) {
alert (“Entrada obrigatória para o Diâmetro 8,00.”);
document.frm.PercRetido800.focus();
return (false); }
…
if (valorNR == “”) {
alert (“Entrada obrigatória para o campo %Retido no Diâmetro NR.”);
document.frm.PercRetidoNR.focus();
return (false); }
…
}
</script>
Neste caso foram renomeados todos os campos PercRetido para que ficassem seqüenciais, depois eliminou-se a duplicidade de linhas de código através do Extract Method transformando aproximadamente 70 linhas em apenas 8 linhas de código.
Código-fonte após a aplicabilidade das técnicas de Refactoring
<script language=”JavaScript”>
function validarDados() {
…
validarPercRetido();
...
}
function validarPercRetido() {
for (pos = 1; pos < 11; pos++) {
if (eval(“document.frm.PercRetido”+pos+”.value”) == “”) {
alert (“Entrada obrigatória para o campo %Retido.”);
eval(“document.frm.PercRetido”+pos+”.focus()”); return false;
}
}
}
</script>
4. Refactoring e Design Patterns
É necessário deixar claro ao leitor que Refactoring e Design Patterns são metodologias diferentes. A tabela abaixo apresenta de forma objetiva o que é necessário entender de cada uma das metodologias.
5. Conclusão
A documentação interna dos sistemas geralmente é ruim ou quase inexistente, pois muitos desenvolvedores estão mais preocupados em desenvolver rapidamente suas aplicações e mostrar produtividade, do que documentar as linhas de código, visando a manutenibilidade futura pelos seus colegas e até mesmo por si próprio.
As novas ferramentas de desenvolvimento com o aumento da demanda de software faz com que as organizações tendam a gastar grande parte de seus recursos humanos com a manutenção de softwares antigos, pois a vida útil do software é muito maior do que a do hardware, e é comum que os softwares sofram manutenções dos tipos: perfectiva (adicionar novas funcionalidades), adaptativa (adequar o programa a novas plataformas de software e hardware) e preventiva (prevenir possíveis erros, ex. bug do milênio). Com isso os desenvolvedores tornam-se escravos das aplicações que criaram, pois além de estar em evolução, existe o fato de as manutenções inferidas poderem introduzir novos erros.
Devido à demanda das manutenções, o que exigia grandes esforços das equipes, foram realizados estudos e catalogadas técnicas de manutenção durante a fase de codificação, chamadas agora de Refactoring.
As técnicas de Refactoring foram divididas em vários métodos para facilitar o entendimento dos desenvolvedores, bem como a comunicabilidade entre os mesmos.
Conclui-se que as técnicas de Refactoring devem ser aplicadas sempre que possível para facilitar o entendimento do código por outros desenvolvedores e com isso tentar diminuir o tempo gasto no entendimento do código.
6. Referências
1. FOWLER, M. et al. Refactoring: improving the design of existing code.[S.l]: Addison-Wesley, 2000.
2. PRESSMAN, R. S. Engenharia de software. São Paulo: Makron Books do Brasil, 1995. 879 p.
3. Refactorings in alphabetical order. Disponível em: <http://www.Refactoring.com/catalog/index.html> Acesso em: 08 out. 2003.