Cuidado com a duplicidade de código

October 24, 2010

Duplicar código tem o potencial de trazer uma série de problemas, acho que todos concordamos com isso.

O problema é que em alguns casos essa duplicidade se manifesta de uma forma bem sutil, em que alguns programadores podem simplesmente nem perceber, ou achar que por ser algo bem simples não trará problema nenhum, ou então que simplesmente não tem como resolver.

Um exemplo simples, em Delphi:

procedure TForm1.FazAlgo1;
begin
  A;
  BeginTransaction;
  try
    B;
    Commit;
  except
    Rollback;
    raise;
  end;
  C;
end;

procedure TForm1.FazAlgo2;
begin
  D;
  BeginTransaction;
  try
    E;
    Commit;
  except
    Rollback;
    raise;
  end;
  F;
end;

Considere A, B, C, D, E e F como sendo blocos de código do seu software, todos diferentes uns dos outros.

Boa parte do código destes dois métodos não tem qualquer semelhança, mas ainda assim podemos ver a duplicidade: Em ambos os casos temos a abertura de uma transação, um processamento e finalmente a confirmação da transação. Caso ocorra algum erro no processamento, desfazemos a transação.

Como resolver isso?

Existem diversas formas. Em Delphi, utilizando os métodos anonimos introduzidos no Delphi 2009, poderíamos ter algo assim:

procedure TForm1.FazAlgo1;
begin
  A;
  ExecutarEmTransacao(procedure
  begin
    B;
  end);
  C;
end;

procedure ExecutarEmTransacao(Proc: TProc);
begin
  BeginTransaction;
  try
    Proc;
    Commit;
  except
    Rollback;
    raise;
  end;
end;

Observe que o método FazAlgo1 ficou quatro linhas menor, mas isso pouco importa, o que estamos discutindo aqui não é sobre economizar linhas de código, mas sim sobre não repetir rotinas. Em outras palavras: No exemplo que acabei de dar, o meu objetivo foi evitar a repetição da rotina “Abre transação, executa o processamento, confirma a transação. Se houver um erro, desfaz a transação e solta o erro”.

Se eu não me preocupo em reduzir linhas de código, por que estou tão preocupado em repetir aquela rotina então? Vamos lá:

  • Pense: Toda vez que você precisar repetir aquele código de controle transacional, qual a possibilidade de você:
    1. Trocar o try-except por um try-finally
    2. Esquecer de abrir a transação
    3. Esquecer de confirmar a transação
    4. Esquecer de desfazer a transação
    5. Engolir a exceção após desfazer a transação (Isto é, esquecer de chamar o “raise” para continuar levantando a exceção para quem mais quiser pegar)
  • Poluição do código: Quem estiver olhando aqueles métodos FazAlgo1 e FazAlgo2 quer saber o que eles fazem. A pessoa vai querer saber que uma determinada parte do mesmo é executada no contexto de uma transação. Mas a pessoa não quer e não precisa saber em detalhes como esse controle transacional é implementado, pois isso só vai poluir e dificultar o seu entendimento do código
  • Manutenção: Se amanhã você decidir habilitar um log de abertura/fechamento de transações, você teria que sair alterando dezenas ou centenas de lugares que utilizam transações

Por estes motivos, eu digo que preferiria a segunda abordagem que mostrei independentemente de qualquer redução do número de linhas de código.

A título de informação: Utilizando AOP, isso poderia ser resolvido de forma ainda mais elegante. Em Java poderíamos contar com o AspectJ por exemplo.

Ainda em Delphi, uma outra forma sutil de repetição de código é quando precisamos iterar sobre todos os registros de um dataset. Normalmente é algo assim:

procedure FazAlgoA;
begin
  DataSet1.First;
  while not DataSet1.Eof do
  begin
    { Algum processamento aqui... }
    DataSet1.Next;
  end;
end;

Ou pior ainda, se este DataSet estiver ligado a algum componente visual e você quer iterar sem que o usuário perceba a movimentação e depois você precisa posicionar o dataset no registro onde ele estava inicialmente:

procedure FazAlgoA;
var
  BM: String;
begin
  DataSet1.DisableControls;
  try
    BM := DataSet1.Bookmark;
    DataSet1.First;
    try
      while not DataSet1.Eof do
      begin
        { Algum processamento aqui... }
        DataSet1.Next;
      end;
    finally
      DataSet1.Bookmark := BM;
    end;
  finally
    DataSet1.EnableControls;
  end;
end;

Agora pense em ter este mesmo comportamento, com isso:

procedure TForm1.FazAlgoA;
begin
  IterarEmDataSet(DataSet1, procedure
  begin
    { Algum processamento aqui... }
  end);
end;

“Ah, mas eu uso uma linguagem que não tem essas facilidades”. Paciência, irmão… Use a sua criatividade e resolva seus problemas com as ferramentas que você tem em mãos! Na nossa profissão, dependemos da criatividade tanto quanto um músico ou um pintor, então comece a utiliza-la! Uma boa forma de obter inspiração é estudando outras linguagens e também o código fonte escrito por outros programadores.

Por exemplo, eu já implementei essa iteração em Delphi utilizando recursos que estavam disponíveis desde uma das primeiras versões do mesmo. Embora não tenha ficado tão elegante quanto a solução com métodos anônimos, resolveu grande parte da repetição de código.

O que quero dizer aqui é que você não deve abandonar boas práticas de programação apenas por que o seu ambiente de desenvolvimento é limitado. É claro que há certas barreiras impostas pela linguagem que não podem ser quebradas, mas isso não é motivo para desistir sem ao menos se esforçar para solucionar o problema.

Leave a Reply

Spam protection by WP Captcha-Free