Código Ruim

Category : Refatoração

Um código bom é como uma prosa bem redigida, exprime com clareza sua intenção e exerce boa influência nas alterações futuras. Passamos a maior parte do tempo de programação lendo e aprendendo códigos dos outros e quanto mais agradável esta experiência, melhores são nossos resultados.

Infelizmente a realidade é um pouco diferente…

Na Semana passada um novato da equipe de desenvolvimento Delphi pediu para que eu verificasse um trecho de código no módulo de folha de pagamentos, pois achou engraçado a lógica condicional que fazia a mesma coisa independendente do resultado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
procedure TForm1.ProcessarLog();
begin
if cdsEventos.FieldByName('PROCESSADO').AsString = 'P' then
    LogVer.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
               '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
               '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
               '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
               '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
else
    if cdsEventos.FieldByName('PROCESSADO').AsString = 'A' then
        LogAlte.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', )+'|')
    else
        begin
        LogCanc.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
        end;
end;

Ao passar o olho rapidamente enxerguei a mesma coisa que ele, as condicionais não serviam para nada. Prestando um pouco mais de atenção percebemos que de acordo com a o valor do campo ‘PROCESSADO’ a informação seria escrita num arquivo de log diferente (LogVer, LogAlte ou LogCanc).

A duplicação do código de escrita no log é tão gritante que oculta tudo mais que está em volta, escondendo sua intenção. Para piorar este trecho de código estava num procedimento que não tinha nada a ver com log de informações e fazia mais uma dúzia de coisas diferentes.

Refatorando

Apenas eliminando a duplicação a legibilidade melhorou drasticamente, e pudemos enxergar a intenção do código sem distrações. Com três refatorações simples melhoramos muito aquele trecho de código:

  • Movemos o código duplicado para uma variável que descreve exatamente o que aquele emaranhado de cocatenação de strings significa.
  • Renomeamos as variáveis que representam nossos objetos de log para algo mais descritivo.
  • Substituímos as strings mágicas ‘P’ e ‘A’ por constantes que expressam o significado oculto nestas strings.

Eis o resultado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TForm1.ProcessarLog();
begin
NovaEntrada := '|' + Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString) +
               '|' + Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString) +
               '|' + Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString) +
               '|' + Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat)) +
               '|' + Alinhar(DIREITA, 18, ' ', '') + '|');

if cdsEventos.FieldByName('Processado').AsString = PROCESSADO then
    LogProcessados.Add(NovaEntrada)
else if cdsEventos.FieldByName('Processado').AsString = ALTERADO then
    LogAlterados.Add(NovaEntrada)
else
    LogCancelados.Add(NovaEntrada);
end;

A identação do código também foi corrigida.

Com isso ganhamos clareza de intenção e o próximo novato que passar por este código irá entender o que está acontecendo ali sem distrações. Refatorações posteriores melhorarão ainda sua estrutura, pois ainda temos aquela dúzia de coisas misturadas no procedimento.

Mais melhorias

O código de formatação da entrada de log deve ser extraído para sua própria função. Com isso temos menos um desvio mental durante a leitura, já que dificilmente estariamos interessados em saber como a linha do log está sendo formatada aqui.

Se um dia a formatação fosse alterada teríamos que descobrir onde este código está e encontrá-lo em ‘ProcRegistrosFuncionarios()’, com proximadamente 200 linhas não seria nada intuitivo.

Extraímos o código de formatação para sua própria função:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
procedure TForm1.ProcessarLog();
begin
if cdsEventos.FieldByName('Processado').AsString = PROCESSADO then
    LogProcessados.Add(NovaEntradaLogFormatada())
else if cdsEventos.FieldByName('Processado').AsString = ALTERADO then
    LogAlterados.Add(NovaEntradaLogFormatada())
else
    LogCancelados.Add(NovaEntradaLogFormatada());
end;

