Advertisement
  1. Code
  2. Web Development
Code

Refatorando Código Legado: Parte 1 - O Resultado Esperado

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Refactoring Legacy Code.
Refactoring Legacy Code: Part 2 - Magic Strings & Constants

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Código velho. Código feio. Código complicado. Código macarrônico. Código sem sentido. Em outras palavras, Código Legado. Esta é uma série que ajudará você a trabalhar e lidar com esse tipo de código.

Em um mundo ideal, você só escreveria código novo, do zero. Você escreveria ele lindo e perfeito. Nunca teria de revê-lo e nunca teria de manter projetos com mais de dez anos. Isso em um mundo ideal...

Infelizmente, vivemos em uma realidade que não é a ideal. Temos de entender, modificar e aprimorar código de eras atrás. Temos de trabalhar com código legado. O que está esperando? Comecemos com o primeiro tutorial pegando o código, entendendo um pouco do que ele faz e criaremos uma rede de segurança para modificações futuras.

Definição de Código Legado

Código legado foi definido de tantas formas, que é impossível encontrar definição única e aceita. Os poucos exemplos do começo deste tutorial são só a ponta geleira. Assim, não darei qualquer definição oficial. Ao invés disso, citarei a minha favorita.

Para mim, código legado não passa de código sem testes. ~ Michael Feathers

Pois bem, essa é a primeira definição formal para a expressão código legado, publicada por Michael Feathers em seu livro Trabalhando Eficientemente com Código Legado. Claro, a indústria tem usado essa expressão por anos, basicamente para qualquer código que seja difícil de alterar. Entretanto, essa definição tem algo mais. Ela explica o problema claramente, de modo que a solução se torna óbvia. "Díficil de alterar" é muito vago. O que deveríamos fazer para tornar mais fácil a alteração? Não tenho ideia! "Código sem testes", por outro lado, é bem mais concreto. E a resposta para nossa pergunta é simples, crie código testável e coloque-o sob test. Então, comecemos.

Obtendo Nosso Código Legado

Esta série será baseada no excepcional jogo Trivia Game do J.B. Rainsberger projetado para os eventos Legacy Code Retreat. Ele foi criado para parecer com código legado de verdade e também oferece oportunidades para uma grande variedade de refatoração, com um nível razoável de dificuldade.

Baixe o Código Fonte

O Trivia Game está hospedado no GitHub sob a licença GPLv3. Assim, você pode brincar com ele sem problemas. Começaremos essa série verificando o repositório oficial. O código também está atrelado a este tutorial com todas as modificações que fizermos. Se ficar confuso em algum ponto, dê uma olhada no resultado final.

Ao abrir o diretório trivia, você verá nosso código em várias linguagens de programação. Trabalharemos com o PHP, mas sinta-se livre para escolher sua linguagem favorita e aplicar as técnicas apresentadas aqui.

Entendendo o Código

Por definição, código legado é difícil de entender, especialmente se não sabemos o que ele deve fazer. Assim, o primeiro passo é executar o código e realizar uma análise para saber do que ele trata.

Temos dois arquivos em nosso diretório.

O arquivo GameRunner.php parece um ótimo candidato para tentarmos executar o código.

Certo. Nossa suposição estava correta. Nosso código executou e produziu algum resultado. Analisar esse resultado nos ajudará a deduzir alguma ideia sobre o que o código faz.

  1. Sabemos que é um Jogo de Perguntas e Respostas. Descobrimos ao ver os códigos fonte.
  2. Nosso exemplo tem três jogadores: Chet, Pat e Sue.
  3. Há algum tipo de rolagem de dados ou coisa parecida.
  4. Há posicionamento de jogadores. Será algum tipo de tabuleiro?
  5. Há várias categorias de perguntas a serem feitas.
  6. Usuários respondem questões.
  7. Respostas corretas dão ouro para os jogadores.
  8. Respostas erradas mandam os jogadores para a área de penalidades.
  9. Jogadores podem sair da área de penalidades, baseada em uma lógica não tão óbvia.
  10. Aparentemente, aquele usuário que alcançar seis moedas é o vencedor.

Isso é bastante coisa. Fomos capazes de descobrir a maior parte do comportamento da aplicação apenas olhando o seu retorno. Em aplicações reais, o retorno pode não ser texto em uma tela, mas pode ser uma página na web, um registro de erro, uma base de dados, uma comunicação de rede, um arquivo geração e por aí vai. Em outros casos, o módulo que precisa de mudanças não pode ser executado isoladamente. Se for o caso, você precisará executá-lo a partir dos outros módulos da aplicação maior. Apenas tente adicionar o mínimo para obter uma quantidade suficiente de retorno do seu código legado.

Escaneando o Código

Agora que temos uma ideia sobre o que o código faz, podemos começar a verificá-lo. Começaremos com o executador.

O Executador do Jogo

Gosto de começar passando o código pelo formatador de código da minha IDE. Isso aprimora bastante a leitura tornando o código mais familiar ao que estou acostumado. Assim, isso:

... virará isso:

... que, de certa forma, é melhor. Pode não ter sido uma grande diferença em uma mínima quantidade de código, mas fará muita diferença em nosso próximo arquivo.

Olhando o arquivo GameRunner.php, podemos identificar, facilmente, alguns aspectos chave que observamos do retorno da execução. Podemos ver as linhas que adicionam os usuários (9-11), que um método roll() é executado e um vencedor é selecionado. Claro, eles estão além dos segredos mais profundos da lógica do jogo, mas, pelo menos, começamos a identificar os métodos principais que nos ajudarão a descobrir o resto do código.

O Arquivo Game

Devemos fazer o mesmo processo de formatação no arquivo Game.php.

Esse arquivo é muito maior: Cerca de 200 linhas de código. A maioria dos métodos tem um tamanho bom, mas alguns deles são bem grandes e, após a formatação, veremos que tem dois pontos em que a indentação do código vai além de quatro níveis. Geralmente, altos níveis de indentação indicam várias decisões complexas. Por hora, podemos assumir que esses pontos em nosso código serão mais complexos e mais sensíveis a alterações.

O Resultado Esperado

E em pensar em mudança leva-nos à nossa falta de testes. Os métodos que vimos no arquivo Game.php são bem complexos. Não se preocupe em entendê-los. A essa altura, eles também são um mistério para mim, também. Código legado é um mistério que precisamos resolver e entender. Tomamos nosso primeiro passo para entendê-lo e chegou a hora de tomarmos o segundo.

E o Que É o Resultado Esperado?

Ao trabalhar com código legado, é quase impossível de entendê-lo e escreve algum código que exercitará todos os caminhos lógicos do código. Para esse tipo de teste, precisaremos entender o código, mas não ainda. Assim, tomaremos outra abordagem.

Ao invés de tentar descobrir o que testar, podemos testar tudo, inúmeras vezes, para que obtenhamos uma grande quantidade de retorno, sobre o qual podemos assumir que ele foi produzido exercitando todas as partes do nosso código. É recomendado que execute o código, pelo menos 10.000 (dez mil) vezes. Escreveremos testes para executar pelo menos duas vezes mais e salvar o retorno.

Escrevendo o Gerador do Resultado Esperado

Podemos pensar além e começar criando arquivos separados para o gerador e para o teste. Mas isso é realmente necessário? Ainda não temos certeza disso. Então, que tal começarmos comum arquivo de testes básico que executará nosso código uma vez e construiremos nossa lógica a partir dele.

Você encontrará a pasta Test nos arquivos anexados, dentro da pasta source, fora do diretório trivia. Nesse diretório, criaremos um arquivo chamado: GoldenMasterTest.php.

Poderíamos fazer isso de várias formas. Por exemplo, poderíamos executar nosso código através da linha de comando e redirecionar seu retorno para esse arquivo. Entretanto, colocá-lo em teste que seja facilmente executável em nossa IDE, é uma vantagem que não devemos ignorar.

O código é bem simples, ele carrega o retorno e o guarda na variável $output. A função require_once() também executará todo o código dentro dos arquivos incluídos. Em nossa função var_dump() veremos um retorno já familiar.

Porém, em uma segunda execução, poderemos observar algumas coisas estranhas:

... o retorno está diferente. Mesmo que executemos o mesmo código, o retorno é diferente. Os números obtidos são diferentes, as posições dos jogadores são diferentes....

Alimentando o Gerador Randômico

Ao analisar o código essencial do executador, podemos ver que ele usa uma função rand() para gerar números aleatórios. Nossa próxima parada é na documentação oficial do PHP para pesquisarmos sobre essa função rand().

O gerador de números aleatórios é alimentado automaticamente.

A documentação informa que a alimentação acontece automaticamente. Agora, temos outra tarefa. Precisamos de uma maneira de controlar essa alimentação. A função srand() pode ajudar com isso. Eis a definição dela na documentação.

Alimenta o gerador de números aleatórios com uma semente ou com um valor aleatório caso não passe algum valor para a semente.

Isso nos diz que, se executarmos essa função antes de qualquer chamada a rand(), nós sempre teremos os mesmos resultados.

Colocamos srand(1) antes de nosso require_once(). Agora, o resultado sempre será o mesmo.

