Artigo Java Magazine 46 - Refactoring: da Teoria à Prática
Como como utilizar técnicas de refactoring para simplificar a manutenção e melhorar a qualidade e a evolução do seu código.
Melhorando a estrutura do seu código-fonte
Utilizando técnicas de refactoring para simplificar a manutenção e melhorar a qualidade e a evolução do seu código
O refactoring (ou refatoração) é uma atividade comum em processos ágeis como o Extreme Programing, nos quais as mudanças no código são constantes. Essa técnica, no entanto, não é utilizada apenas em softwares desenvolvidos sob esses processos. Seu uso é muito mais abrangente e traz como conseqüência direta um código mais limpo, de manutenção mais fácil e com maior qualidade.
Neste artigo veremos os conceitos de refactoring – quando refatorar uma aplicação e como fazê-lo – e nos apoiaremos nos comandos de refatoração do IDE Eclipse para demonstrar tais tarefas. Embora seja usado um IDE para automatizar alguns passos, o enfoque deste artigo será nas atividades de refatoração em si, de forma independente de ferramentas, bem como nos conceitos fundamentais envolvidos.
O Contexto
Quem nunca se deparou com um código no qual a alteração de um simples método levou à modificação de dezenas de linhas de código? E quando foi necessário manter um método "faz tudo" com 500 linhas? Isso sem falar daqueles métodos com dezenas de parâmetros, códigos duplicados e algoritmos que ninguém consegue entender.
Esses sintomas são denominados "badsmells" (maus cheiros) pelos especialistas em refactoring, e resultam principalmente de um design ruim, ou mesmo da falta de design. Os problemas ficam evidentes quando se deseja incluir uma nova funcionalidade ou alterar o código existente: normalmente é difícil (ou até impossível) mudar o código sem quebrar o funcionamento do programa.
Mas por que alterar?
Suponha que você vem utilizando um componente qualquer, que tem funcionado adequadamente ao longo de vários anos. Durante esse período, o componente foi usado em diversos sistemas e demonstrou funcionamento e performance adequados, o que o tornou bastante confiável.
Agora seu componente será utilizado em um novo sistemas, e para isso algumas alterações deverão ser efetuadas por questões de compatibilidade. O autor do componente não está mais na empresa, e coube a você assumir a nova tarefa.
Só que, se o componente não tiver sido bem projetado, o que parecia uma alteração simples pode se tornar um pesadelo: sistemas com design ruim são difíceis de alterar. Nestes casos, entender o que deve ser mudado não é uma tarefa simples, e são grandes as chances de se introduzir bugs ao longo do processo.
Cria o código é apenas o início. Todos nós sabemos que os sistemas evoluem, e que o que foi acordado com o cliente hoje nem sempre valerá amanhã. Por isso, código bom não é código que "apenas funciona": é código que também é simples de entender, alterar e estender.
As técnicas de refactoring melhoram o design do software e facilitam seu entendimento. Como conseqüência, o tornam mais simples e facilitam a descoberta de bugs.
Entendendo a técnica
Refactoring é uma técnica poderosa, e que precisa ser aplicada com cuidado. O princípio é simples: pequenas alterações são efetuadas de forma incremental no código, enquanto a funcionalidade do sistema permanece inalterada.
A capacidade de refactoring está presente em todos os IDEs modernos, com diferentes graus de sofisticação. Em geral as operações de refactoring automatizadas pelas IDEs são bastante seguras e podem ser efetuadas sem receio.
Dentre os IDEs livres, o Eclipse e o NetBeans destacam-se pela diversidade de comandos, possuindo funcionalidades equivalentes (o plug-in Jackpot aumenta bastante o poder de refactoring do NetBeans, mas deve ser instalado à parte1). Já o IntelliJ IDEA (um produto proprietário) é o IDE com maior capacidade de refactoring existente.
Todas as operações de refactoring disponíveis nas ferramentas de desenvolvimento seguem o catálogo proposto por Martin Fowler (veja as referências ao final do artigo). Porém, como veremos ao longo do texto, nem todas possuem suporte automatizado.
Refatorando
A melhor maneira de fixar os conceitos de refactoring é praticando. A Listagem 1 apresenta a classe CartaoUtil. Nela, o método validar() verifica números de cartões de crédito e imprime uma mensagem indicando se o número do cartão é válido ou não. Os parâmetros para validação são a bandeira do cartão (Visa, MasterCard, American Express ou Diners), além de seu número e data de validade.
package br.com.jm.refactoring;
import java.text.”;
import java.util.*;
public class CartaoUtil {
public static final int VISA = 1;
public static final int MASTERCARD = 2;
public static final int AMEX = 3;
public static final int DINERS = 4;
public static final String CARTAO_OK = “Cartão válido”;
public static final String CARTAO_ERRO = “Cartão inválido”;
public String validar(int bandeira, String numero, String validade) {
boolean validade ok = false;
// ----- VALIDADE -----
Date dataValidade = null;
try {
dataValidade = new SimpledateFormat(“MM/yyyy”).parse(validade);
} catch (ParseException e) {
return CARTAO_ERRO;
}
Calendar calValidade = new GregorianCalendar();
calValidade.setTime(dataValidade);
// apenas mês e ano são utilizados na validação
Calendar calTemp = new GregorianCalendar();
Calendar calHoje = (GregorianCalendar) calValidade.clone();
calHoje.set(Calendar.MONTH, calTemp.get(Calendar.MONTH));
calHoje.set(Calendar.YEAR, calTemp.get(Calendar.YEAR));
validadeOK = calHoje.before(calValidade);
if (!validadeOK) {
return CARTAO_ERRO;
}
else {
// ---- PREFIXO E TAMANHO -----
String formatado = “”;
// remove caracteres não-numéricos
for (int i = 0; i < numero.length(); i++) {
char c = numero.chartAt(i);
if (Character.isDigit(c) {
formatado += c;
}
}
boolean formatoOK = false;
switch (bandeira) {
case VISA: // tamanhos 13 ou 16, prefixo 4.
if (formatado.startsWith(“4”) &&
(formatado.length() == 13 ||
formatado.length() == 16 )) {
formatoOK = true;
} else {
formatoOK = false;
}
break;
case MASTERCARD: // tamanho 16, prefixos 51 a 55
if ((formatado.startsWith(“51”) ||
formatado.startsWith(“52”) ||
formatado.startsWith(“53”) ||
formatado.startsWith(“54”) ||
formatado.startsWith(“55”) &&
formatado.length() == 16) {
formatoOK = true;
} else {
formatoOK = false;
}
break;
case AMEX: // tamanho 15, prefixos 34 e 37.
if ((formatado.startsWith(“34”) ||
formatado.startsWith(“37”) &&
formatado.length() == 15 ) {
formatoOK = true;
} else {
formatoOK = false;
}
break;
case DINERS: // tamanho 14, prefixos 300 305, 36 e38.
if ((formatado.startsWith(“300”) ||
formatado.startsWith(“301”) ||
formatado.startsWith(“302”) ||
formatado.startsWith(“303”) ||
formatado.startsWith(“304”) ||
formatado.startsWith(“305”) ||
formatado.startsWith(“36”) ||
formatado.startsWith(“38”) &&
formatado.length() == 14) {
formatoOK = true;
} else {
formatoOK = false;
}
break;
default:
formatoOK = false;
break;
}
if (!formatoOK) {
return CARTAO_ERRO;
}
else {
// ----- NÚMERO -----
// fórmula de LUHN (http://www.merriampark.com/anatomycc.htm)
int soma = 0;
int digito = 0;
int somafim = 0;
boolean multiplica = false;
for (int 1 = formatado.length() – 1; i >= 0; i--) {
digito = Integer.parseInt(formatado.substring(i,i+1));
if (multiplica) {
somafim = digito * 2;
if (somafim > 9) {
somafim -= 9;
}
} else {
somafim = digito;
}
soma += somafim;
multiplica = !multiplica;
}
int resto = soma % 10;
if (resto == 0) {
return CARTAO_OK;
} else {
return CARTAO_ERRO;
}
}
}
}
}
Analisando o exemplo
Aparentemente o código inicial funciona bem, e valida os quatro tipos de cartão corretamente. Uma análise criteriosa, no entanto, mostra uma série de problemas.
O primeiro ponto que merece destaque é que o código não é verdadeiramente orientado a objetos. Percebe-se que não existe a abstração do principal elemento do negócio: o cartão de crédito. As quatro bandeiras são definidas através de constantes numéricas, o que oferece pouca flexibilidade (se uma nova bandeira tiver que ser adicionada, o código terá que ser alterado).
Além disso, a validade do cartão é definida através de um parâmetro do tipo String, que deve obedecer a uma formatação específica: DD/AAAA. Esse tipo de detalhe torna a aplicação "frágil" e aumenta a possibilidade de bugs.
A instrução switch também contribui para que o código se torne procedural. Se você deseja escrever um código realmente orientado a objetos, de evitar essa construção (como veremos posteriormente, em casos como o mostrado o swicht pode ser facilmente substituído pelo uso de polimorfismo).
Outro problema evidente problema evidente é o tamanho do método. Métodos muito extensos são um indicativo de que fazem mais do que deveriam. A quantidade de condições (ifs e elses) torna complicada sua leitura e aumenta o custo de manutenção. A grande quantidade de returns também dificulta a compreensão do método, pois não é simples entender o seu fluxo.
Por fim, utilizar uma mensagem como retorno do método também não é a melhor opção. Imagine que a mensagem tivesse que ser escrita em HTML, ou então ser internacionalizada para outros idiomas. Neste caso, o código teria que ser modificado, embora sua função principal não tenha relação com a interface com o usuário.
Passo 1:"enxugando" as construções
Antes de aplicar os padrões do catálogo de refactoring, vamos simplificar um pouco o código. Uma análise rápida mostra dois pontos do programa que são suscetíveis à simplificação: laços e condicionais. Utilizaremos expressões regulares para tornar esses trechos mais limpos.
O laço for a seguir remove caracteres inválidos no número do cartão, conservando apenas os dígitos. O número 5501-7615-8613-3399, por exemplo, seria formatado para 55017615861333992:
String formatado ="";
for (int i=0; i<numero.length();i++{
char c=numero.charAt(i);
if(Character.isDigit(c)){
formatado +=c;
}
}
O código não é difícil de entender, mas apresenta ao menos dois problemas: a quantidade de linhas necessárias para resolver uma tarefa simples e a concatenação de objetos do tipo String. Em Java, como sabemos, Strings são imutáveis. Isso significa que, a cada concatenação, um novo objeto será criado para armazenar o valor do novo texto. Neste código, por exemplo, a variável formato seria recriada dezesseis vezes. Uma possível solução seria utilizar a classe StringBuffer, mas existe uma forma ainda mais simples de resolver o problema.
Utilizando expressões regulares3, podemos solucionar os dois problemas de uma só vez, obtendo um código simples e de funcionalidade idêntica:
String formatado=numero.replaceAll(“\\D”,"");
O segundo ponto passível de alteração são as instruções case. Cada uma dessas instruções valida o formato do cartão de acordo com a bandeira. Os cartões MasterCard, por exemplo, devem iniciar com os números entre 51 e 55 e possuir 16 dígitos:
if((formatado.starsWith("51")||
formatado.startsWith("52")||
formatado.startsWith("53")||
formatado.startsWith("54")||
formatado.startsWith("55")&&
formatado.length()==16){
valido = true;
}else{
valido = false;
}
Todo esse código pode ser substituído por uma expressão regular simples:
valido=formatado.matches("^5[1-5]\\d$");
Modificando apenas o laço for e as instruções case, já temos uma diminuição significativa na quantidade de linhas e na complexidade do código – veja a Listagem 2. (Nesta e em listagens posteriores, omitimos a parte que não foi alterada depois do refactoring em questão).
Destacamos que neste ponto os casos de teste devem ser re-executados para certificar que o código não foi "quebrado".
Após essa primeira "limpeza", partiremos para operações de refactoring propriamente ditas.
package br.com.jm.refactoring;
import java.text.*;
import java.util.*;
public class CartaoUtil {
// constants da classe
public String validar(int bandeira, String numero, String validade) {
…
if (!validadeOK) {
return CARTAO_ERRO;
}
else {
// ----- PREFIXO E TAMANHO -----
String formatado = numero.replaceAll(“\\D”, “”);
boolean formatoOK = false;
switch (bandeira) {
case VISA; // tamanhos 13 ou 16, prefixo 4.
formatoOK = formatado.matches(“^4(\\d|\\d)$”);
break;
case MASTERCARD; // tamanho 16, prefixos 51 A 55
formatoOK = formatado.matches(“^5[1-5]\\d$”);
break;
case AMEX; // tamanho 15, prefixos 34 e 37
formatoOK = formatado.matches(“^3[47]\\d$”);
break;
case DINERS; // tamanho 14, prefixos 300 a 305, 36 e 38.
formatoOK = formatado.matches(“^3[68]\\d|0[0-5]\\d)$”);
break;
default;
formatoOK = false;
break;
}
If (!formatoOK) {
return CARTAO_ERRO;
}
else {
// fórmula de LUHN
}
}
}
}
Passo 2: alterando o retorno
Como analisado anteriormente, o uso de uma mensagem textual como retorno do método traz uma série de inconvenientes para a evolução do sistema. Para resolver o problema, faremos duas alterações no código; trocaremos o tipo de retorno para boolean e criaremos exceções para os possíveis erros de validação.
Neste passo mudaremos o retorno do método. As exceções serão tratadas no Passo 4.
O retorno do método pode ser alterado através do refactoring Change Method Signature. Para efetuar esse refactoring no Eclipse, selecione o método validar() e escolha a opção de menu Refactor>Change Method Signature. A tela da Figura 1 será exibida. Altere o tipo de retorno para boolean e escolha OK; na próxima tela serão exibidos alguns erros de conversão. Ignore-os e aceite as alterações.
O código refatorado apresentará erros nos locais em que as constantes CARTAO_OK e CARTAO_ERRO são referenciadas, pois essas constantes são do tipo String. Para que o código continue compilável, as constantes deverão ser alteradas manualmente para o tipo boolean4:
public static final boolean CARTAO_OK = true;
public static final booelan CARTAO_ERRO = false;
Agora que o método retorna um valor booleano indicando o estado do cartão, as constantes CARTAO_OK e CARTAO_ERRO perderam sua utilidade e podem ser removidas. Para remover as constantes de forma segura; usaremos o refactoring Inline constant, que substitui a referência à constante pelo seu valor.
Para realizar a operação basta marcar a constante e selecionar o menu Refactor>Inline. A Figura 2 será exibida. Aceite as opções default e clique em OK. Repita o comando para a outra constante.
Passo 2: criando métodos auxiliares
O método validar() efetua três testes distintos para decidir se o cartão é válido ou não. Primeiro a data de validade é analisada; caso a data esteja correta, o formato do número é checado (prefixo e tamanho). Por fim, se os dois primeiro testes tiverem sucesso, o formato do número é conferido através da fórmula de LUHN (também conhecida como "Módulo 10").
Essa implementação torna o método grande e aumenta a complexidade da manutenção. É recomendável que cada um desses testes seja separado em um método distinto, facilitando o entendimento e permitindo que futuras alterações possam ser feitas de forma pontual. A separação dos métodos será feita através do refactoring Extract method.
Para extrair cada método, basta selecionar o trecho que corresponderá ao método e escolher o comando de menu Refactor>Extract Method. Uma tela semelhante à da Figura 3 será mostrada. Preencha o novo nome do método e aceite as opções default.
Neste exemplo, os três novos métodos foram criados com os nomes isValidadeOK(), isFormatoOK() e isNumeroOK().
A nova classe é exibida na Listagem 3. É importante salientar que nenhum refinamento foi feito ainda sobre os métodos criados. Estes métodos são apenas o resultado do refactotring efetuado pelo Eclipse.
package br.com.jm.refactoring;
import java.text.*;
import java.util.*;
public class CartaoUtil {
// constantes da classe
public boolean validar(int bandeira, String numero, String validade) {
boolean validadeOK = false;
validadeOK = isvalidadeOK(validade);
if (!validadeOK(validade);
return false;
}
else {
// ----- PREFIXO E TAMANHO -----
String formatado = numero.replaceAll(\\D, “”);
boolean formatoOK = false;
formatoOK = isFormatoOK(bandeira, formatado);
if (!formatoOK) {
return false;
}
else {
return isNumeroOK(formatado);
}
}
}
private boolean isValidadeOK(String validade) {
boolean validadeOK;
// ----- VALIDAE ------
Date dataValidade = null;
try {
dataValidade = new SimpleDateFormate(“MM/YYYY”).parse(validade);
} catch (ParseException e) {
return false;
}
Calendar calValidade = new GregorianCalendar();;
calValidade.setTime(datValidade);
// apenas mês e ano são utilizados na validação
Calendar calTemp = new GregorianCalendar();
Calendar calHoje = (GregorianCalendar) calValidade.clone();
callHoje.set(Calendar.MONTH, calTemp.get(Calendar, MONTH));
callHoje.set(Calendar.YEAR, calTemp.get(Calendar, YEAR));
validadeOK = calHoje.before(calValidade);
return validadeOK
}
private boolean isFormatoOK(int bandeira, String formatado) {
bolean formatoOK;
switch (bandeira) {
case VISA; // tamanhos 13 ou 16, prefixo 4.
formatoOK = formatado.matches(“^4(\\d|\\d)$”);
break;
case MASTERCARD; // tamanho 16, prefixos 51 A 55
formatoOK = formatado.matches(“^5[1-5]\\d$”);
break;
case AMEX; // tamanho 15, prefixos 34 e 37
formatoOK = formatado.matches(“^3[47]\\d$”);
break;
case DINERS; // tamanho 14, prefixos 300 a 305, 36 e 38.
formatoOK = formatado.matches(“^3[68]\\d|0[0-5]\\d)$”);
break;
default;
formatoOK = false;
break;
}
return formatoOk;
}
private boolean isNumeroOK(String formatado) {
// -----NÚMERO-----
// fórmula de LUHN (http://www.merriampark.com/anatomycc.htm)
int soma = 0;
int digito = 0;
int somafim = 0;
boolean multiplica = false;
for (int 1 = formatado.length() – 1; i >= 0; 1--) {
digito = integer.parseInt(formatado.substring(i,i+1));
if (multiplica) {
somafim = digito * 2;
if (somafim > 9) {
somafim -= 9;
}
} else {
somafim = digito;
}
soma += somafim;
multiplica = !multiplica;
}
int resto = soma % 10;
if (resto == 0) {
return true;
} else {
return false;
}
}
}
Passo 3: removendo os parâmetros
Analisando novamente o método validar(), percebemos que todos os parâmetros são, na verdade, atributos de um cartão de crédito. Porém, até o momento essa abstração não existe, e o cartão é representado através do conjunto de parâmetros do método: bandeira, número e validade. Uma evolução natural do código deve contemplar essa classe.
Antes de criar a classe Cartao, trabalharemos nos parâmetros do método validar(). Os três parâmetros serão convertidos em atributos da classe CartaoUtil, e o atributo validade será desmembrado em dois atributos inteiros: mes e ano5 (esse tratamento minimiza eventuais erros de formatação). É importante notar que todas essas operações serão feitas manualmente, já que o Eclipse não oferece suporta automatizado para essa etapa.
Em seguida, a classe CartaoUtil será renomeada para Cartao (visto que agora reflete essa abstração) e um construtor será adicionado, com os novos atributos. Como o método de validação agora não possui mais parâmetros e seu retorno é booleano, vamos renomeá-lo para isValido().
Para renomear a classe e o método, é recomendável utilizar o comando de refactoring Rename (Refactor>Rename), que já efetua todas as alterações na classe e em suas dependentes.
Passo 4: adicionado Exceptions
No Passo 1 alteramos o retorno do método para booleano, o que resolveu o problema das mensagens mas trouxe um inconveniente: não é possível saber a causa do erro de validação. Para resolver esse problema usaremos exceções.
As mudanças deste passo seguem o refactoring Replace Error Code with Exception, que não é suportado de forma automática pelo Eclipse. Assim, as exceções deverão ser codificadas manualmente.
Para seguir rigorosamente o refactoring Replace Error Code with Exception como definido no catálogo, ele deveria ser feito no primeiro passo, substituindo as mensagens de erro por exceções. Transferimos o refactoring para esse passo para simplificar o entendimento.
Criaremos três exceções, que refletem os erros da nossa regras de validação: ValidadeIncorretaException, NumeroIncorretoException e FormatoIncorretoException (Listagens 4,5 e 6). As exceções serão introduzidas nos métodos correspondentes para indicar cada uma das situações de erro. É importante salientar que todas as exceções são do tipo RuntimeException, o que significa que seu tratamento não é obrigatório no código chamador.
A substituição de códigos de erro por RuntimeExceptions deve ser feita de forma cuidadosa, pois é fácil introduzir bugs. Uma estratégia é, num primeiro momento, utilizar exceções checadas e verificar pontos do código onde eventualmente será necessário utilizar blocos finally. Após essa etapa, é seguro transformar as exceções em RuntimeExceptions.
As mudanças correspondentes aos Passos 3 e 4 estão refletidas na Listagem 7.
package br.com.jm.refactoring.excecao;
import br.com.jm.refactoring.Cartao;
public class ValidadeIncorretaException extends RuntimeException {
public ValidadeIncorretaException (Cartao cartao) {
super(cartao.getMes() + “/” + cartao.getAno());
}
}
package br.com.jm.refactoring.excecao;
import br.com.jm.refactoring.Cartao;
public class NumeroIncorretoException extends RuntimeException {
public NumeroIncorretoException (Cartao cartao) {
super(cartao.getNumero());
}
}
package br.com.jm.refactoring.excecao;
import br.com.jm.refactoring.Cartao;
public class FormatoIncorretoException extends RuntimeException {
public FormatoIncorretoException (Cartao cartao) {
super(cartao.getNumero());
}
}
package br.com.jm.refactoring;
import java.text.*;
import java.util.*;
import br.com.jm.refactoring.excecao.*;
public class Cartao {
//... Constantes da classe
private int bandeira;
private String numero;
private int mes;
private int ano;
public Cartao(int bandeira, String numero, int mes, int ano) {
this.bandeira = bandeira;
this.numero = numero;
this.mes = mes;
this.ano = ano;
}
//getters omitidos
public boolean isValido() {
...
}
private boolean isValidadeOK(int mes, int ano) {
...
}
private boolean isFormatoOK(int bandeira, String formatado) {
...
}
private boolean isNumeroOK(String formatado) {
...
}
}
Passo 5: switch e classes do modelo
A última mudança no código trata do switch. Como indicado anteriormente, implementar a lógica de validação das bandeiras através desse condicional não é a melhor estratégia; a cada nova bandeira a classe Cartao deverá ser alterada para adição de um novo case. Além disso, misturar validações distintas em um mesmo método pode prejudicar o entendimento do código.
A solução para esse problema consiste em separar cada validação em uma classe específica (sob uma mesma hierarquia), e através de polimorfismo executar a validação apropriada. Ao todo, serão criadas quatro classes, uma para cada bandeira.
A alteração no código de validação será feita através do refactoring Replace Conditional with Polymorphism. Esse refactoring também não possui suporte automatizado no Eclipse, portanto as mudanças deverão ser feitas manualmente.
A refatoração será efetuada aplicando-se o pattern Template Method. Primeiro será criado o método formato(), responsável por retornar o formato utilizado na validação da bandeira. Esse método será abstrato na classe Cartao (que será transformada em abstrata) e implementado em cada uma das bandeiras.
protect abstract String formato();
Como o formato será definido através de polimorfismo, o atributo bandeira e as constantes das bandeiras não têm mais valor e serão removidos da classe. Também serão necessárias algumas mudanças no método isFormatoOK() para refletir a nova estratégia:
private boolean isFormatoOK(String formatado){
if(!formatado.matches(formato())){
throw new FormatoIncorretoException(this);
}
else{
return true;
}
}
Com a nova implementação, o método isFormatoOK() chama o método formato() sempre que precisa verificar o formato de um cartão. Esse método estará implementado em casa uma das bandeiras e será executado conforme a instância criada. Para cartões MasterCard, por exemplo, o método retorna o seguinte formato:
protected String formato(){
return "^5[1-5]\\d$";
}
A nova classe Cartao encontra-se na Listagem 8. As classes para cada uma das bandeiras podem ser vistas nas Listagens de 9 a 12.
package br.com.jm.refactoring;
import java.text.*;
import java.util.*;
import br.com.jm.refactoring.excecao.*;
public abstrat class Cartao {
private String numero;
private int mes;
private int ano;
public Cartao (String numero, int mes, inte ano) {
this.numero = numero;
this.mes = mes;
this.ano = ano;
}
//getters omitidos
public boolean isValido() {
...
}
private boolean isValidadeOK(int mes, int ano) {
...
}
protected abstract String formato();
private boolean if FormatoOK(String formatado) {
if (!formatado.matches(formato())) {
throw new FormatoIncorretoException(this);
}
else {
return true;
}
}
private boolean isNumeroOK(String formatado) {
...
}
}
package br.com.jm.refactoring;
public class visa extends Cartao {
public Visa(String numero, int mes, int ano) {
super(numero, mes, ano);
}
@Override
protected String formato() {
return “^4(\\d|\\d)$”;
}
}
package br.com.jm.refactoring;
public class visa extends Cartao {
public Mastercard(String numero, int mes, int ano) {
super(numero, mes, ano);
}
@Override
protected String formato() {
return “^5[1-5]\\d$”;
}
}
package br.com.jm.refactoring;
public class Amex extends Cartao {
public Amex(String numero, int mes, int ano) {
super(numero, mes, ano);
}
@Override
protected String formato() {
return “^3[47]\\d$”;
}
}
package br.com.jm.refactoring;
public class Diners extends Cartao {
public Diners(String numero, int mes, int ano) {
super(numero, mes, ano);
}
@Override
protected String formato() {
return “^3[68]\\d|0[0-5]\\d)$”;
}
}
Conclusões
Efetuando refactorings progressivos sobre o software, conseguimos torná-lo mais simples e facilitar sua evolução. A criação de bandeiras, por exemplo, tornou-se um processo simples: basta implementar uma nova classe que estenda Cartao e codificar o método formato(). As regras de cada bandeira agora estão descritas em classes específicas, e nenhuma alteração é necessária na classe Cartao.
Obviamente algumas melhorais ainda poderiam ser feitas no código para torná-lo mais robusto como, por exemplo, a alteração do parse no método isValidadeOK(), ou a inclusão de um atributo para representar o titular do cartão. Por questões de espaço, no entanto, não abordaremos essas melhorias aqui.
O refactotring é uma técnica poderosa e de extrema importância. Seu uso ajuda a tornar as aplicações mais simples e extensíveis e até prevenir bugs. A técnica é simples, e possui excelente suporte de IDEs. Seu uso constante, portanto, é altamente recomendável.
_________________________________________
[1] – O Jackpot está em desenvolvimento e é planejado para incorporação à versão 6.0 do NetBeans.
[2]- Os números de cartões utilizados nesse artigo foram criados de acordo com o algoritmo apropriado (fórmula de LUHN), mas, em princípio, não existem.
[3] – O artigo “Uma Mala Direta com JavaMail” na Edição 39 apresentou o essencial sobre expressões regulares dentro de uma aplicação prática. Você pode ver mais detalhes sobre a sintaxe dessas expressões na JavaDoc da classe java.util.regex.Pattern.
[4] – Uma alternativa é utilizar a opção “quick fix” do Eclipse. Selecione a opção Change type of 'CARTAO_ERRO' to 'boolean'. Ainda assim o valor da constante (false) terá que ser alterado manualmente.
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo