Melhorando a Qualidade de Código com a Técnica de Refatoração - Estudo de Caso em um Sistema...
-
Upload
anderson-lemos-camelo -
Category
Documents
-
view
190 -
download
0
description
Transcript of Melhorando a Qualidade de Código com a Técnica de Refatoração - Estudo de Caso em um Sistema...
CENTRO UNIVERSITÁRIO CESMAC CURSO DE PÓS-GRADUAÇÃO “LATO SENSU”
ENGENHARIA DE SOFTWARE
MELHORANDO A QUALIDADE DO CÓDIGO COM A
TÉCNICA DE REFATORAÇÃO: estudo de caso em um sistema legado Java
Anderson Lemos Camelo
Maceió/AL
2013
Anderson Lemos Camelo
MELHORANDO A QUALIDADE DO CÓDIGO COM A
TÉCNICA DE REFATORAÇÃO: estudo de caso em um sistema legado Java
Maceió/AL
2013
Anderson Lemos Camelo
MELHORANDO A QUALIDADE DO CÓDIGO COM A
TÉCNICA DE REFATORAÇÃO: estudo de caso em um sistema legado Java
Artigo apresentado ao Centro
Universitário Cesmac como parte dos
requisitos para a obtenção do título de
Especialista em Engenharia de Software,
sob a orientação do Prof. Me. Adilson
Jorge dos Santos.
Maceió/AL
2013
Anderson Lemos Camelo
MELHORANDO A QUALIDADE DO CÓDIGO COM A
TÉCNICA DE REFATORAÇÃO: estudo de caso em um sistema legado Java
Aprovado em 30 de setembro de 2013
__________________________________________
Prof. Me. Adilson Jorge dos Santos
- Orientador -
Maceió/AL
2013
AGRADECIMENTOS
Agradeço, primeiramente, a Deus por sempre se colocar como uma ponte para que eu
pudesse transpor as águas agitadas do caminho pelo qual trilhei.
Agradeço a minha família pelo amor e cumplicidade durante todos os momentos de
minha vida.
Agradeço ao professor Adilson Santos, meu orientador, por incentivar o
desenvolvimento do tema escolhido e colaborar com a conclusão deste trabalho.
Por último, mas não menos importante, agradeço a todas as pessoas que duvidaram da
minha capacidade e colocaram meu talento a prova.
“Tudo que está no plano da realidade já foi sonho
um dia.”
Leonardo da Vinci
SUMÁRIO
1 INTRODUÇÃO ........................................................................................... 1
2 FUNDAMENTAÇÃO TEÓRICA ............................................................... 3
2.1 REFATORAÇÃO ........................................................................................ 3
2.2 CÓDIGO LIMPO ......................................................................................... 4
2.3 ODORES DE CÓDIGO ............................................................................... 5
2.3.1 CÓDIGO DUPLICADO .............................................................................. 5
2.3.2 MÉTODO LONGO ...................................................................................... 6
2.3.3 CLASSE GRANDE ..................................................................................... 6
2.3.4 LISTA DE PARAMETROS LONGA ......................................................... 7
2.3.5 INVEJA DE DADOS ................................................................................... 7
2.3.6 COMENTÁRIOS ......................................................................................... 9
2.3.7 PROPÓSITO OBSCURO ............................................................................ 9
2.4 PROJETO SIMPLES ................................................................................... 11
2.5 MÉTRICAS DE CÓDIGO FONTE ............................................................. 12
3 METODOLOGIA ........................................................................................ 13
4 RESULTADOS E DISCUSSÃO ................................................................. 14
4.1 ARQUITETURA DO SISTEMA REVERSI ............................................... 14
4.2 REFATORAÇÃO DO SISTEMA REVERSI .............................................. 15
4.2.1 PORQUE REFATORAR ............................................................................. 15
4.2.2 O PRIMEIRO PASSO DA REFATORAÇÃO ............................................ 16
4.2.3 O PROCESSO DE REFATORAÇÃO ......................................................... 18
4.2.4 QUANDO PARAR DE REFATORAR ....................................................... 23
4.3 MÉTRICAS DO SISTEMA REVERSI ....................................................... 24
5 CONCLUSÃO ............................................................................................. 26
REFERÊNCIAS ........................................................................................................ 26
LISTA DE ILUSTRAÇÕES
Figura 1 - Exemplo de refatoração de uma lista de parâmetros longa ....................... 7
Figura 2 - Exemplo de refatoração de um código “acidente de trem” ....................... 8
Figura 3 - Exemplo de inveja de dados que não viola a Lei de Demeter .................. 9
Figura 4 - Exemplo de código com propósito obscuro .............................................. 10
Figura 5 - Exemplo de código legível ........................................................................ 11
Figura 6 - Diagrama de pacotes do Reversi ............................................................... 15
Figura 7 - Particionamento de equivalência para o Reversi ....................................... 16
Figura 8 - Conjunto de testes automáticos do Reversi ............................................... 17
Figura 9 - Método buscaJogadaComputador com vários odores ............................. 19
Figura 10 - Refatoração do antigo método buscaJogadaComputador ...................... 19
Figura 11 - Método de fábrica obterComputador ...................................................... 20
Figura 12 - Inveja de dados do objeto objControle ................................................... 20
Figura 13 - Responsabilidade reposicionada na classe ControladorDoJogo ............ 21
Figura 14 - Código “Acidente de trem” ao chamar método getMatPedrasTabuleiro 21
Figura 15 - Refatoração do Acidente de Trem do método getMatPedrasTabuleiro . 22
Figura 16 - Muitas linhas de código no método executaJogada ............................... 23
Figura 17 - Refatoração do método executaJogada .................................................. 23
Figura 18 - Métricas do projeto Reversi .................................................................... 25
1
MELHORANDO A QUALIDADE DO CÓDIGO COM A TÉCNICA DE
REFATORAÇÃO: ESTUDO DE CASO EM UM SISTEMA LEGADO JAVA
Anderson Lemos Camelo
Adilson Jorge dos Santos (Orientador)
RESUMO: Este artigo mostra como a técnica de refatoração pode ser empregada para melhorar a
qualidade de código de sistema legado desenvolvido em Java. Serão mostrados todos os passos da
metodologia necessários para aplicação da refatoração com segurança. Também será feita uma
exploração do sistema legado com o intuito de identificar os principais trechos de código ruim e a
melhor estratégia para transformá-lo em um código limpo. Ao fim da discussão, serão levantados vários
dados estatísticos do sistema legado, antes e depois da refatoração, para mensurar os ganhos de
qualidade de código advindos do uso desta técnica.
PALAVRAS-CHAVES: Refatoração; Código Legado; Linguagem de Programação Java; Código
Limpo; Testes Automatizados.
ABSTRACT: This article aims to show how the refactoring technique can be used to improve the
quality of a legacy code in a system developed in Java. We will show all the methodology steps needed to
safely implement the refactoring. Also there will be an exploration of the legacy system in order to
identify key pieces of bad code and the best strategy to turn them into clean code. At the end of the
discussion, we will gather statistical data from the legacy system, before and after refactoring, to
measure the improvement in code quality from the use of this technique.
KEYWORDS: Refactoring; Legacy Code; Java Programming Language; Clean Code; Automated
Testing.
1 INTRODUÇÃO
Quando a aplicação da tecnologia de objetos, e particularmente à linguagem de
programação Java, se tornou corriqueira, um novo problema surgiu para a comunidade
de software. Um número significativo de programas pobremente projetados, criados por
desenvolvedores menos experientes e que faziam uso de metodologias consideradas
2
antiquadas, resultavam em aplicações ineficientes e difíceis de manter e estender. Os
profissionais de desenvolvimento de sistemas acabaram descobrindo como é difícil
trabalhar com aplicações “não-ideais” herdadas ou legadas (FOWLER et al., 2004).
Segundo Feathers (2004), o conceito de sistemas de software legados não se
restringe apenas aos sistemas que possuem muito tempo de vida, continuam em
produção e se mantém em uso, mas principalmente aos sistemas desenvolvidos
recentemente e que apresentam problemas de qualidade que podem impedir sua
evolução ou até gerar erros graves após uma simples manutenção. Normalmente estes
sistemas não podem ser substituídos por diversas razões, o que os tornam caros quanto à
manutenção e trazem muitos riscos no momento de sua evolução.
Devido a estes problemas torna-se necessário melhorar a qualidade desses
sistemas legados podendo ser uma forma interessante de mantê-los ativos, tornando-os
mais uma vez produtivos e permitindo que melhorias e evoluções sejam efetuadas com
menos riscos. Consequentemente é gerado uma maior confiabilidade e segurança a cada
evolução que se faça necessária. O incremento de novas funcionalidades nos sistemas
legados com maior qualidade permite que a empresa tenha vantagens competitivas sem
grandes investimentos de novos desenvolvimentos, gerando lucros, permitindo também
a integração com novos sistemas (SARTORELLI, 2007). Logo, para melhorar a
integridade estrutural e o desempenho destes sistemas é necessário recorrer a técnica de
refatoração.
A refatoração foi inicialmente concebida nos círculos de Smalltalk, mas não
demorou muito a encontrar o seu caminho em outras linguagens de programação.
Devido ao fato de refatoração ser essencial ao desenvolvimento de frameworks, o termo
vem à tona rapidamente quando desenvolvedores de frameworks falam sobre seu
trabalho. Ele surge quando eles refinam suas hierarquias de classes e quando se
empolgam com o número de linhas de código que conseguiram apagar.
Desenvolvedores de frameworks sabem que um framework não estará pronto na
primeira tentativa - ele deve evoluir à medida em que eles ganham experiência. Eles
também sabem que o código será lido e modificado mais frequentemente do que será
escrito. A chave para manter o código legível e modificável é refatorar - para
frameworks, em particular, ou para qualquer outro software, de maneira geral
(FOWLER et al., 2004).
3
No entanto, o processo de refatorar requer alterações no código em
funcionamento que podem introduzir falhas sutis. A refatoração, se não for feita
apropriadamente, pode atrasar o projeto em dias, ou até mesmo semanas. E a
refatoração se torna mais arriscada ainda quando praticada informalmente e sem seguir
nenhuma metodologia aceita. O programador começa a alterar o código. Logo descobre
novas oportunidades de mudanças, e altera ainda mais o código. Quanto mais ele
trabalha, mais coisas aparecem e mais alterações são feitas (FOWLER et al., 2004). No
final,o programador não consegue mensurar o impacto das mudanças realizadas no
funcionamento do sistema. Para evitar este cenário de incerteza, a refatoração deve ser
feita sistematicamente.
O objetivo deste trabalho é apresentar a técnica de refatoração sob uma
perspectiva simples e prática, visando melhorar a qualidade de código de um sistema
legado de forma evolutiva e garantindo segurança durante o processo de manutenção.
Será mostrado como aplicar os principais passos da metodologia e as melhores
estratégias para transformar um código ruim em um código bom, utilizando como
estudo de caso o sistema legado Reversi, desenvolvido na linguagem Java.
2 FUNDAMENTAÇÃO TEÓRICA
Nesta seção será apresentada a fundamentação teórica levantada sobre os
principais conceitos necessários para o estudo deste trabalho. Será abordada uma breve
explanação sobre refatoração, código limpo, odores de código, projeto simples e
métricas de código fonte.
2.1 REFATORAÇÃO
Refatoração é o processo de alteração de um sistema de software de modo que o
comportamento externo do código não mude, mas que sua estrutura interna seja
melhorada. É uma maneira disciplinada de aperfeiçoar o código que minimiza a chance
de introdução de falhas. Em essência, quando se usa refatoração, está se melhorando o
projeto do código após este ter sido escrito (FOWLER et al., 2004).
O uso da técnica de refatoração aprimora o design de um software e evita a
deterioração tão comum durante o ciclo de vida de um código. Esta deterioração é
4
geralmente causada por mudanças com objetivos de curto prazo ou por alterações
realizadas sem a clara compreensão da concepção do sistema (WIKIPÉDIA, 2013a).
Com a refatoração, descobre-se que o ponto de equilíbrio do trabalho muda.
Descobre-se que o projeto, em vez de acontecer todo no inicio, ocorre continuamente
durante o desenvolvimento. Aprende-se, com a construção do sistema, a como melhorar
o projeto. A interação resultante leva a um programa que permanece bom à medida em
que o desenvolvimento continua (FOWLER et al., 2004).
Segundo Fowler et al. (2004), é desejado que os programas que sejam fáceis de
ler, que tenham toda sua lógica especificada em um e apenas um lugar, que não
permitam que as alterações arrisquem o comportamento preexistente e que permitam
que lógica condicional seja expressa da forma mais simples possível. Logo, refatorar é
processo de pegar um programa em produção e agregar a ele valor, não por meio da
alteração de seu comportamento, mas dando a ele mais destas qualidades que nos
permitem continuar desenvolvendo rapidamente.
Durante a fase de refatoração, pode-se aplicar qualquer conceito sobre um bom
projeto de software. Pode-se aumentar a coesão, diminuir o acoplamento, separar
preocupações, modularizar as preocupações do sistema, reduzir o tamanho das classes e
funções, escolher nomes melhores, além de poder remover a duplicação, garantir a
expressividade do código e minimizar o número de classes e métodos (MARTIN,
2009).
2.2 CÓDIGO LIMPO
A definição de um bom código não é precisa. Do mesmo modo que enfrenta-se
dificuldades para definir o que é arte, não se pode definir um conjunto de parâmetros
lógicos e mensuráveis para delimitar a diferença de qualidade entre códigos-fonte. Pode-
se considerar aspectos como testabilidade, eficiência, facilidade de modificação, processo
pelo o qual foi desenvolvido, entre outros.
No livro Clean Code (MARTIN, 2009), o autor entrevistou grandes especialistas
em desenvolvimento de software como Ward Cunningham (colaborador na criação do
Fit, do Wiki e da Programação Extrema (BECK, 1999)) e Dave Thomas (fundador da
OTI - Object Technology International - e muito envolvido no Projeto Eclipse)
questionando-os quanto a uma definição para código limpo. Cada um dos entrevistados
5
elaborou respostas diferentes, destacando características subjetivas, como elegância,
facilidade de alteração e simplicidade, e outras puramente técnicas, incluindo a falta de
duplicações, presença de testes de unidade e de aceitação e a minimização do número de
entidades (ALMEIDA; MIRANDA, 2010).
Em certo sentido, um código limpo está inserido em um estilo de programação
que busca a proximidade a três valores: expressividade, simplicidade e flexibilidade. Tais
termos são utilizados por Kent Beck no livro Implementation Patterns (BECK, 2007)
que estão em conformidade com a unidade de pensamento que permeia as respostas dos
especialistas (ALMEIDA; MIRANDA, 2010).
2.3 ODORES DE CÓDIGO
Segundo a Wikipédia (2013b), odores de código (do inglês bad smell) é qualquer
sintoma no código fonte de um programa que possivelmente indique sérios problemas
em sua concepção. Geralmente não são erros - eles não são códigos tecnicamente
incorretos e não impedem o correto funcionamento do programa. Em fez disto, eles
indicam deficiências no projeto que podem vir a atrasar o desenvolvimento ou aumentar
o risco de bugs e falhas no futuro. Odores de código são heurísticas para indicar quando
refatorar, e quais as técnicas específicas de refatoração devem ser usadas.
A seguir, será feita uma breve explanação sobre os odores de código mais
comuns: código duplicado, método longo, classes grandes, lista de parâmetros longa,
inveja de dados, comentários e propósito obscuro de variáveis e métodos.
2.3.1 CÓDIGO DUPLICADO
Como o próprio nome diz, código duplicado é, simplesmente, o mesmo trecho
de código repetido em vários outros lugares como classes e métodos. É considerado o
pior odor de um código, pois quando a lógica do trecho duplicado muda, é necessário
alterá-la em diversos pontos sistema. Segundo Martin (2009), autores como Dave
Thomas e Andy Hunt o chamaram de princípio do Não Se Repita (do inglês DRY -
Don't Repeat Yourself), enquanto Kent Beck o tornou como o centro dos princípios da
eXtreme Programming (XP) e o chamou de “Uma vez, e apenas uma”.
A forma mais obvia de duplicação é quando você possui blocos de código
idênticos, como se alguns programadores tivessem saído copiando e colando o mesmo
6
código várias vezes. Nestes casos, deve-se substituir estes trechos por métodos. Uma
das formas mais simples de duplicação são as estruturas aninhadas de switch/case e
if/else que aparecem repedidas vezes em diversos módulos, sempre testando as mesmas
condições. Neste caso, deve-se substituir pelo polimorfismo (MARTIN, 2009).
2.3.2 MÉTODO LONGO
O odor de método longo consiste em métodos que comportam grandes trechos
de códigos, geralmente realizam mais de uma ação, atuam em vários níveis de
abstração, contém um número elevado estruturas condicionais e laços de repetição
aninhados, é narrado por meio de comentários de código, entre outras coisas.
Segundo Almeida e Miranda (2010), o ideal é que cada método seja pequeno o
suficiente para facilitar sua leitura e compreensão. Deve-se ter em vista a dificuldade de
assimilação de grandes porções de informação durante a leitura e o fato de que nem
sempre uma instrução é clara. A partir dessas idéias, um método será uma porção curta
de código que trabalha com poucas variáveis e tem um nome explicativo que espelha a
sua funcionalidade.
Segundo Fowler et al. (2004), uma boa técnica para decompor métodos longos é
olhar para seus comentários. Um bloco de código com um comentário que lhe diz o que
ele faz pode ser substituído por um método cujo nome seja baseado no comentário.
Além deste, expressões condicionais, condições e laços também podem ser
decompostos em métodos menores.
2.3.3 CLASSE GRANDE
De maneira análoga, o odor de classe grande apresenta as mesmas deficiências
de design de código que o odor de método longo. Logo, do mesmo modo que considera-
se importante limitar a quantidade de informação que um método transmite ao leitor,
quer-se que as classes sejam o menor possível. Além de facilitar a leitura e
entendimento, programar buscando minimizar o tamanho das classes leva-se a criar
unidades coesas e a evitar duplicações.
Segundo Fowler et al. (2004), o tamanho de uma classe deve ser definido através
do Principio da Responsabilidade Única (do inglês SPR - Single Responsability
7
Principle). Este principio afirma que uma classe ou módulo deve ter apenas uma
responsabilidade e apenas um motivo para mudar.
2.3.4 LISTA DE PARAMETROS LONGA
O número de argumentos de um método se torna bastante importante quando se
quer métodos pequenos e com apenas uma tarefa. Se um método recebe muitos
argumentos, provavelmente os utiliza para um conjunto de operações e não uma
somente.
Listas de parâmetros longas são difíceis de entender, porque se tornam
inconsistentes e difíceis de usar e porque você irá sempre alterá-las à medida que
precisar de mais dados. A maioria das alterações é removida passando-se objetos,
porque é muito mais provável que você precise fazer apenas algumas solicitações para
chegar em um novo dado (FOWLER et al., 2004).
Figura 1 - Exemplo de refatoração de uma lista de parâmetros longa
Fonte: Autoria própria, baseada em Martin (2009)
2.3.5 INVEJA DE DADOS
Segundo Martin(2009), os métodos de uma classe devem ficar interessados
apenas nas variáveis e funções da classe a qual eles pertencem, e não nas de outras
classes. Quando um método usa métodos de acesso e de alteração de algum outro objeto
para manipular os dados dentro deste objeto, o método inveja o escopo da classe
daquele outro objeto. Ele queria estar dentro daquela outra classe de modo que pudesse
ter acesso direto às variáveis que está manipulando. É o que chamamos inveja de dados
(do inglês, Feature Envy), um dos odores mais comuns em códigos que apresentam
problemas na divisão de responsabilidades entre as classes e alto acoplamento.
8
Um exemplo clássico deste odor é o código conhecido como “acidente de trem”
(do inglês, train wreck), onde uma série de métodos do getters são chamadas de forma
encadeada, como os vagões de um trem.
Figura 2 - Exemplo de refatoração de um código “acidente de trem”
Fonte: Autoria própria, baseada em Freeman e Pryce (2012)
O acidente de trem é uma violação clássica do estilo “Diga, Não Pergunte” (do
inglês, Tell, Don’t Ask) ou mais formalmente, Lei de Demeter (do inglês, The Law of
Demeter). Segundo Almeida e Miranda (2010), esta lei diz que um método “M” de uma
classe “C” só deveria chamar um método:
da própria classe;
de um objeto criado por M;
de um objeto passado como argumento para M;
de um objeto guardado em uma variável de instância de C.
Seguido consistentemente, este estilo produz código mais flexível, porque ele é
mais fácil de trocar objetos que executam o mesmo papel. O chamador não vê nada da
estrutura interna deles ou da estrutura do resto do sistema que está por trás da interface
do papel do objeto chamado. Além disto, ele força a tornar explícitas e então nomear as
interações entre os objetos, ao invés de deixá-las implícitas na cadeia de getters
(FREEMAN; PRYCE, 2012).
No entanto, existem alguns casos onde um método mesmo sem violar a Lei de
Demeter, tem um grande acoplamento com outra classe. O exemplo da figura 3 não viola
esta, pois faz chamadas somente a métodos de um objeto que lhe foi passado por
parâmetro. Como se pode-se ver, o método salarioDoMes possui um grande
9
acoplamento com a classe Empregado ao utilizar diversos de seus métodos, de forma que
fica caracterizado uma inveja de dados.
Figura 3 - Exemplo de inveja de dados que não viola a Lei de Demeter
Fonte: (ALMEIDA; MIRANDA, 2010)
2.3.6 COMENTÁRIOS
Um dos odores mais polêmicos é o de comentários. Na verdade, um comentário
bem feito será sempre bem vindo. No entanto, segundo Fowler et al. (2004), é
surpreendente a frequência com que se veem códigos cheios de comentários e percebe-se
que eles estão lá porque os códigos são ruins. Neste contexto, os comentários são
utilizados como desodorante de código que cheira mal.
Segundo Martin (2009), os comentários tendem a mentir para os leitores do
código onde eles foram fixados. Nem sempre, e não intencionalmente, mas é muito
comum. Quanto mais antigo um comentário for e quanto mais longe ele estiver do
código o qual ele descreve, mais provável será que esteja errado. O motivo é simples: é
pouco provável que os programadores consigam mantê-los atualizados.
Segundo Fowler et al. (2004), uma boa heurística para evitar o mal uso de
comentários: sempre que se sentir necessidade de escrever um comentário, deve-se
experimentar primeiro tornar o trecho de código mais legível de modo que qualquer
comentário se torne supérfluo.
1.3.7. PROPÓSITO OBSCURO
Segundo Boswell e Foucher (2009), códigos devem ser escritos de modo a
minimizar o tempo necessário para sua compreensão. É o chamado Teorema
Fundamental da Legibilidade. Segundo Martin (2009), nomes em softwares são 90%
responsáveis pela legibilidade do mesmo. Precisa-se tomar seu tempo para escolhê-los
10
sabiamente e mantê-los relevantes. Nomes são muito importantes para serem tratados de
qualquer jeito.
Chama-se de propósito obscuro (do inglês, Obscured Intent), um conjunto de
pequenos odores que degradam a legibilidade do código fonte, principalmente, no que
se refere a nomes de variáveis e métodos que não revelam seu propósito facilmente,
como por exemplo, códigos que apresentam os “números mágicos”, ou seja, números
cujo significado não está claro; como também a notação Húngara, que consiste em
nomear as variáveis com o prefixos que demonstram o seu tipo.
Um bom exemplo de código com propósito obscuro é o mostrado na figura 4. É
bem provável que um desenvolvedor perca bastante tempo para conseguir entender a
lógica contida neste método. Isto, simplesmente, porque o desenvolvedor não se
esforçou para nomear as variáveis de forma clara.
Figura 4 - Exemplo de código com propósito obscuro
Fonte: (MARTIN, 2009)
Por outro lado, o código exibido na figura 5 foi escrito de uma forma bem mais
legível. Fica claro a intenção do desenvolvedor em calcular a pontuação de uma partida
de boliche.
11
Figura 5 - Exemplo de código legível
Fonte: (MARTIN, 2009)
2.4 PROJETO SIMPLES
Segundo Sommerville (2007), a insatisfação com abordagens pesadas de
desenvolvimento de software, que focam principalmente no planejamento, no projeto e
na documentação de um sistema, levou um número de desenvolvedores de software da
década de 1990 a propor novos métodos, denominados Ágeis. Estes permitiam que a
equipe de desenvolvimento se concentrasse no software somente, em vez de em seu
projeto e documentação. Geralmente, os Métodos Ágeis contam com uma abordagem
iterativa para especificação, desenvolvimento e entrega de software, e foram criados
principalmente para apoiar o desenvolvimento de aplicações de negócios nas quais os
requisitos de sistema mudam rapidamente durante o processo de desenvolvimento. Eles
destinam-se a entregar um software de trabalho rapidamente aos clientes, que podem
então propor novos requisitos e alterações a serem incluídos nas iterações posteriores do
sistema.
Provavelmente, o nome mais conhecido do movimento ágil seja o de Kent Beck,
idealizador do método ágil conhecido como Extreme Programming (XP). Durante a
criação do conceito de XP, Beck propôs um método para criação de software bem
projetado conhecido como Projeto Simples. Segundo ele, um projeto é “simples” se
seguir, em ordem de relevância, as seguintes regras (MARTIN, 2009):
a) Efetuar todos os testes;
b) Sem duplicação de código;
12
c) Expressar o propósito do programador;
d) Minimizar o número de classes e métodos.
Efetuar todos os testes significa utilizar a metodologia de desenvolvimento
guiada por teste (TDD), que consiste em criar testes automáticos para cada nova
funcionalidade antes mesmo de escrever seu código.
Programar utilizando essa metodologia torna os sistemas passíveis de testes, isso
nos direciona a um projeto que segue o Princípio da Responsabilidade Única, ou seja,
um projeto no qual as classes sejam pequenas e de único propósito. Classes que seguem
esse princípio geralmente são mais fáceis de testar. Logo, quanto mais testes se criar,
mais serão direcionados a construir coisas mais simples de serem testadas.
Similarmente, o forte acoplamento dificulta a criação de testes, quanto mais
testes se criar, mais será usado os princípios como o Princípio da Inversão de
Dependência, que diz que as classes devem depender de abstrações e não de detalhes
concretos, e frameworks de injeção de dependência de modo a minimizar este
acoplamento.
Segundo Martin (2009), ao seguir primeira regra do Projeto Simples, estarão
preparados para atingir as demais regras, ou seja, ao dispor de um conjunto de testes, é
possível manter o código de um sistema limpo, para isso basta recorrer à técnica de
refatoração.
2.5 MÉTRICAS DE CÓDIGO FONTE
Segundo Almeida e Miranda (2010), as métricas permitem criar mecanismos
automatizáveis para detecção de características obtidas através da análise do código-
fonte. Elas podem ser usadas como pontos de avaliação da qualidade do software.
Existem dois tipos de métricas de código-fonte. Algumas avaliam características de
métodos e outras de classes. As métricas de classe são normalmente somas ou médias
dos valores das métricas de métodos. Dentre as métricas selecionadas para este trabalho,
pode-se citar:
Complexidade Ciclomática de McCabe: calcula o número de caminhos
linearmente independentes (ou fluxos) de um método. Quanto menor o valor
desta métrica, mais fácil de assimilar será o código;
13
Número de Classes: calcula o número de classes do projeto. Quanto maior o
valor desta métrica, mais provável será a alta coesão e a divisão de
responsabilidades entre as classes;
Total de Linhas de Código: calcula o número de linhas de código de uma classe.
Quanto menor o valor desta métrica, mais provável será a alta coesão e a divisão
de responsabilidades entre as classes;
Linha de Código por Método: calcula o número de linhas de código de um
método. Quanto menor o valor desta métrica, mais legível e menos coisas fará o
método.
3 METODOLOGIA
Como estudo de caso deste trabalho, foi escolhido o aplicativo Reversi, em sua
versão 0.25 Beta. Reversi é um aplicativo criado exclusivamente para fins acadêmicos,
como parte de um trabalho de conclusão de curso de graduação (CAMELO, 2011).
Escrito na linguagem Java, ele consiste na implementação para computador do jogo de
estratégia de tabuleiro de mesmo nome, mas que também pode ser conhecido como
Othello em algumas regiões. Ele foi escolhido principalmente por ser classificado como
software legado, ou seja, que não possui testes automatizados; além disso, ele não foi
construído com boas práticas de projeto, tornando-o um ambiente rico em bad smells; e
possuir uma quantidade de linhas de código relativamente pequena, mas adequada para
um trabalho como o que se está desenvolvendo.
Como ambiente de desenvolvimento, foi utilizada a IDE Eclipse Java EE for
Web Developers em sua versão Juno. A fim de obter métricas para esta pesquisa, foram
instalados neste ambiente os plug-ins EclEmma Java CodeCoverage for Eclipse, versão
2.2.1, utilizado para mensurar a quantidade de código coberta por testes; e Metrics for
Java projects, versão 1.3.6, utilizado para realizar análise estática do código e gerar uma
série de estatísticas da qual se fará uso da complexidade ciclomática de McCabe, do
número de linhas de código por método, do total de linhas de código das classes e
número de classes do projeto.
Na fase de codificação, foram utilizados os frameworks JUnit, versão 4.8.1,
usado para dar suporte a criação de testes automatizados da linguagem de programação
14
Java; e o WindowLicker, versão 1.0.0, usado em conjunto com o JUnit para dar suporte
a testes de aceitação através da interface de usuário Swing.
Por fim, o próprio processo de refatoração como um todo pode ser considerado
como parte da metodologia, isto por que será necessário seguir um conjuntos de
pequenos passos e boas práticas para se obter sucesso ao aplicar a técnica em qualquer
projeto. Será apresentado este processo, desde a criação de um conjunto sólido de testes
até a detecção dos principais odores de código e a respectiva heurística utilizada para
refatoração do mesmo.
4 RESULTADOS E DISCUSSÃO
Esta seção tem como objetivo apresentar o sistema legado escolhido como
estudo de caso deste trabalho, explicar suas principais características dando ênfase em
aspectos da sua arquitetura e qualidade de código.
4.1 ARQUITETURA DO SISTEMA REVERSI
Do ponto de vista de projeto e arquitetura, a aplicação Reversi é classificada
como Desktop e apresenta características modestas se comparada com os padrões atuais
de desenvolvimento. Mesmo apenas se passado pouco mais de dois anos desde o seu
lançamento, ela aparenta ser bem mais antiga, pois foi construída por desenvolvedores
inexperientes, que não faziam uso de uma metodologia de desenvolvimento, utilizando-
se somente o kit de desenvolvimento Java, em sua versão 1.6.0, e não faz uso de
nenhuma API ou framework robusto. Dado estes fatos, já poderíamos facilmente
classificá-la como um sistema legado. No entanto, segundo Feathers (2004), existe uma
importante característica que já enquadraria esta aplicação como legada: a ausência de
testes automáticos.
A arquitetura está dividida em três camadas principais - Gui ou interface,
Domínio e IA. A figura 6 ilustra esta divisão. O pacote Gui está situado na parte superior
do diagrama, sua função é agrupar todas as classes de interface gráfica. É através dessas
classes que o jogador interage com o jogo posicionando suas pedras no tabuleiro. O
pacote situado na parte central do diagrama é o de Domínio, sua função é agrupar todas
as classes responsáveis por controlar as regras de negocio da aplicação. As classes de
15
Domínio armazenam informações sobre o estado atual do jogo como, por exemplo,
pontuação e espaços vagos no tabuleiro. Situado na parte inferior do diagrama, está o
pacote de IA (Inteligência Artificial). Sua função é agrupar todas as classes responsáveis
por implementar os jogadores virtuais da aplicação, mais especificamente, os algoritmos
Poda alfa-beta e Genético (CAMELO, 2011).
Figura 6 - Diagrama de pacotes do Reversi
Fonte: CAMELO (2011)
4.2 REFATORAÇÃO DO SISTEMA REVERSI
Nesta seção será detalhada a metodologia de refatoração do sistema Reversi.
4.2.1 PORQUE REFATORAR
Suponha-se, hipoteticamente, que os desenvolvedores de Reversi recebessem
hoje uma oferta irrecusável para migrar seu software para um ambiente web ou alterar
sua interface gráfica para a interface nativa de algum dispositivo móvel compatível com
as especificações do projeto.
Dado o alto acoplamento e a falta de coesão das classes do sistema, não seria
possível aproveitar a regra de negócio do projeto e utilizá-lo em outra interface. Logo, a
solução mais rápida e obvia seria duplicar o projeto, o que resultaria em dois projetos
distintos, o desktop clássico e o com a nova interface.
Um problema poderia vir a surgir quando se descobrisse um bug em sua
implementação de algoritmo genéticos, por exemplo, a correção teria que ser feita em
16
dois lugares (projetos) diferentes, o que implicaria em mais tempo para manter o
sistema ou inconsistência das correções nos projetos.
Portanto, segundo Fowler et al. (2004), quando descobre-se que tem que
acrescentar uma característica a um programa e o código desse programa não está
estruturado de forma conveniente para tal, primeiro refatore o programa para facilitar o
acréscimo da característica e só depois faz-se a adição.
4.2.2 O PRIMEIRO PASSO DA REFATORAÇÃO
Segundo Fowler et al. (2004), o primeiro passo da refatoração é sempre o
mesmo, verificar se o projeto possui um conjunto de testes confiáveis para o trecho de
código que se deseja refatorar. Caso o projeto não possua testes ou possua, mas não
sejam confiáveis, cria-se seu próprio conjunto de testes. É essencial para a refatoração
que se tenha um bom conjunto de testes, pois eles dirão se for introduzido alguma falha
durante este processo.
Para criação dos cenários de testes foi utilizada a técnica de modelagem de teste
baseado na especificação, conhecida como Particionamento de Equivalência. Esta
técnica consiste em dividir as entradas do sistema em conjuntos que produzem a mesma
saída. Para cada conjunto de entradas é especificado um teste. Para o Reversi, foi criado
o particionamento como o mostrado na figura 7. Nela, foi dividido o conjunto de
entradas entre jogadas boas, jogadas ruins e jogadas boas e ruins, e o conjunto de saídas
entre vitórias, empates e derrotas.
Figura 7 - Particionamento de equivalência para o Reversi
Fonte: Autoria própria
17
A partir desta especificação foram criados um conjunto de testes de aceitação
para o sistema. Testes de aceitação são testes automáticos que exercitam o sistema da
perspectiva do usuário, ou seja, como se ele estivesse interagindo com o sistema. Para
que isto fosse possível, foi utilizado o framework WindowLicker em conjunto com o
JUnit. Assim, os testes interagem com a aplicação clicando nos locais da tela do sistema
que foram programados, realizando jogadas e validando cada comportamento resultante
da mesma. Este conjunto de testes pode ser visto na figura 8. Como é possível observar,
devido a dificuldade de se obter um conjunto de jogadas que faça a partida terminar
empatada, o teste que cobre a situação de empate foi descartado.
Figura 8 - Conjunto de testes automáticos do Reversi
Fonte: Autoria própria
Por fim, vale apena ressaltar um problema ocorrido neste primeiro passo. Para se
construir testes automáticos para um sistema, o mesmo deve ser passível de teste, ou
seja, ele deve prover mecanismos para que seja possível simular ou prever seu
comportamento. No caso do Reversi, as jogadas realizadas pelo computador são
imprevisíveis, haja vista que as mesmas empregam uma regra de negócio complexa.
Assim, seria muito difícil automatizar as jogadas de um usuário sem saber o conjunto de
jogadas do computador. A solução é substituir o computador real por um computador de
18
teste (ou computador Stub1). Em sistemas não passíveis de teste isto muito vezes não é
possível por conta do alto acoplamento entre as classes. Portanto, antes de criar este
conjunto de testes, foi necessário realizar uma pequena alteração no projeto, colocando
um pequeno trecho de código de teste no código do sistema. Isto não é recomendável,
mas foi necessário para a realização deste trabalho.
4.2.3 O PROCESSO DE REFATORAÇÃO
Agora que o projeto do Reversi já possui um conjunto sólido de testes, está-se
apto para começar a refatorar qualquer trecho de código que esteja coberto por estes
testes. O processo de refatoração deve seguir os seguintes passos:
a) Explorar o código do sistema em busca de algum odor;
b) Realizar a refatoração de um pequeno trecho do código “fedorento”;
c) Executar o conjunto de testes automáticos (ou teste de regressão) para se
certificar de que as alterações não comprometeram o sistema;
d) Caso os testes tenham sido executados com sucesso, siga para o passo a; caso
contrário, desfaça suas alterações e volte ao passo b.
O primeiro trecho de código que chamou atenção é o da figura 9. Apesar de ser
relativamente pequeno, ele é um verdadeiro ecossistema de pequenos odores. A começar
pelo nome, que apesar de ser legível, não segue a convenção da linguagem para nomes
de métodos, pois não começa com verbo para indicar uma ação. Existe um alto
acoplamento em se instanciar as classes Busca e População, além disso, elas são usadas
somente neste método, o que pode ser um indício de responsabilidade mal posicionada.
O método realizaJogada recebe dois parâmetros quando poderia simplesmente receber
um único objeto: Jogada.
Os comentários são usados de forma a minimizar o odor do código mal escrito,
ora usado para esconder código morto, ora usado de forma redundante para descrever o
funcionamento do método. Pode-se ver também uma pequena Inveja de Dados na
expressão condicional. Assim como, o método realiza duas ações, busca a jogada e
depois a executa. Por último, existe um erro de lógica no comando condicional if/else: se
1 Segundo InfoQ (2013), stub é um objeto falso utilizado, no lugar de um objeto real, para propósito de
testes. Ele fornece respostas pré configuradas para as chamadas que lhe são feitas durante os testes e
normalmente não respondem a nada que não esteja programado para o teste.
19
o método getStrInteligênciaArtificial retornar nulo, por exemplo, o sistema irá escolher o
algoritmo minimax alfa-beta, quando deveria tomar outra ação.
Figura 9 - Método buscaJogadaComputador com vários odores
Fonte: Autoria própria
Após a refatoração, o método anteriormente denominado
buscaJogadaComputador ficou bem mais intuitivo, como mostra a figura 10. Além de
um nome mais legível, ele agora executa somente uma ação, e a executa de forma bem
feita.
Figura 10 - Refatoração do antigo método buscaJogadaComputador
Fonte: Autoria própria
A responsabilidade de instanciar a inteligência artificial da aplicação foi
transferida para um método de fábrica, como mostra a figura 11. Nela, também é
possível ver a correção da lógica do comando condicional if/else, e um exemplo de
comentário de código benéfico, sinalizando um débito técnico.
20
Figura 11 - Método de fábrica obterComputador
Fonte: Autoria própria
O segundo trecho de código que chamou atenção foi o da figura 12. Além de
uma série de nomeações de métodos que não seguem a convenção da linguagem, tem-se
um caso típico de Inveja de Dados, mais precisamente nas chamadas dos métodos
fimJogo, isVezJogado e resultadoJogo. Apesar de não violar a Lei de Demeter, está
claro que este trecho de código “inveja os dados” do objControle.
Figura 12 - Inveja de dados do objeto objControle
Fonte: Autoria própria
A solução é posicionar a responsabilidade de obter a mensagem com status atual
do jogo na classe correta, ou seja, na classe ControladorDoJogo como mostra a figura
13.
21
Figura 13 - Responsabilidade reposicionada na classe ControladorDoJogo
Fonte: Autoria própria
Além da falta de legibilidade dos nomes de variáveis e métodos, e há um
excessivo número de comentários que insistem em explicar o que os nomes ruins do
método não conseguem. Um dos odores que mais vem se repetindo no código do
Reversi é o Acidente de Trem e a duplicação de código que sempre o acompanha. A
figura 14 mostra claramente a repetição de código causada pelo Acidente de Trem ao
chamar as funcionalidades do objeto objControle. Por fim, outro odor que está
começando a “cheirar forte” neste trecho de código, devido a grande quantidade de
“números mágicos” e notação Húngara, é o chamado Propósito Obscuro.
Figura 14 - Código “Acidente de trem” ao chamar método getMatPedrasTabuleiro
Fonte: Autoria própria
A solução para este tipo de odor consiste basicamente em delegar para a classe
colaboradora mais próxima a responsabilidade de prover a funcionalidade que se estava
interessado ao fazer o Acidente de Trem. No caso especifico, foi delegado a classe
ControladorDoJogo a obtenção do caminho da imagem, como mostra a figura 15. Esta
22
refatoração também irá resolver o problema de duplicação de código ocorrido ao se
chamar o método getMatPedrasTabuleiro, pois agora existirá apenas uma única
chamada a este método, centralizada em uma classe que possua tal responsabilidade.
Também foram removidos os “números mágicos” com constantes explicativas e
renomeadas as variáveis de forma mais legível. Por fim, vale lembrar que o trecho de
código da figura 14 acabou sendo extraído para um método que explicará claramente
seu propósito, chamado gerarTabuleiroLabelComEstadoAtualDoJogo.
Figura 15 - Refatoração do Acidente de Trem do método getMatPedrasTabuleiro
Fonte: Autoria própria
Um método que também chamou atenção foi o executaJogada, que possuía 191
linhas de código, como mostra a figura 16. Logo, não resta nenhuma dúvida de que este
método realiza mais de uma ação. Além disso, mesmo ocultando a maior parte do
código contido no método, é possível imaginar que ele continha uma quantidade
elevada de “odores”.
23
Figura 16 - Muitas linhas de código no método executaJogada
Fonte: Autoria própria
A solução para este problema foi remanejar as responsabilidades deste método,
ora utilizando uma extração de trechos de código para um método menor com propósito
claro, ora criando novas classes para realizar funções específicas. De acordo com os
comentários da figura 16, a responsabilidade de executaJogada é atualizar o estado do
tabuleiro, invertendo as cores das pedras afetadas após uma jogada. A refatoração deste
método pode ser vista na figura 17. Foram criadas várias classes do tipo Reversível que
implementam o método reverter de acordo com a direção (ou sentido) do tabuleiro que
se deseja atualizar.
Figura 17 - Refatoração do método executaJogada
Fonte: Autoria própria
4.2.4 QUANDO PARAR DE REFATORAR
Não existe uma condição de parada exata para determinar quando a refatoração
termina, haja vista que tudo depende da sensibilidade e necessidades do desenvolvedor
que a executa. No caso do Reversi, o objetivo foi seguir apenas a Regra do Escoteiro (do
inglês, The Boy ScoutRule), uma metáfora que segundo Martin (2009), diz:
“Deixe a área do acampamento mais limpa do que como você a encontrou.”
24
Fazendo uma analogia para programação, se cada desenvolvedor deixar o código
fonte do sistema em que está trabalhando mais limpo do que quando o recebeu de outro
programador, ele estará atendendo à Regra.
Portanto, este processo de refatoração foi repetido várias vezes consecutivas até
que todo o código do Reversi, que estava coberto por testes, estivesse melhor que o
código original.
4.3 MÉTRICAS DO SISTEMA REVERSI
Com o intuito de mensurar os ganhos de qualidade advindos do processo de
refatoração, foi realizada a coleta das métricas escolhidas para o projeto Reversi. Elas
foram coletadas durante os três marcos deste processo, são eles:
a) Após posse do sistema legado, ou seja, antes da refatoração ser aplicada;
b) Após a criação do conjunto de testes de aceitação;
c) Após o processo de refatoração chegar ao fim.
As métricas coletadas podem ser vistas na figura 18. Como pode-se ver, as
quatro primeiras métricas referentes a complexidade ciclomática1 e linhas de código
avaliam o projeto no escopo de método. Nelas, o processo de refatoração reduziram
seus números iniciais aproximadamente pela metade (com exceção da complexidade
ciclomática máxima que teve uma redução muito maior). Isto significa que os métodos
do projeto estão mais coesos, realizam menos ações e estão mais legíveis.
1 Complexidade ciclomática (ou complexidade condicional) é uma métrica de software usada para indicar
a complexidade de um programa de computador. Desenvolvida por Thomas J. McCabe em 1976, ela
mede a quantidade de caminhos de execução independentes a partir de um código fonte (WIKIPÉDIA,
2013c).
25
Figura 18 - Métricas do projeto Reversi
Fonte: Autoria própria
As métricas seguintes, total de classes e média de linhas de código das classes,
avaliam o projeto no escopo de classes. Percebe-se que os números se contrapõem em
proporções bastante semelhantes, enquanto o total de classes do projeto teve um
aumento pouco maior que o dobro em relação aos números iniciais, a média de linhas de
código das classes do projeto teve seus valores reduzidos pela metade em relação aos
números iniciais. Isto significa que as responsabilidades das classes foram melhor
divididas através da criação de mais classes e a coesão das mesmas aumentada pela
menor quantidades de linhas que elas apresentam.
Por fim, as métricas de cobertura de testes nos mostram a porcentagem de linhas
de código do projeto coberta por testes. Como pode-se ver, o projeto saiu do patamar de
projeto legado, ou seja, que não possui código coberto por testes, para um projeto com
49,4% do seu código coberto por testes. Vale lembrar que este conjunto de testes
cobrem apenas os pacotes Gui e Domínio do projeto, ficando o pacote IA sem testes e,
consequentemente, sem refatoração. Isto aconteceu porque foi necessário substituir o
computador real por um computador de teste (ou computador Stub) durante a execução
dos testes. Assim, o fluxo de execução do programa, que passava pelo pacote IA, foi
desviado para um pacote de testes previamente configurado.
26
5 CONCLUSÃO
Este artigo sugeriu a utilização da técnica de refatoração para melhorar a
qualidade de código de sistemas legados, com o intuito de avaliar se os benefícios
advindos de sua utilização são satisfatórios o bastante para adotá-la no processo de
evolução deste tipo de sistema, tornando-os mais produtivos e permitindo que suas
melhoria e manutenção sejam efetuadas com rapidez e segurança.
Ao final do processo de refatoração foi constatado, com base em um conjunto de
métricas de software, que a qualidade do projeto aumentou consideravelmente. As
classes se tornaram mais coesas e suas responsabilidades foram melhor divididas. Os
métodos perderam boa parte de sua complexidade e tamanho, e agora realizam ações
mais específicas. Além disso, ganharam maior legibilidade. Por fim, o projeto ganhou
um conjunto de testes automáticos que podem ser executados a qualquer momento,
permitindo que o sistema possa evoluir com segurança e rapidez.
Tendo em vista os fatos mencionados, considera-se a técnica de refatoração
importante para a melhoria da qualidade de sistemas legados, tornando-os mais
produtivos e permitindo que melhorias e evoluções sejam efetuadas com um menor
risco.
REFERÊNCIAS
ALMEIDA, Lucianna Thomaz; MIRANDA, João Machini de. Código Limpo e seu
Mapeamento para Métricas de Código Fonte. Trabalho de Conclusão de Curso -
Instituto de Matemática e Estatística, Universidade de São Paulo - USP. São Paulo,
2010.
BECK, Kent. Extreme Programming Explained. Boston: Addison Wesley, 1999.
BECK, Kent. Implementation Patterns. Boston: Addison Wesley, 2007.
BOSWELL, Dustin; FOUCHER, Trevor. A Arte de Escrever Programas Legíveis:
técnicas simples e práticas para elaboração de programas fáceis de serem lidos e
entendidos. Trad. Rafael Zanolli. São Paulo: Novatec, 2012.
27
CAMELO, Anderson Lemos. Um Jogo de Othello Evolutivo. Trabalho de Conclusão
de Curso (Graduação em Ciências da Computação) - Instituto de Computação,
Universidade Federal de Alagoas - UFAL, Maceió, 2011.
FOWLER, Martin; BECK, Kent; BRANT, John; OPDYKE, Willian; GAMMA, Erich.
Refatoração: aperfeiçoando o projeto de código existente. Trad. Acauan Fernandes.
Porto Alegre: Bookman, 2004.
FEATHERS, Michael C. Working Effectively with Legacy Code. UpperSaddle River:
Prentice Hall, 2004.
FREEMAN, Steve; PRYCE, Nat. Desenvolvimento de Software Orientado a
Objetos, Guiado por Testes. Trad. Savannah Hartmann. Rio de Janeiro: Alta Books,
2012.
INFOQ. Mocks Aren't Stubs. 2013. Disponível em:
<http://www.infoq.com/br/articles/mocks-Arent-Stubs>. Acesso em: 28 set. 2013.
MARTIN, Robert C. Código Limpo: habilidades práticas do agile software. Trad.
Leandro Chu. Rio de Janeiro: Alta Books, 2009.
SARTORELLI, Reinaldo Coelho. Proposta de Processo para Automação de Testes
em Sistemas Legados. Trabalho de Conclusão de Curso (Pós-Graduação em Técnicas
de Construção de Software Orientado a Objetos) - Centro Universitário SENAC, São
Paulo, 2007.
SOMMERVILLE, Ian. Engenharia de Software. Trad. Selma Shin Shimizu
Melnikoff, Reginaldo Arakaki, Edilson de Andrade Barbosa. São Paulo: Pearson
Addison-Wesley, 2007.
WIKIPÉDIA. Refatoração. 2013a. Disponível em:
<http://pt.wikipedia.org/wiki/Refatoração>. Acesso em: 17 set. 2013.
WIKIPÉDIA. CodeSmell. 2013b. Disponível em:
<http://http://en.wikipedia.org/wiki/Code_smell>. Acesso em: 17 set. 2013.
WIKIPÉDIA. Complexidade ciclomática. 2013c. Disponível em:
<http://pt.wikipedia.org/wiki/Complexidade_ciclomática>. Acesso em: 28 set. 2013.