Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Programming Fundamentals
Code

Refatorando Código Legado: Parte 11: O Fim?

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Refactoring Legacy Code.
Refactoring Legacy Code - Part 10: Dissecting Long Methods with Extractions

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

No antigo anterior, aprendemos uma nova maneira de compreender e criar novo código, a partir da extração até a exaustão. Embora aquele tutorial tenha sido bom para aprender sobre as técnicas, é difícil aceitá-lo como um exemplo ideal para entender os benefícios. Nesse artigo, extrairemos até a exaustão no código do nosso jogo de perguntas e respostas, e analisaremos o resultado final.

Também concluiremos nossa série sobre refatoração. Se você acha que deixei passar algo, sinta-se livre para comentar com a proposta de algum tópico. Se aparecerem algum boa ideia, continuarei com tutoriais extras, baseado em seus pedidos.

Atacando o Método Mais Longo

Que melhor maneira para começar nosso artigo que atacando nosso método mais longo e extrair as menores peças possíveis. Criando testes iniciais, como de costume, deixará o procedimento mais eficiente e divertido.

Como de costume, você tem o código no ponto que começamos esse tutorial, presente no diretório php_start, enquanto o resultado final estará no diretório php.

A nossa primeira vítima é o método wasCorrectlyAnswered().

Testando o Método wasCorrectlyAnswered()

Como aprendemos em artigos anteriores, o primeiro passo para que possamos modificar nosso código legado é criar testes para ele. Esse processo pode ser bem difícil. Felizmente, o método wasCorrectlyAnswered() é bem direto. É composto de vários condicionais if-else. Cada ramo do código retorna um valor. Quando temos um valor de retorno, sempre é possível achar que o teste é realizável. Não necessariamente fácil, mas possível.

Não há qualquer regra sobre o que testar primeiro. Simplesmente escolhemos o caminho de execução. Na verdade, tivemos uma grande surpresa e reusamos um dos métodos privados que extraímos há alguns tutoriais atrás. Mas, ainda não terminamos. Os testes estão passando, então é hora de refatoração.

Isso é muito mais fácil de ler e, significantemente, mais descritivo. Você pode ver os métodos extraídos no código em anexo ao tutorial.

Esperamos que esse teste passe, mas ele falhou. Os motivos para isso não estão claros. Uma olhada mais a fundo em didPlayerNotWin() pode ajudar.

O método retorna true quando o jogador não ganhou. Talvez seja melhor renomear nossa variável, mas, primeiro, os testes devem passar.

Ao analisar mais de perto, podemos ver que misturamos alguns valores aqui. Nossa confusão entre o nome do método e o nome da variável, fez com que revertêssemos as condições.

Isso funciona. Ao analisar didPlayerNotWin(), também observamos que ela usar o operador de igualdade para determinar o vencedor. Devemos configurar nosso valor para um a menos, porque o valor é incrementado no código de produção que testamos.

As últimas três partes são bem simples de escrever. Apenas são variações das duas primeiras. Você pode encontrá-las no código em anexo.

Extrair até a Exaustão o Método wasCorrectlyAnswered()

O maior problema é o confuso nome de variável, $winner. Ele deveria ser $notAWinner, para simbolizar que não é o vencedor.

Podemos perceber que a variável $notAWinner apenas é usada para retornar algum valor. Poderíamos invocar o método didPlayerNotWin(), diretamente, na declaração return?

Isso ainda mantém nossos testes passando, mas, se executarmos os testes do resultado esperado, eles falharão com um erro do tipo "not enough memory" (memória insuficiente). Na verdade, a mudança faz com que o jogo nunca termine.

O que está acontecendo é que o jogador atual é atualizado para o próximo jogador. Se tivéssemos apenas um jogador, sempre usaríamos o mesmo. É assim que funcionam os testes. Você nunca saberá quando encontrará alguma lógica escondida em um código difícil.

Apenas adicionando outro jogador em cada um dos nossos testes relacionados a esse método, temos certeza que a lógica será coberta. Esse teste fará com que a declaração return modificada logo acima, falhe.

Facilmente, percebemos a seleção do próximo jogador é idêntica em ambas as partes da condição. Podemos movê-la para um método próprio. O nome que escolhemos para esse método é selectNextPlayer(). Esse nome ajuda a destacar o fato do valor do jogador atual ser alterado. Ele também sugere que o método didPlayerNotWin() pode ser renomado em algo que reflita a relação com o jogador atual.

Nosso código está ficando menor e mais expressivo. O que podemos fazer mais? Poderíamos alterar o nome da esquisita lógica do "não vencedor" e alterar o método para uma lógica positiva ao invés de negativa. Ou poderíamos continuar extraindo e lidar com a confusão da lógica negativa mais tarde. Não acredito que tenha um método específico a seguir. Então, deixarei o problema da lógica confusa como um exercício para você e continuarei com a extração.

Como regra geral, tente ter uma única linha de código em cada caminho da lógica de decisão.

Extraímos um bloco inteiro de código de cada parte da nossa declaração if. Esse é um passo importante e algo que você sempre deve pensar. Quando tiver um caminho de decisão ou um laço em seu código, dentro dele deveria ter apenas uma única declaração. A pessoa que ler o método não se importará com os detalhes da implementação. Ele ou ela só se preocupará com a lógica da decisão, a declaração if.

E, se pudermos nos livrar de qualquer código extra, devemos fazê-lo. Removemos o else, mantivemos a mesma lógica e realizamos uma pequena economia. Gosto mais dessa solução porque ela destaca qual o comportamento "padrão" da função. O que que está no primeiro nível interior da função (a última linha de código da função). A declaração if é uma funcionalidade excepcional além da funcionalidade padrão.