function TForm1.NovaEntradaLogFormatada(): String
begin
    Result := '|' + Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString) +
              '|' + Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString) +
              '|' + Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString) +
              '|' + Alinhar(DIREITA, 10, '0', FormatFloat('#0.00', cdsEventos.FieldByName('QUANTIDADE').AsFloat)) +
              '|' + Alinhar(DIREITA, 18, ' ', '') + '|');
end;

Plantando uma semente

O código existente influencia a maneira como os novos códigos são escritos. É como plantar uma semente, bom código irá gerar códigos cada vez melhores. Código ruim é como uma erva daninha que irá se multiplicar e acabar com seu sistema.

A refatoração nos deu mais clareza e disponibilizou uma função única para formatação da entrada no log. Imagina se fosse necessário incluir a opção para escrever o log em XML. Com a primeira versão do código provavelmente um programador inexperiente iria aumentar as duplicações e piorar mais o código que já estava ruim, criando uma aberração como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
procedure TForm1.ProcessarLog();
begin
if FormatoXML then
begin
    if CDSGrid.FieldByName('PROCESSADO').AsString = 'P' then
        LogVer.Add('<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />')
else if CDSGrid.FieldByName('PROCESSADO').AsString = 'A' then
    LogAlte.Add('<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />')
else
    LogCanc.Add('<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />')
end
else
begin
if cdsEventos.FieldByName('PROCESSADO').AsString = 'P' then
    LogVer.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
               '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
               '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
               '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
               '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
else
    if cdsEventos.FieldByName('PROCESSADO').AsString = 'A' then
        LogAlte.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', )+'|')
    else
        begin
        LogCanc.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
        end;
end;
end;

Enquanto que com nossa nova versão refatorada, com a metade de linhas o resultado da alteração seria:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
procedure TForm1.ProcessarLog();
begin
if cdsEventos.FieldByName('Processado').AsString = PROCESSADO then
    LogProcessados.Add(NovaEntradaLogFormatada())
else if cdsEventos.FieldByName('Processado').AsString = ALTERADO then
    LogAlterados.Add(NovaEntradaLogFormatada())
else
    LogCancelados.Add(NovaEntradaLogFormatada());
end;

function TForm1.NovaEntradaLogFormatada(): String
begin
if FormatoXML then
    Result := '<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />'
else
    Result := '|' + Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString) +
              '|' + Alinhar(DIREITA, 6, '0', cdsEventos.FieldByName('EVENTO').AsString) +
              '|' + Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString) +
              '|' + Alinhar(DIREITA, 10, '0', FormatFloat('#0.00', cdsEventos.FieldByName('QUANTIDADE').AsFloat)) +
              '|' + Alinhar(DIREITA, 18, ' ', '') + '|');
end;

Ainda não está perfeito, mas podemos ir refatorando cada vez mais, como extrair as responsabilidades de log para outra classe, e de lá aplicar padrões de projeto como o Strategy, aumentando ainda mais a reusabilidade do código. Eu parei por aqui porque precisamos entregar logo a correção efetuada no projeto.

Conclusão

Código legado largado é difícil de entender e alterar. Sua manutenção se torna penosa para o programador e consequentemente encarece o software, já que fatores como tempo e qualidade entram em jogo.

Escreva seu código para que seus leitores o entenda sem precisar chamar o colega ao lado e influencie-os a dar bons passos nas alterações futuras. É sua responsabilidade como programador.

Como disse Martin Fowler em seu livro Refactoring: Improving the Design of Existing Code:

“Qualquer tolo consegue escrever código que um computador entenda. Somente bons programadores escrevem código que humanos possam entender.”

Aprenda a expressar a intenção do seu código, não seja um tolo.

Livros recomendados

Clean Code: A Handbook of Agile Software Craftsmanship [Robert C. Martin]
Refactoring: Improving the Design of Existing Code [Martin Fowler]

Comments (2)

No meu Blog: Código Ruim http://goo.gl/fb/34Dr #refatoração #boaspraticas

No meu Blog: Código Ruim http://goo.gl/fb/34Dr #boaspraticas #refatoracao #refactoring

Post a comment