É claro que o centro das atenções é o imenso método relatório. Ao se deparar com métodos muito extensos, o melhor a se fazer é decompor o método em pequenas partes. Pequenas partes de código tende a deixas as coisas mais gerenciáveis, são mais fáceis de trabalha-los e movimenta-los.
A primeira fase do refactoring é dividir o método e mover as partes para classes que se adaptam melhor. O objetivo principal é facilitar a criação do método RealtorioHTML com a menor duplicação de código possível.
O primeiro passo é encontrar um amontoado lógico de código e utilizar a
Extração de Método. Uma parte de código óbvia são as sequências de
if-else. Essa para ser uma boa parte de código para ser isolada e ter seu próprio método.
Quando extraímos um método, assim como em qualquer outro refactoring, precisamos saber o que pode dar errado. Se fizermos uma má extração, poderemos inserir um bug ao programa. Então, antes de fazer o refactoring, precisamos pensar em como fazer isso com segurança. Para isso precisamos seguir os itens de segurança.
Primeiro precisamos localizar e identificar no fragmento de código todas as variáveis locais e parâmetros. No trecho de código que vamos extrair identificamos as seguintes variáveis: Locacao e TotalLocacao. A variável Locacao não é modificada pelo código, mas Contador e TotalLocacao são modificadas. Para qualquer variável que não é modificada pelo código eu posso defini-la no novo método como um parâmetro. Variáveis que sofrem modificação precisam de um cuidado maior. Se houver somente uma variável que sofre modificação, podemos retorna-la como resultado do método. A variável Contador, apesar de sofre modificação durante o código, ela não faz parte da regra de negócio, ela é apenas um artifício para navegar entre os itens do objeto Locacao, portanto não há necessidade de retorna-la fora do método. Sendo assim, posso definir a variável TotalLocacao como retorno do método.
A seguir mostramos o código antes e depois do refactoring. Destacaremos em negrito as modificações.
Antes
|
Depois
|
function TCliente.Relatorio: string;
var
Total, TotalLocacao: Double;
PontosCliente: Integer;
Resultado: String;
Contador: Integer;
Locacao: TLocacao;
begin
Total := 0;
PontosCliente := 0;
Resultado := 'Registro de locação de ' + Nome + #13;
// Determina total de cada locação
for Contador := 0 to Locacoes.Count - 1 do
begin
Locacao := Locacoes.Items[Contador];
TotalLocacao := 0;
if Locacao.Filme.CodigoPreco = TFilme.Regular then
begin
TotalLocacao := TotalLocacao + 2;
if (Locacao.DiasAluguel > 2) then
TotalLocacao := TotalLocacao + ((Locacao.DiasAluguel - 2) * 1.5);
end
else if Locacao.Filme.CodigoPreco = TFilme.Lancamento then
begin
TotalLocacao := TotalLocacao + (Locacao.DiasAluguel * 3);
end
else if Locacao.Filme.CodigoPreco = TFilme.Infantil then
begin
TotalLocacao := TotalLocacao + 1.5;
if (Locacao.DiasAluguel > 3) then
TotalLocacao := TotalLocacao + ((Locacao.DiasAluguel - 3) * 1.5);
end;
// Adiciona pontos de clientes
PontosCliente := PontosCliente + 1;
// Gera bonus porlocação de lançamento por dois dias
if (Locacao.Filme.CodigoPreco = TFilme.Lancamento) and (Locacao.DiasAluguel > 1) then
PontosCliente := PontosCliente + 1;
//Mostra dados dessa locação
Resultado := Resultado + '|' + Locacao.Filme.Titulo + '|' +
FloatToStr(TotalLocacao) + #13;
Total := Total + TotalLocacao;
end;
//Adiciona Rodapé
Resultado := Resultado + 'O total é: ' + FloatToStr(Total) + #13;
Resultado := Resultado + 'Você ganhou: ' + FloatToStr(PontosCliente) + ' pontos';
Result := Resultado;
end; |
function TCliente.Relatorio: string;
var
Total, TotalLocacao: Double;
PontosCliente: Integer;
Resultado: String;
Contador: Integer;
Locacao: TLocacao;
begin
Total := 0;
TotalLocacao := 0;
PontosCliente := 0;
Resultado := 'Registro de locação de ' + Nome + #13;
// Determina total de cada locação
for Contador := 0 to Locacoes.Count - 1 do
begin
Locacao := Locacoes.Items[Contador];
TotalLocacao := TotalLocacaoItem(Locacao);
// Adiciona pontos de clientes
PontosCliente := PontosCliente + 1;
// Gera bonus porlocação de lançamento por dois dias
if (Locacao.Filme.CodigoPreco = TFilme.Lancamento) and (Locacao.DiasAluguel > 1) then
PontosCliente := PontosCliente + 1;
//Mostra dados dessa locação
Resultado := Resultado + '|' + Locacao.Filme.Titulo + '|' +
FloatToStr(TotalLocacao) + #13;
Total := Total + TotalLocacao;
end;
//Adiciona Rodapé
Resultado := Resultado + 'O total é: ' + FloatToStr(Total) + #13;
Resultado := Resultado + 'Você ganhou: ' + FloatToStr(PontosCliente) + ' pontos';
Result := Resultado;
end;
function TCliente.TotalLocacaoItem(Locacao: TLocacao): Double;
var
TotalLocacao: Double;
begin
TotalLocacao := 0;
if Locacao.Filme.CodigoPreco = TFilme.Regular then
begin
TotalLocacao := TotalLocacao + 2;
if (Locacao.DiasAluguel > 2) then
TotalLocacao := TotalLocacao + ((Locacao.DiasAluguel - 2) * 1.5);
end
else if Locacao.Filme.CodigoPreco = TFilme.Lancamento then
begin
TotalLocacao := TotalLocacao + (Locacao.DiasAluguel * 3);
end
else if Locacao.Filme.CodigoPreco = TFilme.Infantil then
begin
TotalLocacao := TotalLocacao + 1.5;
if (Locacao.DiasAluguel > 3) then
TotalLocacao := TotalLocacao + ((Locacao.DiasAluguel - 3) * 1.5);
end;
Result := TotalLocacao;
end;
|
Quando fazemos uma mudança desse nível, temos que compilar e executar o teste. Após executar, todos os teste rodaram sem erros.
Digamos que na criação do método TotalLocacaoItem, tivéssemos colocar o retorno do tipo Integer ao invés de Double. Nesse caso, seria fácil pegar o problema no momento da compilação que estaria acusando erro de tipo de retorno. Esse é a essência do processo de refactoring pois o seria fácil e rápido encontrar o motivo do problema.
Então fica a dica: Refatore o programa em pequenos passos. Se você cometer um erro, será fácil encontrar o bug.
Na ferramenta Delphi XE3 temos o menu Refactor. (Não sei a partir de qual versão do Delphi já possui esse recurso). Se utilizarmos o item Extract Method (Shift+Ctrl+M), ele fará esse trabalho de extrair o método, gerando um novo método com o código selecionado. Conforme as imagens abaixo:
Gerando o código abaixo:
function TCliente.Relatorio: string;
var
Total, TotalLocacao: Double;
PontosCliente: Integer;
Resultado: String;
Contador: Integer;
Locacao: TLocacao;
begin
Total := 0;
PontosCliente := 0;
Resultado := 'Registro de locação de ' + Nome + #13;
// Determina total de cada locação
for Contador := 0 to Locacoes.Count - 1 do
begin
Locacao := Locacoes.Items[Contador];
TotalLocacaoItem(TotalLocacao, Locacao);
// Adiciona pontos de clientes
PontosCliente := PontosCliente + 1;
// Gera bonus porlocação de lançamento por dois dias
if (Locacao.Filme.CodigoPreco = TFilme.Lancamento) and (Locacao.DiasAluguel > 1) then
PontosCliente := PontosCliente + 1;
//Mostra dados dessa locação
Resultado := Resultado + '|' + Locacao.Filme.Titulo + '|' +
FloatToStr(TotalLocacao) + #13;
Total := Total + TotalLocacao;
end;
//Adiciona Rodapé
Resultado := Resultado + 'O total é: ' + FloatToStr(Total) + #13;
Resultado := Resultado + 'Você ganhou: ' + FloatToStr(PontosCliente) + ' pontos';
Result := Resultado;
end;
procedure TCliente.TotalLocacaoItem(var TotalLocacao: Double; Locacao: TLocacao);
begin
TotalLocacao := 0;
if Locacao.Filme.CodigoPreco = TFilme.Regular then
begin
TotalLocacao := TotalLocacao + 2;
if (Locacao.DiasAluguel > 2) then
TotalLocacao := TotalLocacao + ((Locacao.DiasAluguel - 2) * 1.5);
end
else if Locacao.Filme.CodigoPreco = TFilme.Lancamento then
begin
TotalLocacao := TotalLocacao + (Locacao.DiasAluguel * 3);
end
else if Locacao.Filme.CodigoPreco = TFilme.Infantil then
begin
TotalLocacao := TotalLocacao + 1.5;
if (Locacao.DiasAluguel > 3) then
TotalLocacao := TotalLocacao + ((Locacao.DiasAluguel - 3) * 1.5);
end;
end;
Note que nesse caso, o método gerado, TotalLocacaoItem, não possui retorno. A ferramenta seta o parâmetro TotalLocacao como var e retorna nele mesmo o resultado da função. Esse recurso normalmente é usado quando precisamos que o método retorne mais de um valor, em todo o caso, no nosso exemplo o uso desse recurso não afeta em nada o refactoring, afinal o mais importante foi realizado, ou seja, extraiu o método TotalLocacaoItem do método Relatorio.
E os testes continuam rodando sem erros:
Continua no próximo post...