Já vi motivos que escrever dessa forma pode esconder o fato que a funcionalidade padrão não executa se a declaração if for ativada. Só posso concordar com isso, então, se preferir manter a parte else para clareza, por favor, o faça.

Podemos continuar a extração dentro do nosso método privado recém criado. Aplicando o mesmo princípio ao nosso próximo condicional, nos levará ao código a seguir.

Ao olhar os métodos privados getCorrectlyAnsweredForPlayersNotInPenaltyBox() e getCorrectlyAnsweredForPlayerGettingOutOfPenaltyBox(), imediatamente, observamos que uma simples atribuição está duplicada. Essa atribuição pode ser óbvia para alguém como nós, que sabemos como as coisas funcionam, mas não para alguém recém chegado. Extrair essa única linha em um método é uma ótima ideia giveCurrentUserACoin().

Isso também ajuda com a parte da duplicação. Se, no futuro, modificamos a forma de darmos moedas aos jogadores, apenas precisaremos alterar dentro desse método privado.

Então os dois métodos de respostas corretas são idênticos, exceto que um deles retornar com um pequeno erro de digitação. Extraímos o código duplicado e mantemos as diferenças em cada um dos dois. Você talvez ache que deveríamos usar a extração de método e usar um parâmetro no código invocado, retornando uma vez de modo normal e outra vez com o erro. Contudo, a solução proposta abaixo tem uma vantagem: Ela mantem os dois conceitos, de não estar na caixa de penalidades e de sair da caixa de penalidades, separados.

Isso encerra o trabalho no método wasCorrectlyAnswered().

E o Método wrongAnswer()?

Com 11 linhas, esse método não é enorme, mas é grande. Você se lembra da pesquisa que diz que o número mágico é sete mais ou menos dois? Ela diz que nosso cérebro pode pensar, simultaneamente, sobre 7+-2 coisas. Isso é, temos uma capacidade limitada. Então, para entendermos um método mais facilmente e completamente, queremos que a lógica caiba nesse espaço. Com um total de 11 linhas, e um conteúdo de 9 linhas, o método está no limite. Talvez argumente que há uma linha vazia e outro só com as chaves. Isso o faria ter apenas 7 linhas de lógica.

Embora as chaves e espaços sejam curtos em espaço, eles tem significados para nós. Eles separam as partes da lógica, gerando significado, de modo que nosso cérebro possa processá-los. Sim, é mais fácil que comparado com uma única linha de lógica.

É por isso que nosso número de linhas alvo para o método é quatro linhas. Isso está abaixo do mínimo da teoria apresentada, logo, tanto um gênio quanto um programador mediano serão capazes de compreender o método.

Já temos um método para esse trecho de código, então, devemos usá-lo.

Melhor, devemos parar ou continuar?

Poderíamos colocar essas variáveis em linha. A propriedade $this->currentPlayer, obviamente, retorna o jogador atual, então, não há necessidade de repetir lógica. Não aprendemos qualquer coisa ou abstraímos algo usando uma variável local.

Chegamos a 5 linhas. Alguma coisa a mais?

Podemos extrair a linha acima para seu próprio método. Isso ajudará a explicar o que está acontecendo e isolará a lógica com relação a enviar o jogador atual para a caixa de penalidades.

Ainda 5 linhas, mas todas com chamadas de métodos. As duas primeiras estão apresentando coisas. As duas próximas estão relacionadas à nossa lógica. A última linha apenas retorna verdadeiro. Não consigo pensar em qualquer maneira para tornar esse método mais fácil, sem introduzir complexidades através da extrações que fizéssemos. Por exemplo, poderíamos extrair os dois métodos de apresentação para um único método privado. Se fizéssemos isso, onde esse método deveria ficar? Na classe Game ou na classe Display? Acredito que já seja uma questão muito complexa em relação à simplicidade do nosso método.

Pontos Finais e Algumas Estatísticas

Vejamos algumas estatísticas, usando uma ótima ferramenta criada pelo criador do PHPUnit https://github.com/sebastianbergmann/phploc.git

Estatísticas em Relação ao Código Original

Estatísticas em Relação ao Código Refatorado

Análises

Dados brutos só são bons se pudermos analisá-los.

O número de linhas de lógica de código aumentou bastante, de 99 para 151. Mas esse número não deve enganar você, fazendo pensar que o código ficou mais complicado. Essa é uma tendência natural se código bem refatorado, por conta do número de métodos e das respectivas invocações.

Assim que vemos o tamanho médio das classes, percebemos uma queda drástica no número de linhas, de 88 para 36.

E isso é tão surpreendente quanto o tamanho dos métodos, que saiu de uma média de sete linhas para apenas duas linhas.

Embora o número de linhas seja um bom indicador de volume de código por unidade de medida, o ganho real está na análise da complexidade ciclomática. Toda vez que tomamos uma decisão em nosso código, aumentamos a complexidade ciclomática. Quando encadeamos um if dentro de outro, a complexidade ciclomática aumenta exponencialmente. Nossas extrações contínuas nos levaram a uma única decisão nos métodos, reduzindo assim, a complexidade média de 3.18 para 1.00. Você pode ler isso como "nossos métodos refatorados são 3.18 vezes mais simples que o código original". A nível de classe, a queda na complexidade é ainda mais interessante. Caiu de 25.00 para 6.50.

O Fim?

Bom. É isso. O fim da série. Sinta-se a vontade de expressar sus opiniões e se achar que deixamos de falar de algum tópico de refatoração, deixa um comentário com um pedido sobre o mesmo. Se for interessante, transformarei em partes extras desta série.

Obrigado pela sua atenção.

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.