RP : Refactored Programming ­ Conjunto de boas pr áticas par a uma pr ogr amação limpa e pr odutiva. MARLON PATRICK 1, 4 & LUCIANO CABRAL 1,2,3 1 Faculdade de Tecnologia de Pernambuco (FATEC) Recife – PE – Brasil 2 Centro de Informática (CIn) Universidade Federal de Pernambuco (UFPE) Recife – PE – Brasil 3 Scopus Tecnologia Ltda Recife – PE – Brasil 4 TCI BPO S/A Recife – PE – Brasil {marlonpatrick10,lscabral}@gmail.com Abstract. This article aims to provide a manual of good practices programming based on well established techniques of refactoring that can and should be implemented in time of encryption to the development of software with code clean, readable, easy to understand and maintain. Keywords: refactoring, programming, good practices, development clean and fast. Resumo. Este artigo tem como objetivo apresentar um manual de boas práticas de programação baseados em técnicas de refactoring bem consolidadas que podem e devem ser aplicadas em tempo de codificação visando um desenvolvimento de software com código limpo, legível, de fácil entendimento e manutenção. Palavras­chave: refactoring, programação, boas práticas, desenvolvimeneto limpo e rápido.
1. Intr odução Atualmente o “mercado de softwares” é um dos que mais crescem (Consultoria IDC), as empresas precisam cada vez mais deles para solucionar problemas que as mesmas enfrentam. Porém, não são mais simples contas a pagar, ou softwares de controle de vídeo­locadora, pelo contrário, são cada vez mais complexos e específicos com objetivos de sanar grandes problemas. Com isso, os clientes das empresas de TI são cada vez mais exigentes e perfeccionistas, e para atendê­los é necessário que os softwares sejam ainda mais flexíveis e funcionais. No entanto, não é a realidade que se vive. Muitas vezes só existe a preocupação que o software esteja funcionando e esquece­se essa outra característica importante: a Flexibilidade. O que acontece é que quando o cliente por alguma razão decide mudar uma regra ou requisito do software, a dificuldade de realizar essa alteração é tão grande que gera um esforço considerável e não esperado no planejamento do projeto. Com certeza isso não acontece por que programadores são preguiçosos e desorganizados, geralmente é o oposto, até visualiza­se que algo está errado e poderia ser melhorado, mas os prazos geralmente apertados não permitem fazer determinados ajustes que são necessários. E como praticamente não existem empresas que reservam tempo para revisão de código, então a pseudo­solução é anotar a possível melhoria para fazer um dia quando houver tempo e prosseguir o desenvolvimento rápido e sujo (Quick and dirty), este que acaba sendo uma ilusão já que essa desorganização implica em muitos erros e bastante tempo para as correções. Uma excelente técnica para solucionar esses problemas seria o Refactoring, no entanto para utilizá­la é necessário alguma reserva de tempo e que no final não agregaria nenhuma nova funcionalidade no software, e sim traria uma grande melhoria em partes não funcionais, como: simplicidade, flexibilidade e clareza. Melhorias essas que não são visíveis ao cliente e que desta forma, normalmente não interessam aos gerentes de projetos. Além disso, ela tem outro ponto fraco que é o risco de inserir bugs no software que teoricamente está em perfeito funcionamento. Então, pergunta­se:
· Como resolver esse problema?
· Como tornar o código do meu projeto limpo sem alocar grande quantidade de tempo para refatorá­lo? Eis que surge a proposta apresentada neste artigo. Já que não se tem tempo para revisão de código e posteriormente aplicação de refactoring no mesmo, a única solução é viabilizar­se uma série de boas práticas de programação para policiar os programadores de forma que haja uma garantia mínima que cada nova linha de código seja limpa e facilmente entendível por outro programador utilizando­se desta série proposta. Com isso, estar­se­ia reduzindo o esforço de qualquer manutenção necessária no software, diminuindo a quantidade de correções e conseqüentemente reduzindo a possibilidade de inserir erros em partes que já estão funcionando. Seguindo este raciocínio, na Seção 2 são exibidas todas as técnicas com um embasamento teórico e também um exemplo prático; na Seção 3 é consolidada a idéia que se apresenta neste artigo, um novo estilo de programação, destacando­se suas dificuldades e vantagens; na Seção 4 explanam­se as possíveis aplicações da proposta e quais os efeitos adquiridos em cada uma; na Seção 5 são mostrados alguns gráficos com a intenção de comprovar o que se diz no presente, os mesmos foram obtidos a partir de alguns testes realizados; na Seção 6 apresentamos algumas considerações finais; na Seção 7 são feitos agradecimentos a todos que contribuíram de alguma forma para este artigo e na Seção 8, que por sinal é a última, citamos as referências utilizadas. 2. O Estado da Ar te Custos e prazos muitas vezes distorcem o conceito de qualidade de software em projetos que fogem ao planejamento especificado. Diante de tal fato, decidiu­se o estudo aprofundado de técnicas de refactoring, com o objetivo de gerar­se um conjunto de boas práticas para uma programação limpa e produtiva aplicáveis em tempo de codificação. Devido a isto, ressaltam­se os detalhes de tal conjunto nas seções a seguir.
2.1. Refactoring “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 você usa refatoração, você está melhorando o projeto do código após este ter sido escrito.” (FOWLER,1999). Na década de 80 programadores da comunidade Smalltalk começaram a usar o termo Refactoring para definir alterações realizadas no software em que apenas melhorava­se o código sem adicionar funcionalidades ao mesmo. Dois dos principais difusores dessa nova idéia eram Ward Cunningham e Kent Beck, programadores respeitados e que já naquela época iniciavam também o desenvolvimento da metodologia ágil XP 1 . Desta maneira a nova técnica foi ganhando popularidade e força, cada vez mais atraindo pesquisadores ela foi amadurecendo. O problema é que não existiam referências bibliográficas sobre o tema, assim a técnica era praticada muitas vezes de forma equivocada não atingindo seus principais benefícios. Até que em 1992, Bill Opdyke faz sua tese de doutorado com refactoring como tema central, que chamou­se “Refactoring of Object Oriented Frameworks”, considerado até hoje um dos melhores trabalhos, se não o melhor, no que se diz respeito a esse assunto. Em 1999, Don Roberts também apresenta sua tese de doutorado baseada nessa ascendente prática de engenharia de software, o título era “Analisys for Refactoring”. Mas o tema Refactoring ganha força máxima quando ainda em 1999, Martin Fowler pública seu livro totalmente voltado a esse assunto, “ Refactoring: Improving the Design of Existing Code” , listando um catálogo de mais de 70 técnicas de refatoração, quais problemas elas solucionavam, quando e como usá­las, além de vários outros tópicos que estão ligados ao Refactoring. 1 XP: Programação Extrema (do inglês eXtreme Programming), ou simplesmente XP, é uma metodologia ágil para equipes pequenas e médias e que irão desenvolver software com requisitos vagos e em constante mudança. Para isso, adota a estratégia de constante acompanhamento e realização de vários pequenos ajustes durante o desenvolvimento de software. [ <http://www.improveit.com.br/xp>]
Segundo FOWLER, o principal objetivo da refatoração é tornar o código mais fácil de entender e de modificar, ou seja, apenas mudanças que cumpram esses objetivos podem ser consideradas refatorações. É importante ressaltar esta afirmação por que é muito comum ouvir­se terceiros citando refatoração referindo­se a qualquer mudança no código que não altere o seu comportamento, o que é muitas vezes errado. Um bom exemplo disso é a otimização de desempenho, que assim como a refatoração, apenas altera a estrutura interna do software além do seu desempenho, no entanto seus objetivos são diferentes, pois na maioria das vezes a melhoria na performance gera um código de difícil entendimento, mas que é necessário. FOWLER destaca quatro grandes benefícios da refatoração: • Melhoria no projeto de software através de um código estruturado • Código auto­explicativo, facilitando seu entendimento • Código limpo, possibilitando maior visibilidade de erros • Programação ocorre mais rápido como conseqüência das três citadas acima Ele defende que um processo de refatoração deve ser feito com um grande apoio de testes unitários, já que o mesmo altera um código que está funcionando é fundamental garantir que após a refatoração ele continuará funcionando perfeitamente. E esse acaba sendo o grande problema com a refatoração, a possibilidade de inserir erros em códigos já consolidados, além do tempo que é necessário para realizá­la e testá­la. 2.2. Codificando apoiado em técnicas de refatoração Apresentado o Refactoring, suas vantagens e seus problemas mostra­se nesta seção um catálogo de técnicas de refatoração que podem e devem ser usados em tempo de codificação. O objetivo é que o código seja desde sempre o mais limpo e organizado possível sem a necessidade de revisão ou mudança em um código já em funcionamento. Esse catálogo é de grande valia principalmente para programadores iniciantes ou que estejam migrando de linguagens estruturadas para linguagens orientadas a objetos, porém, mesmo programadores mais experientes cometem alguns erros aqui relatados principalmente em virtude da correria para se entregar as funcionalidades de um software no prazo combinado com o cliente.
Todas as técnicas foram obtidas do catálogo mantido por FOWLER no site <http://www.refactoring.com> e são apresentados no mesmo formato. Primeiro o nome da técnica de refatoração, em seguida uma breve descrição do problema e da solução, uma motivação e por fim um exemplo prático com o código antes e depois da aplicação da técnica. Um ponto a reforçar é que os exemplos apresentados aqui são bastante simples, de fácil entendimento e demonstração. Isso pode muitas vezes ocultar o real valor da técnica analisada, mas que quando aplicadas a um software com toda sua complexidade se tornam muito importantes para a estruturação e não deteriorização do código. 2.2.1. Dividir variável temporária (Split Temporary Variable) Problema : Tem­se uma variável temporária recebendo atribuições mais de uma vez para representar dados diferentes, e a mesma não é uma variável de loop. Solução: Criam­se uma variável temporária para cada atribuição de diferentes dados. Motivação: Variáveis temporárias devem representar apenas um dado durante um método, o fato de está recebendo mais de uma atribuição indica que ela possui mais de uma responsabilidade, assim, cada responsabilidade implica em uma variável temporária exclusiva. Esse problema confunde quem lê o código. Tabela 1. Compar ação entr e o código or iginal e o código r efator ado com Dividir var iável tempor ár ia (1). Código Original double temp = 2 * (height + width); System.out.pr intln(temp); Temp = height * width; System.out.pr intln(temp); Código Refatorado final double per imeter = 2 * (height + width); System.out.pr intln(per imeter ); final double ar ea = height * width; System.out.pr intln(ar ea); 2.2.2. Separar consulta de modificador (Separate Query from Modifier ) Problema : Tem­se um método que retorna um valor, mas também modifica o estado de um objeto. Solução: Criam­se dois métodos, um para a consulta e outro para a modificação. Motivação: É uma boa idéia sinalizar claramente a diferença entre métodos com e sem efeitos colaterais. Uma boa regra a seguir é dizer que qualquer método que retorna um
valor não deve ter efeitos colaterais. Porém, muitas vezes há operações que devem ser feitas em um mesmo momento por motivos de concorrência, assim, deverá existir um terceiro método que englobe a consulta e a modificação de modo que quem lê o código entenda claramente qual parte é responsável pela consulta e pela modificação. Tabela 2. Compar ação entr e o código or iginal e o código r efator ado com Separ ar consulta de modificador (2). Código Original public double getNoteMathematic(){ this.noteMatemathic += this.pointExtr a; r etur n this.noteMatemathic; } Código Refatorado double getNoteMathematic(){ r etur n this.noteMatemathic; } void addPointExtr a(){ double noteWithPonintExtr a = this.getNoteMathematic() + this.getPointExtr a(); this.setNoteMatemathic(noteWithPointExtr a); } 2.2.3. Auto­Encapsular Campo (Self Encapsulate Field) Problema : Tem­se um método que acessa um campo interno à classe diretamente, porém o acoplamento a este campo está ficando inadequado. Solução: Substituem­se os acessos diretos por chamadas a métodos de acesso ao campo. Motivação: Uma boa prática bastante conhecida é que campos de uma classe devem ser sempre privados e só devem ser acessados por outras classes através de seus métodos de acesso. Mas, outro ponto é o acesso a esses campos dentro da própria classe. Muitos defendem que neste local é válido o acesso direto e há os que defendam que mesmo dentro da classe o acesso deve ser feito apenas pelos devidos métodos. Porém, a grande vantagem de usar métodos é que esses podem ser subscritos por subclasses para funcionarem de uma maneira específica, evitando­se assim repetição de código toda vez que a subclasse precisar desse novo comportamento. Tabela 3. Compar ação entr e o código or iginal e o código r efator ado com Auto­ Encapsular Campo (3). Código Original Código Refatorado
pr ivate int _low, _high; pr ivate int _low, _high; boolean includes (int ar g) { r etur n ar g >= _low && ar g <= _high; } boolean includes (int ar g) { r etur n ar g >= getLow() && ar g <= getHigh(); } int getLow() {r etur n _low;} int getHigh() {r etur n _high;} 2.2.4. Substituir variável temporária por consulta (Replace Temp with Query) Problema : Usa­se uma variável temporária para armazenar o resultado de uma expressão. Solução: Substitui­se a variável temporária pelo próprio método que foi usado para atribuir­lhe um valor. Caso não tenha sido um método crie um que retorne o valor da expressão. Isso deve ser aplicado se o valor da expressão não for volátil, caso contrário deve­se manter a variável temporária. Além disso, prima­se pelo bom senso do desenvolvedor, já que há situações que são mais custosas as consultas do que a variável temporária, como exemplo um método que precise acessar um banco de dados para retornar um valor. Motivação: O problema das variáveis temporárias é que elas só podem ser vistas no método declarado, assim encorajam construções de métodos longos ou com grandes listas de parâmetros, pois é a única maneira que se pode alcançá­las, ambas as opções são consideradas Bad Smells 2 em FOWLER (FOWLER, 1999). Substituindo­se essas variáveis por um método de consulta esse valor é acessível por qualquer outro método da classe sem necessidade de parâmetros extras. Tabela 4. Compar ação entr e o código or iginal e o código r efator ado com Substituir var iável tempor ár ia por consulta (4). Código Original 2 Código Refatorado Definido por Martin Fowler e Kent Beck, o termo bad smell (maus cheiros) se refere às características encontradas nas estruturas de códigos que indicam que o mesmo está com problemas e precisa ser refatorado de alguma maneira.
double basePr ice = _quantity * _itemPr ice; if (basePr ice > 1000) r etur n basePr ice * 0.95; else r etur n basePr ice * 0.98; if (basePr ice() > 1000){ r etur n basePr ice() * 0.95; } r etur n basePr ice() * 0.98; ... double basePr ice() { r etur n _quantity * _itemPr ice; } 2.2.5. Substituir variável estática por parâmetro (Replace Static Variable with Parameter) Problema : Tem­se um método dependente de uma variável estática que é usada em outros contextos. Solução: Adiciona­se um novo parâmetro em substituição da variável estática. Motivação: Variáveis estáticas, também conhecidas como variáveis de classe, não devem afetar o estado ou comportamento de uma determinada instância desta classe, pois essas variáveis possuem o mesmo valor para todas as instâncias, como o nome sugere, elas são compartilhadas por todas as instâncias da classe. Isso pode causar confusão, por exemplo, quando uma instância vai usar o valor do campo estático e espera­se que esse campo esteja com um valor, porém, antes dessa utilização, outra instância seta o valor desse campo estático, o que na maioria dos casos, mudaria completamente o resultado obtido. Caso precise­se mesmo que a variável estática seja usada, é melhor analisar se ela realmente deve ser estática ao invés de um atributo de instância. Tabela 5. Compar ação entr e o código or iginal e o código r efator ado com Substituir var iável estática por par âmetr o (5). Código Original static double X; static double Y; public boolean isHemisphereNorth(){ return Y > 0; } Código Refatorado //talvez tenham que ser de instância static double X; static double Y; boolean isHemisphereNorth(double coordinatedY){ return coordinatedY > 0; } 2.2.6. Substituir parâmetro por método (Replace Parameter with Method) Problema : Nota­se um objeto que invoca um método A e então passa o resultado como parâmetro para o método B, apesar de B também poder chamar o método A.
Solução: Remove­se o parâmetro de B e deixa­se que ele mesmo chame A. Motivação: Se um método puder de alguma outra forma obter um valor que é passado como parâmetro, ele deve fazê­lo. Longas listas de parâmetros são difíceis de entender e deve­se reduzi­las tanto quanto possível. Tabela 6. Compar ação entr e o código or iginal e o código r efator ado com Substituir par âmetr o por método (6). Código Original int basePr ice = _quantity * _itemPr ice; discountLevel = getDiscountLevel(); double finalPr ice = discountedPr ice (basePr ice, discountLevel); Código Refatorado int basePr ice = _quantity * _itemPr ice; double finalPr ice = discountedPr ice (basePr ice); . . double discountedPr ice(int basePr ice){ discountLevel = getDiscountLevel(); //do thing } 2.2.7. Decompor condicional (Decompose Conditional) Problema : Tem­se uma estrutura condicional complicada (if ­ else if ­ else). Solução: Extraia métodos das condições. Motivação: Umas das fontes de complexidade de um programa são suas lógicas condicionais complexas. O problema é que se sabe o que as condições estão testando, mas não se sabe o real propósito desses testes, então para eliminação desse problema faz­se uso de métodos com nomes que deixam claro a intenção do teste. Com isso, você está substituindo a declaração do que está fazendo pela declaração de porquê você está fazendo. Tabela 7. Compar ação entr e o código or iginal e o código r efator ado com Decompor condicional (7). Código Original if(dateBegin!=null && !dateBegin.equals(“”) && dateEnd!=null && !dateEnd.equals(“”)) { …
}else if(dateBegin!=null && !dateBegin.equals(“”)){ …
}else if(dateEnd!=null && !dtEnd.equals(“”)){ …
} Código Refatorado if(per iodRepor ted()){ …
}else if(dateBeginRepor ted()){ …
}else if(dateEndRepor ted()){ …
} boolean per iodRepor ted(){ r etur n dateBeginRepor ted() && dateEndRepor ted(); }
2.2.8. Consolidar expressão condicional (Consolidate Conditional Expression) Problema : Tem­se uma seqüência de testes condicionais que resultam no mesmo valor. Solução: Combina­se a seqüência de testes em uma única expressão condicional e se possível, extrai­se para um método. Motivação: Não se deve usar uma série de ifs para realizar testes condicionais que resultem na mesma ação, para isso existe os operadores lógicos AND e OR. Com a utilização destes operadores fica muito mais claro que você está fazendo na verdade uma única verificação. Tabela 8. Compar ação entr e o código or iginal e o código r efator ado com Decompor condicional (8). Código Original double disabilityAmount() { if (_senior ity < 2) r etur n 0; if (_monthsDisabled > 12) r etur n 0; if (_isPar tTime) r etur n 0; // compute the disability amount Código Refatorado double disabilityAmount() { if (isNotEligableFor Disability()){ r etur n 0; } // compute the disability amount boolean isNotEligableFor Disability(){ r etur n (_senior ity < 2 || monthsDisabled > 12 || isPar tTime); } 2.2.9. Consolidar fragmentos condicionais duplicados (Consolidate Duplicate Conditional Fragments) Problema : Tem­se um fragmento de código que aparece em todos os ramos de uma expressão condicional. Solução: Move­se o código duplicado para fora da expressão. Motivação: Se tem um código executado em todas as ramificações de um condicional, então certamente ele é independente da expressão. Com isso, devemos retirá­lo desta expressão, deixando mais claro o que varia e o que não varia de acordo com as condições. Tabela 9. Compar ação entr e o código or iginal e o código r efator ado com Decompor condicional (9). Código Original if (isSpecialDeal()) { total = pr ice * 0.95; send(); }else { total = pr ice * 0.98; send(); } Código Refatorado if (isSpecialDeal()){ total = pr ice * 0.95; }else{ total = pr ice * 0.98; } send();
2.2.10. Encapsular downcast 3 (Encapsulate Downcast) Problema : Um método retorna um objeto que precisa fornecer downcast por seus solicitantes. Solução: Mova o downcast para dentro do método. Motivação: Downcasting é uma das coisas mais inconvenientes que linguagens orientadas a objetos fortemente tipadas nos obrigam a fazer. Esse é um mal que deve ser feito menos possível, ele acaba causando um trabalho extra aos clientes do método, portanto devemos fornecer o objeto mais especializado possível. Tabela 10. Compar ação entr e o código or iginal e o código r efator ado com Decompor condicional (10). Código Original Código Refatorado Reading r ead = (Reading) lastReading(); //do something Reading r ead = lastReading(); //do something Object lastReading() { r etur n r eadings.lastElement(); } Reading lastReading() { r etur n (Reading) r eadings.lastElement(); } 2.2.11. Introduzir variável explicativa (Introduce Explaining Variable) Problema: Tem­se uma expressão lógica complicada. Solução: Coloca­se o resultado ou partes da expressão em uma variável temporária cujo nome explique seu propósito. Motivação: Expressões podem se tornar complexas e difíceis de entender. Nesse caso variáveis temporárias podem ser úteis para quebrá­las em algo mais fácil de 3 Em linguagens orientadas a objetos existem dois tipos de conversões (castings) entre tipos. Uma delas é a conversão ampliadora, onde um objeto mais especializado é atribuído a uma referência de um objeto mais genérico. A outra é a conversão redutora (downcast), onde uma referência a um objeto mais genérico é atribuída a um menos genérico, nesses casos é necessário explicitar o tipo para o qual se está convertendo.
compreender. Percebe­se que essa técnica soluciona o mesmo problema que Decompor condicional, porém ao invés de usar métodos para facilitar o entendimento usa variáveis temporárias. FOWLER defende que ela só deve ser usada se realmente não puder usar Decompor condicional, devido aos problemas com variáveis temporárias. Tabela 11. Compar ação entr e o código or iginal e o código r efator ado com Intr oduzir var iável explicativa (11). Código Original Código Refatorado if ( (platfor m.toUpper Case().indexOf(" MAC" ) > ­1) && (br owser .toUpper Case().indexOf(" IE" ) > ­1) && wasInitialized() && r esize > 0 ) { // do something } final boolean isMacOs = platfor m.toUpper Case().indexOf(" MAC" ) > ­1; final boolean isIEBr owser = br owser .toUpper Case().indexOf(" IE" ) > ­1; final boolean wasResized = r esize > 0; if (isMacOs && isIEBr owser && wasInitialized() && wasResized){ // do something } 2.2.12. Reduzir escopo de variável (Reduce Scope of Variable) Problema : Tem­se uma variável temporária declarada num escopo maior do que o que ela é utilizada. Solução: Reduz­se o escopo da variável para deixá­la somente visível onde é usada. Motivação: Como visto em outras técnicas aqui apresentadas, excesso de variáveis temporárias podem nos trazer alguns problemas na compreensão de um código, assim as mesmas devem ser evitadas o máximo possível. Mas mesmo assim, nunca vamos escapar completamente desse tipo de variáveis, porém podemos melhorar o seu uso declarando­ as num espaço que serão usadas independente de condições, evitando declarações de variáveis não utilizadas (que dependem de alguma condição) além do melhoramento na leitura do código devido à definição clara em que escopo essa variável é utilizada. Tabela 12. Compar ação entr e o código or iginal e o código r efator ado com Reduzir escopo de var iável (12). Código Original void doSomething(){ int i = 7; // i is not used her e if (someCondition){ Código Refatorado void doSomething(){ if (someCondition){ int i = 7;
// i is used only within this block //do something } } // i is not used her e } } 2.2.13. Remover atribuições a parâmetros (Remove Assignments to Parameters) Problema : Tem­se um método que faz uma atribuição a um parâmetro. Solução: Usa­se uma variável temporária no lugar da atribuição. Motivação: Como mostrado em Separar consulta do modificador, métodos de consulta e de modificação devem estar separados pra que fique bem claro para os clientes dos métodos que operação estão de fato realizando. Do mesmo modo acontece com atribuições a parâmetros, isso deve ser evitado, pois o intuito dos parâmetros são sempre passar informações, se o parâmetro for um objeto o que se pode fazer é setar atributos do mesmo, mas nunca atribuir a variável que o referência um novo objeto. Isso evita confusão para quem lê o código, pois sempre se espera que o parâmetro inicie e termine o método com o mesmo valor. Tabela 13. Compar ação entr e o código or iginal e o código r efator ado com Remover atr ibuições a par âmetr os (13). Código Original void doSomething(){ int i = 7; // i is not used her e if (someCondition){ // i is used only within this block } // i is not used her e } Código Refatorado void doSomething(){ if (someCondition){ int i = 7; //do something } } 2.2.14. Remover dupla negação (Remove Double Negative) Problema : Tem­se uma dupla negação condicional. Solução: Transforma­se em um único condicional positivo. Motivação: Certamente dupla negação tem sua função em linguagem natural, mas em linguagem de programação ela só ajuda a confundir. Recomenda­se que um método que
retorne um booleano tenha sempre um nome indicando um valor positivo, assim deixamos a negação desse método por conta dos operadores lógicos de negação das linguagens de programação. Tabela 14. Compar ação entr e o código or iginal e o código r efator ado com Remover dupla negação (14). Código Original if ( !item.isNotFound() ){ //do something } Código Refatorado if ( item.isFound() ){ //do something } 2.2.15. Remover flag de controle (Remove Control Flag) Problema : Tem­se uma variável que atua como uma flag de controle para uma série de expressões booleanas. Solução: Usam­se artifícios como break, continue ou return. Motivação: Flags de controle são muito comuns em linguagens estruturadas, mas nas linguagens orientadas a objetos acabam sendo desnecessárias com a introdução de comando break e continue. Elas acabam tornando o código mais complexo e ocultando o real propósito da expressão condicional. Tabela 15. Compar ação entr e o código or iginal e o código r efator ado com Remover flag de contr ole (15). Código Original for (Str ing namePupil : pupils){ found = false; for (Str ing namePlayer : player s){ if( !found ){ if(namePupil.equals(namePlayer )){ // do something found = tr ue; } } } } Código Refatorado for (Str ing namePupil : pupils){ for (Str ing namePlayer : player s){ if( !found ){ if(namePupil.equals(namePlayer )){ // do something br eak; } }
}
} 2.2.16. Substituir atribuição por inicialização (Replace Assignment with Initialization) Problema : Tem­se um código que primeiro declara uma variável e só depois lhe atribui algum valor.
Solução: Em vês disso faça uma inicialização direta. Motivação: Esse problema deriva do estilo de programação estruturado, onde o programador tinha de declarar todas as variáveis que iria usar durante a função e só depois ele as utilizava. Linguagens orientadas a objetos não precisam disso, as variáveis podem e devem ser declaradas no momento em que passarão a ser úteis e agregarão algum valor ao método, antes disso só causarão problemas como linhas desnecessárias no código e variáveis fora de escopo. Tabela 16. Compar ação entr e o código or iginal e o código r efator ado com Substituir atr ibuição por inicialização (16). Código Original void doSomething () { int i; // .... i = 7; } Código Refatorado void doSomething () { // .... int i = 7; } 2.2.17. Substituir código de erro por exceção (Replace Error Code with Exception) Problema : Tem­se um método que retorna um código especial que indica um erro. Solução: Lance uma exceção em vez disso. Motivação: Mais uma prática aprendida nas antigas linguagens de programação estruturadas. Nessas antigas linguagens não existem exceções como nas orientadas a objetos. Tratar erros através de exceções é muito mais legível, pois deixa claro o processamento que se faz quando tudo ocorre bem e o que se faz quando algo der errado, o que não acontece com códigos de erros, pois os tratamentos desses erros acabam incorporados a lógica do programa. Tabela 17. Compar ação entr e o código or iginal e o código r efator ado com Substituir código de er r o por exceção (17). Código Original int withdr aw(int amount) { if (amount > _balance){ r etur n ­1; }else { _balance ­= amount; r etur n 0; } } Código Refatorado void withdr aw(int amount) thr ows BalanceInsufficientException { if (amount > _balance){ thr ow new BalanceInsufficientException(); } _balance ­= amount; }
2.2.18. Substituir exceção por teste (Replace Exception with Test) Problema : Tem­se um método que gera uma exceção em condições que poderia haver um teste primeiro. Solução: Altera­se o método para fazer o teste antes da operação que gera a exceção. Motivação: Exceções têm um propósito bem definido: avisam­nos que um comportamento excepcional, inesperado aconteceu. Assim, as mesmas não devem ser usadas em demasia e como substituas por testes condicionais. Se o erro pode acontecer com grande possibilidade, então se testa primeiro para que se evite o erro. Tabela 18. Compar ação entr e o código or iginal e o código r efator ado com Substituir exceção por teste (18). Código Original double getValueFor Per iod (int per iodNumber ){ tr y { r etur n _values[per iodNumber ]; }catch (Ar r ayIndexOutOfBoundsException e){ r etur n 0; } } Código Refatorado double getValueFor Per iod (int per iodNumber ) { double value = 0; if (per iodNumber < _values.length){ value = _values[per iodNumber ]; } r etur n value; } 2.2.19. Substituir valor mágico por constante simbólica Problema : Tem­se um valor literal com significado especial. Solução: Cria­se uma constante, nomeia­a de acordo com seu significado e substitui o literal por ela. Motivação: Valores mágicos são muito antigos na computação. Muitas vezes depara­se com literais no meio de um processamento e não se sabe ao certo o porquê dele estar ali, e o pior não é isso, muitas vezes esse valor se repete várias vezes e uma possível alteração nesse valor pode causar um trabalho extra para substituí­lo pelo novo valor em todos as partes do código que o referenciam. Observação: O nome dessa técnica no site de FOWLER é Replace Magic Number with Symbolic Constant (Substituir número mágico por constante simbólica), mas modificamos seu nome aqui, pois vemos isso acontecer não apenas com números, mas também com strings e chars. Tabela 19. Compar ação entr e o código or iginal e o código r efator ado com Substituir valor mágico por constante simbólica (19).
Código Original double potentialEner gy(double mass, double height) { r etur n mass * height * 9.81; } Código Refatorado double potentialEner gy(double mass, double height) { r etur n mass * GRAVITATIONAL_CONSTANT * height; } static final double GRAVITATIONAL_CONSTANT = 9.81; 2.2.20. Inverter condicional (Reverse Conditional) Problema : Tem­se um condicional que seria mais fácil de entender se seu sentido fosse invertido. Solução: Inverte­se o sentido do condicional e reordenam­se as cláusulas condicionais. Motivação: Muitas vezes condicionais podem ser redigidos de uma forma que se torna difíceis de lê­los. Uma negação em conjunto com uma clausula else é particularmente um desses casos. Então para se evitar essa complexidade extra inverte­se o condicional. Tabela 20. Compar ação entr e o código or iginal e o código r efator ado com Inver ter condicional (20). Código Original Código Refatorado if ( !isSummer ( date ) ){ char ge = winter Char ge( quantity ); if ( isSummer ( date ) ){ char ge = summer Char ge( quantity ); }else{ char ge = summer Char ge( quantity ); }else{ char ge = winter Char ge( quantity ); } } 2.2.21. Substituir array por objeto (Replace Array with Object) Problema : Tem­se um array ou coleção no qual os elementos não representam a mesma idéia. Solução: Substitui­se o array ou coleção por um objeto que contenha um campo para cada elemento.
Motivação: Arrays e coleções são estruturas de dados usadas para organizar e armazenar dados semelhantes. Freqüentemente vêem­se códigos que armazenam dados diferentes e criam convenções do tipo “o primeiro elemento é o nome, o segundo o CPF...” que não são fáceis de lembrar. Com um objeto que contenham esses campos (nome, CPF e etc) pode­se dar nomes e métodos que retornem ou modifiquem esses valores de modo que não precise­se saber tais convenções. Tabela 21. Compar ação entr e o código or iginal e o código r efator ado com Substituir ar r ay por objeto (21). Código Original Str ing[] r ow = new Str ing[3]; r ow [0] = " Liver pool" ; r ow [1] = " 15" ; Código Refatorado Per for mance r ow = new Per for mance(); r ow.setName(" Liver pool" ); r ow.setWins(" 15" ); 3. Proposta ­ RP: Refactored Programming Apresentado as técnicas acima a proposta deste artigo é utilizá­las no desenvolvimento de software como um novo estilo de programação, nomeado Refactored Programming. Esse estilo de programação não é custoso de se aprender, principalmente para desenvolvedores mais experientes, e na verdade nem é necessário, basta apenas ter uma idéia de quais técnicas existem e durante o desenvolvimento ao se deparar com algo similar dar uma lida no catálogo apresentado aqui e aplicar imediatamente, além disso, quanto mais o programador se dispõe a usar as técnicas mais automáticas elas vão se tornando, até que se chega num ponto onde o custo de se desenvolver com qualidade caia a quase zero, e o tão desastroso método rápido e sujo (Quick and dirty) dá lugar ao rápido e limpo (Quick and Clean). Lembrando que a idéia principal é utilizá­las em tempo de codificação, proporcionando ao software sempre um código de qualidade e que não necessite­se gastar tempo posteriormente para adequá­lo melhor. Os benefícios, como já citados anteriormente são: código legível, simples e de fácil entendimento; facilidade na manutenção do software; percepção de bugs facilitada, diminuição dos riscos de inserir novos bugs e diminuição da quantidade de testes além de vários outros. É importante deixar registrado que o uso do Refactored Programming em conjunto com metodologias ágeis de desenvolvimento, como XP ou Scrum, pode
otimizar ainda mais os benefícios citados acima, pois apesar de deixar o código de um projeto de software com uma alta qualidade há casos em que não pode­se prever e é onde o refactoring da forma tradicional deve ser aplicado para correção dos mesmos, assim pode­se dizer que não estar­se­á definindo aqui uma metodologia mas sim um estilo de programação. Para que isso fique claro para os leitores deste artigo é de interesse dos autores numa próxima oportunidade demonstrarem os benefícios da integração dessas duas idéias: Refactoring Programming em conjunto com Scrum. Refactored Programming é definida como um estilo de programação baseado em técnicas de refactoring, mas que podem e devem ser usadas em tempo de codificação possibilitando ao programador desenvolver um software de maneira rápida e limpa. 4. Aplicação da Pr oposta RP A RP (Refactoring Programming) pode ser aplicada de várias formas no desenvolvimento de um software, tanto no inicio de sua concepção quanto no decorrer desta. A diferença é que no primeiro caso garante­se uma maior qualidade ao código já que as boas práticas listadas aqui serão praticadas desde o início do projeto, já no segundo caso os benefícios também serão notáveis, a questão é que será necessário um maior apoio do refactoring na forma tradicional visto que como a RP não foi aplicada desde o início muito código “sujo” provavelmente já terá sido escrito. Foi feita uma simples aplicação da RP na empresa onde trabalha um dos autores deste artigo, onde um caso de uso foi desenvolvido utilizando­se da mesma, ao final pediu­se que um dos membros da equipe, recém chegado, explicasse o que aquele determinado código fazia. Rapidamente o referido membro conseguiu explicar todo o processamento que o trecho do software fazia, apenas olhando­se os nomes dos métodos, variáveis, constantes e etc. Em seguida o mesmo foi pedido para ele só que em um outro código, feito de qualquer forma, e ele não soube dizer nada. O engraçado é que os códigos faziam a mesma coisa. 5. Resultados Para comprovar o que se tem dito no presente artigo foram realizados alguns testes, que ocorreram da seguinte maneira: Foram selecionados seis desenvolvedores,
esses foram divididos em dois grupos de três desenvolvedores cada. Em cada grupo havia um desenvolvedor com menos de 1 ano de experiência e dois com 3 ou mais anos de experiência no mercado de trabalho. Cada desenvolvedor recebeu um código escrito em linguagem Java, os desenvolvedores do grupo A receberam um código que segue as recomendações da RP (refatorado) e os do grupo B um código feito de qualquer modo (não refatorado), os códigos de um desenvolvedor do mesmo grupo eram exatamente iguais. Os códigos faziam a mesma coisa e tinham um mesmo erro, foi dada a saída a qual era esperada e pediu­se aos desenvolvedores que, individualmente, encontrassem e corrigissem o referido erro de modo que a saída do programa fosse a que vos foi passada. Com isso foram cronometrados o tempo de resolução do erro para cada desenvolvedor e com esses dados calculados os resultados. Os gráficos a seguir demonstram os resultados obtidos. Para que se possa entendê­los melhor segue explicação: Foi considerado que o tempo que o desenvolvedor levou para corrigir o erro no código não refatorado fosse o tempo máximo planejado para a quele determinado caso. Assim, os tempos de resolução do código não refatorado sempre são considerados como 100%, ou seja, gastou­se o máximo de tempo previsto, e com base nisso foi feito o calculo para saber­se a percentagem do tempo de resolução do código refatorado. Desenvolvedores com até 1 ano de experiência 120,00% 100% 100,00% 79,30% 80,00% 60,00% 40,00% 20,00% 0,00% CODIGO REFATORADO CODIGO NÃO REFATORADO
No gráfico acima observa­se que entre os desenvolvedores com até um ano de experiência a média do tempo de resolução do problema fica em torno de 20,7% (100% ­ 79,3%) mais rápida com um código refatorado. Desenvolvedores com mais de 3 anos de experiência 120,00% 100% 100,00% 80,00% 61,60% 60,00% 40,00% 20,00% 0,00% CODIGO REFATORADO CODIGO NÃO REFATORADO No gráfico acima observa­se que entre os desenvolvedores com 3 ou mais anos de experiência a média do tempo de resolução do problema fica em torno de 38,4% (100% ­ 61,6%) mais rápida com um código refatorado. Todos os desenvolvedores 120,00% 100% 100,00% 80,00% 72,00% 60,00% 40,00% 20,00% 0,00% CODIGO REFATORADO CODIGO NÃO REFATORADO
No gráfico acima observa­se a média do tempo de resolução do problema considerando­se todos os desenvolvedores fica em torno de 28% (100% ­ 72%) mais rápida com um código refatorado. 6. Consider ações Finais Após o desenvolvimento deste artigo é conclusivo que a codificação de software pode ser feito de uma maneira rápida, limpa, e eficiente (Quick and clean). Para tanto basta­se apenas utilizar­se de técnicas que vão garantir qualidade em cada nova linha de código escrita pelos programadores. Além disso, é fato que o custo da aplicação dessas técnicas é aceitável, visto que a relação (tempo gasto para codificação/tempo gasto para manutenção) é melhor que as formas tradicionais de desenvolvimento, que pregam rapidez a todo custo na hora da implementação sem pensar que mais tarde numa possível alteração, esse tempo economizado será totalmente perdido. As técnicas do RP, apresentadas aqui, servem como um manual de boas práticas que se adotadas pelas equipes de desenvolvimento trarão benefícios inestimáveis principalmente durante a manutenção do software, posteriormente trará mais segurança as empresas, pois com um código tão legível a dependência destas pelos seus antigos funcionários cai bruscamente, pois novos funcionários rapidamente entenderiam esses legados. Além disso, a RP vem a somar nas metodologias de desenvolvimento ágil, como uma melhoria das mesmas, uma complementação, ambas são facilmente integráveis. A questão é que as metodologias ágeis gastam algum tempo com refactoring e conseqüentemente testes em cima das alterações feitas, tempo este que diminuiria bastante com a integração do RP as mesmas. É importante que fique claro que a RP não é solução para todos os problemas de flexibilidade de um software, não é apenas com sua utilização que um software obterá essa característica. Observa­se que a RP é uma solução para nível de código, mas são necessárias outras soluções para níveis mais altos, pois a mesma não resolve problemas arquiteturais por exemplo.
7. Agr adecimentos Deixo expresso meu sincero agradecimento às seguintes instituições e pessoas, sem as quais o presente trabalho teria sido impossível: ­ ao meu orientador e amigo Professor Luciano Cabral, da Fatec e UFRPE, que foi o incentivador inicial desta proposta de artigo e que me apoiou em todos os momentos do desenvolvimento do mesmo; ­ aos colegas de trabalho, que fizeram sugestões e me ajudaram a realizar os testes da proposta apresentada aqui, em especial Filipe Almeida, Igor Ramos, Ido Júnior, Danilo Costa, Bruno Ribeiro e André Novaes. ­ a Faculdade de Tecnologia de Pernambuco ­ FATEC, pela acolhida no Curso de Ciência da Computação; ­ a TCI BPO, pela permissão de utilização de tempo de trabalho para desenvolvimento deste artigo. 8. Refer ências FOWLER, Martin. Refactoring. Disponível em: <http://www.refactoring.com>. Acesso em Outubro e Novembro de 2008. Portal que descreve técnicas, literatura e códigos relacionados à refactoring. – . Refatoração: Aperfeiçoando o projeto de código existente. Bookman, 2004. TELES, Vinicius Manhães. Extreme Programming. Disponível em: <http://www.improveit.com.br/xp>. Acesso em: 20 out. 2008.
Download

- Unibratec