16 de jan de 2014

Introdução ao Refactoring - Criando um cenário

Como mencionei anteriormente, nossa intenção e demonstrar o refactoring através de exemplos. Vamos procurar demonstrar como o refactoring funciona e demonstrar como ocorre o processo.

Vamos pegar um exemplo pequeno, pois se utilizarmos um exemplo muito extenso, será necessário posts e posts para concluir. Realmente não faz sentido aplicar refactoring em programas muito pequenos, mas vamos interpretar que o exemplo a ser demonstrado é apenas uma pequena parte de um grande programa.

O programa é muito simples. É um programa de videolocadora que calcula e imprime um relatório de um aluguel de video realizado por um cliente. O programa diz qual filme o cliente alugou e por quanto tempo. Ele então calcula o preço, que dependerá o tempo de aluguel e o tipo de filme.
Há três tipos de filmes: regulares, infantil e lançamentos. Além de calcular o preço, o relatório exibe os pontos do cliente, que varia com o tipo do filme alugado.

Algumas classes representam vários elementos da videolocadora, segue algumas:



Em seguida o código de cada classe.

Filme
Filme é um simples classe de dados

unit uFilme;

interface

type
  Filme = class
  private
    FTitulo: string;
    FCodigoPreco: integer;
    procedure SetTitulo(const Value: string);
    procedure SetCodigoPreco(const Value: integer);

  public
    const Infantil = 2;
    const Regular = 0;
    const Lancamento = 1;
    constructor Create(titulo: string; precoCodigo: integer);
    property Titulo:string read FTitulo write SetTitulo;
    property CodigoPreco: integer read FCodigoPreco write SetCodigoPreco;

  end;


implementation

{ Filme }

constructor Filme.Create(titulo: string; precoCodigo: integer);
begin

end;

procedure Filme.SetCodigoPreco(const Value: integer);
begin
  FCodigoPreco := Value;
end;

procedure Filme.SetTitulo(const Value: string);
begin
  FTitulo := Value;
end;

end.

Aluguel
Aluguel representa o aluguel de filmes pelo cliente

unit uAluguel;

interface

uses uFilme;

type
  TAluguel = class
  private
    FFilme: TFilme;
    FDiasAluguel: integer;
    procedure SetFilme(const Value: TFilme);
    procedure SetDiasAluguel(const Value: integer);
  public
    constructor Create(pFilme: TFilme; pDiasAluguel: Integer);
    property Filme: TFilme read FFilme write SetFilme;
    property DiasAluguel: integer read FDiasAluguel write SetDiasAluguel;
  end;

implementation

{ TAluguel }

constructor TAluguel.Create(pFilme: TFilme; pDiasAluguel: Integer);
begin
  Filme := pFilme;
  DiasAluguel := pDiasAluguel;
end;

procedure TAluguel.SetDiasAluguel(const Value: integer);
begin
  FDiasAluguel := Value;
end;

procedure TAluguel.SetFilme(const Value: TFilme);
begin
  FFilme := Value;
end;

end.


Cliente
A classe cliente representa o cliente da loja. Assim como as outras classes ela possui dados e métodos assessores.

unit uCliente;

interface

uses uAluguel, System.Classes;

type

TLocacoes = TList;


type

  TCliente = class
  private
    FNome: string;
    FLocacoes: TLocacoes;
    procedure SetNome(const Value: string);

  public
    constructor Create(pNome:string);
    procedure AdicionaLocacao(Valor: TLocacao);
    property Nome:string read FNome write SetNome;
    property Locacoes: TLocacoes read FLocacoes;
  end;

implementation

{ TCliente }

procedure TCliente.AdicionaLocacao(Valor: TLocacao);
begin
  FLocacoes.Add(TLocacao.Create(Valor.Filme,Valor.DiasAluguel));
end;

constructor TCliente.Create(pNome: string);
begin
  Nome := pNome;
end;


procedure TCliente.SetNome(const Value: string);
begin
  FNome := Value;
end;

end.


A classe cliente possui um método que produz o relatório. A figura abaixo mostra as interações desse método. O código do método vem logo depois do diagrama.





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 +  '\t' + Locacao.Filme.Titulo + '\t' +
    FloatToStr(TotalLocacao) + '\n';
    Total := Total + TotalLocacao;
  end;
  //Adiciona Rodapé
  Resultado := Resultado +  'O total é: ' + FloatToStr(Total) + '\n';
  Resultado := Resultado +  'Você ganhou: ' + FloatToStr(PontosCliente) + ' pontos';
  Result := Resultado;
end;


Comentários sobre o código do programa

Qual sua opinião sobre o código programa? Você pode dizer que ele não está bem implementado, e que certamente não está orientado a objetos. Para um simples programa como esse, isso realmente não importa. Não existe nada de errado com um programa feito rapidamente e bagunçado. Mas se esse código representa um fragmento de um sistema mais complexo, aí sim, temos sérios problema com esse programa.
A rotina relatório na classe cliente está muito extensa e faz muitas coisas. Muitas dessas coisas deveriam estar sob responsabilidade de outras classes.

Mesmo que o programa funcione bem. Isso não é apenas um julgamento de quem não gosta de códigos feios e mal feitos. Para o compilador não importa onde o código está mal feito e onde está bem feito. Mas se precisar realizar uma alteração no sistema, haverá um humano envolvido, e para humanos isso importa.
Um código mal organizado e complicado para se alterar. É complicado por que ele precisa saber onde as mudanças são necessárias. E se é complicado identificar onde precisa ser alterado, existe então uma chance muito grande do desenvolvedor cometar um equívoco e acabar gerando um bug no sistema.

Vamos supor que nesse caso o usuário deseje algumas mudanças no sistema. A primeira mudança que ele deseja é que o relatório seja emitido no formato html. Considere o impacto dessa mudança.
Como você pode observar no código é impossível reaproveitar qualquer comportamento do atual método relatorio para um relatório hmtl. A unica opção que você possui é copiar o método atual, e criar um novo método. Nada trabalhoso, no entanto, o comportamento ficará duplicado.

E se mudar as regras de cálculo? Você terá que ajustar o método Relatorio e o método RelatorioHTML, para que a mudança atenda os dois relatórios.
O problema do copiar colar, vem quando você precisa fazer alguma mudança. Se você está escrevendo um programa pequeno, e que não sofrerá mudanças, o copiar colar atende muito bem. Mas se o programa tem um tempo de vida longo, o copiar colar pode se tornar uma grave ameaça.

Isso leva a uma segunda solicitação de mudança. Os usuários quem mudar a forma de classificar os filmes, mas eles não se decidiram ainda de como será essa nova classificação. Eles tem várias solicitações de mudanças em mente. Essas mudanças afetam diretamente os preços das locações de filmes e o cálculo de pontuação de clientes. E você na sua experiência de desenvolvedor, sabe que os usuários vão continuar solicitando mudanças.

Você pode tentar fazer poucas modificações, que sejam possíveis, no código e depois de tudo funcionar conforme esperado. Relembrando a máxima da velha engenharia: "Se ainda não deu problema, não conserte." O programa pode realmente não dar problema, mas isso dói. Isso faz a sua vida mas difícil, pois você encontra dificuldades em fazer as mudanças que o usuário solicita. É nessa hora que entra o refactoring!

Dica: Quando você tem que adicionar um novo recurso ao programa, e o código do programa não está estruturado de forma conveniente para adicionar o novo recurso, primeiro refatore o programa de modo que a adição do recurso fique mais fácil, e só então adicione o novo recurso.





Nenhum comentário:

Postar um comentário