Colocando o Retorno em Um Arquivo

Essa alteração parece bem razoável. Certo? Nós extraímos a geração de código para um método, executamos ele duas vezes, e esperamos que o retorno seja igual. Contudo, não serão.

O que acontece é: require_once() não carregará o mesmo arquivo duas vezes. A segunda chamada ao método generateOutput() produzirá uma cadeia de caracteres vazia. E o que podemos fazer? E se usarmos apenas require()? O arquivo será executado cada vez.

Bem, isso leva a outro problema: "Cannot redeclare echoln()". Mas, de onde isso veio? Está bem no começo do arquivo Game.php. O motivo desse erro acontecer é porque no arquivo GameRunner.php colocamos o comando include __DIR__ . '/Game.php';, que tenta incluir o arquivo Game.php duas vezes, uma para cada chamada do método generateOutput().

Usando include_once no arquivo GameRunner.php resolverá nosso problema. Sim, precisamos mudar o arquivo GameRunner.php, mesmo sem ter testes para ele, ainda! Contudo, podemos ter 99% de chances que isso não bagunçara o código em si. É uma mudança simples e pequena o suficiente que não nos amedrontará muito. E o mais importante, faz os testes passar.

Execute-o Inúmeras Vezes

Agora que temos código que pode ser executado diversas vezes, é hora de gerar algum retorno.

Extraímos outro método aqui: generateMany(). Ele possui dois parâmetros: Um para o número de vezes que queremos executar o gerador e outro para o arquivo de destino. Ele colocará o retorno gerado em arquivos. Na primeira execução, ele limpa o arquivo e em todas as outras iterações, acrescenta os dados. Você pode olhar o arquivo e ver o retorno gerado 20 vezes.

Mas, espere! O mesmo jogador ganhará sempre? Isso é possível?

Sim! É possível. É algo certo! Sempre temos a mesma semente para nossa função randômica. Nós jogamos o mesmo jogo várias vezes.

Execute-o de Forma Diferente Cada Vez

Precisamos jogar vários jogos diferentes, senão, é quase certeza que só uma parte de nosso código legado será exercitado repetidamente. O escopo do resultado esperado é exercitar o máximo possível. Precisamos realimentar o gerador randômico cada vez, mas de forma controlada. Uma opção é usar nosso contado como o valor da semente.

Isso ainda mantem nossos testes passando, assim, temos certeza que sempre geramos o mesmo retorno completo todas as vezes, enquanto são jogadas partidas diferentes a cada iteração.

Existem vários vencedores para os variados jogos, de forma aleatória. Parece bom.

Obtendo os 20.000

A primeira coisa que podemos fazer é tentar executar nosso código por 20.000 iterações.

Isso quase funcionará. Dois arquivos de 55MB serão gerados.

Por outro lado, o teste falhará por conta de memória insuficiente. Não importa o tanto de memória RAM você tenha, o teste falhará. Eu tenho 8Gb além de 4GB e memória swap e ele falha. As duas cadeias de caracteres são muito grandes para serem comparadas em nossas asserções.

Em outras palavras, geramos arquivos certos, mas o PHPUnit não pode compará-los. Precisamos de uma alternativa.

Isso parece ser uma boa alternativa, mas, ainda assim, falha... Precisamos pesquisar um pouco mais para essa situação.

Isso funciona!

Isso pode comparar as duas cadeias de caracteres e falhar caso sejam diferentes. Porém, tem seu pequeno preço: não será possível precisar o que está diferente entre as duas cadeias de caracteres. Simplesmente dirá "Failed asserting that false is true." (Falha em confirmar que false é true). Trataremos disso em um tutorial futuro.

Considerações Finais

Acabamos por hoje. Aprendemos muitas coisas para nossa primeira seção e começamos bem para um trabalho futuro. Vimos códigos, analizamos de diversas formas e entendemos a maior parte da lógica essencial. Então, criamos um conjunto de testes para garantir que ele seja executador ao máximo. Sim. Os testes são bem lentos. Leva 24 segundos em um computador Core i7 para gerar o retorno duas vezes. Felizmente, em nosso desenvolvimento posterior, manteremos o arquivo gm.txt intocado e geraremos apenas um outro por execução. Mas 12 segundos ainda é muito tempo para uma base de código tão pequena.

Ao terminarmos essa série, nossos testes serão executados em menos de um segundo e testará todo o código de forma apropriada. Assim, fique ligado em nosso próximo tutorial, que nós traremos problemas como constantes mágicas, cadeias de caracteres mágicos e condicionais complexos. Obrigado pela leitura.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.