Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development
Code

Refatorando Código Legado: Parte 9 - Analisando Interesses

by
Difficulty:IntermediateLength:MediumLanguages:
This post is part of a series called Refactoring Legacy Code.
Refactoring Legacy Code: Part 8 - Inverting Dependencies for a Clean Architecture
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)

Neste tutorial, continuaremos a focar na lógica de negócios. Avaliaremos se o conteúdo do arquivo RunnerFunctions.php pertence a uma classe e, em caso positivo, a qual classe. Pensaremos sobre os interesses e sobre onde os métodos pertencem. Por fim, aprenderemos um pouco mais sobre o conceito de simulação. Então, o que estamos esperando? Vamos lá!

RunnerFunctions - Dos Procedimentos à Orientação a Objetos

Mesmo que tenhamos a maior parte de nosso código orientado a objetos, bem organizado em classes, simplesmente, algumas funções vagam em um arquivo. Precisamos parar um pouco para dar um aspecto mais orientado a objetos às funções do arquivo RunnerFunctions.php.

Meu primeiro instinto é de, simplesmente, envolvê-las em uma classe. Isso não é nada genioso, mas é algo que nos faz começar a alterar as coisas. Vejamos se a ideia funciona.

Se fizermos isso, precisaremos modificar os testes e o arquivo GameRunner.php para que usem a nova classe. Nomeamos a classe com algo genérico, por hora, já que renomeá-la será fácil quando for preciso. Nem sabemos ainda se essa classe existirá por conta própria ou se será assimilada à classe Game. Então, não se preocupe com o nome dela ainda.

Em nosso arquivo GoldenMasterTest.php, precisamos modificar a forma que executamos o código. A terceira linha da função generateOutput() precisa ser alterada para que seja criado um novo objeto e invocada a função run() nele. Mas isso dá em erro.

Precisamos modificar nossa classe, mais ainda.

Só precisamos alterar a condição da declaração while no método run(). O novo código invoca didSomebodyWin() e isCurrentAnswerCorrect() a partir da classe atual, colocando $this-> antes desses métodos.

Isso faz com que o resultado esperado passe, mas faz com que o executador quebre os testes.

O problema está na função assertAnswersAreCorrectFor(), mas é facilmente ajustável, criando um objeto executador antes.

Esse mesmo problema também precisa ser ajustado em outras três funções:

Embora isso faça os testes passarem, traz duplicação de código. Como estamos com todos os testes passando, podemos extrair uma função executador para o método setUp().

Muito bom. Todas essas novas criações e refatorações me fizeram pensar. Nomeamos nossa variável de runner. Talvez nossa classe devesse ser nomeada da mesma forma. É hora de refatoração. Deve ser muito fácil.

Se você não marcou "Search for text occurrences" na caixa acima, não esqueça de alterar suas inclusões manualmente, uma vez que a refatoração também renomeará o arquivo.

Agora, temos um arquivo chamado GameRunner.php, outro de Runner.php e um terceiro de Game.php. Não sei você, mas isso está bem confuso para mim. Se eu visse esses três arquivos pela primeira vez na minha vida, não saberia o que cada uma faz. Precisamos eliminar um deles, no mínimo.

O motivo de criarmos o arquivo RunnerFunctions.php lá no começo da refatoração, foi para podermos incluir todos os métodos e arquivos para testes. Precisávamos acessar tudo, mas não executar a não ser em um ambiente preparado em nosso resultado esperado. Ainda podemos fazer a mesma coisa, só que sem executar nosso código a partir do GameRunner.php. Precisamos atualizar as inclusões e criar uma classe, antes de continuarmos.

Isso servirá. Precisamos incluir Display.php explicitamente, para quando Runner tentar criar um novo objeto CLIDisplay, ele saiba o que implementar.

Analisando os Interesses

Acredito que uma das características mais importantes da programação orientada a objetos é definir os interesses. Sempre me pergunta algo como "essa classe realiza o que seu nome diz?", "esse método é de interesse desse objeto?", "meu objeto deve preocupar-se sobre aquele valor em específico?"

Supreendentemente, esses tipos de perguntas tem uma grande capacidade de clarificação sobre o domínio do negócio e sobre a arquitetura do software. Perguntamos e respondemos esses tipos de questões em grupo, lá na Syneto. Muitas vezes, quando um programador encontra-se em um dilema, ele ou ela se levanta, pede por alguns minutos de atenção da equipe para que todos deem suas opiniões sobre o assunto. Aqueles que estão familiarizados com a arquitetura do código, responderão do ponto de vista do software, enquanto os outros, mais familiarizados com o domínio do negócio, podem dar uma luz e comentários sobre os aspectos comerciais.

Pensemos nos interesses do nosso caso. Podemos continuar com foco sobre a classe Runner. É muito mais provável eliminar ou transformar essa classe que a classe Game.

Primeiro, um executador deveria saber se isCurrentAnswerCorrect() funciona? Um executador deve ter qualquer conhecimento sobre perguntas e respostas?

Parece que esse método estaria melhor na classe Game. Acredito, fortemente, que uma classe Game de um jogo sobre perguntas e respostas deva preocupar-se se uma resposta está correta ou não. Também acredito que uma classe Game deve estar preocupada em prover a resposta para a pergunta atual.

É hora de agir. Realizaremos uma refatoração de movimentação de método. Como vimos isso em tutoriais anteriores, apenas mostrarei o resultado final.

É essencial perceber que não foi apenas o método que foi movido, mas, também, a constante que define os limites das respostas.

Mas e o método didSomebodyWin()? Um executador deve decidir se alguém ganhou o jogo? Se olharmos o corpo do método, poderemos ver um problema bem destacado, como se fosse uma lanterna ligada no escuro.

O que quer que seja que esse método faz, ele age apenas sobre o objeto Game. Ele verifica a resposta atual retornada pelo jogo. E, então, retornar o que quer que seja que o objeto Game retorna de seus métodos wasCorrectlyAnswered() ou wrongAnswer(). Esse método não faz qualquer coisa por conta própria. Tudo com o que ele se importa é o objeto Game. Esse é um exemplo clássico sintoma de má programação, chamado de Inveja de Funcionalidade. Uma classe faz algo que outra classe deveria fazer. Hora de movê-la.

Como de costume, movemos os testes primeiro. TDD? Alguém?

Isso nos deixa sem testes a serem executados, logo, podemos dar fim a esse arquivo. Remoção é a minha parte favorita da programação.

E executamos nossos testes e obtemos um erro.

É hora de alterar o código também. Copiar e colar o método na classe Game fará com que todos os testes passem automagicamente. Tanto os antigos quanto os novos movidos para GameTest. Mas, embora isso coloque os métodos no lugar certo, há dois problemas: o executador também precisa ser alterado, e enviamos um objeto Game falso que não precisamos mais, uma vez que tudo faz parte da classe Game, agora.

Consertar o executador é bem fácil. Apenas alterarmos $this->didSomebodyWin(...) para $aGame->didSomebodyWin(...). Precisaremos voltar aqui e alterá-la novamente, após nosso próximo passo. A refatoração dos testes.

É hora da simulação! Ao invés de usar nossa classe falsa, criada ao final dos nossos testes, usaremos a biblioteca Mockery. Ela nos permite sobrescrever um método de Game, esperar que ele seja invocado e que tenha retornado o valor que queremos. Claro, poderíamos alcançar isso fazendo com que nossa classe falsa estendesse Game e sobrescrevêssemos o método por conta própria. Mas, por que ter esse trabalho todo quando temos uma ferramenta para isso?

Após nosso segundo método ser reescrito, podemos remover a classe game falsa e quaisquer métodos que a inicializava. Problema resolvido!

Pontos Finais

Mesmo que, basicamente, tenhamos trabalhado apenas com a classe Runner, obtivemos um ótimo progresso, hoje. Aprendemos sobre responsabilidade, identificamos métodos e variáveis que pertencem a outra classe. Pensamos em um nível mais alto e evoluímos nosso código em direção a uma solução melhor. Na equipe da Syneto, há uma forte crença que sempre há alguma forma boa de programar e que nunca devemos enviar uma mudança a não ser que o código esteja um pouco mais limpo e claro. Essa é uma técnica que, como tempo, pode levar a uma base de código melhor, com menos dependências, mais testes e, eventualmente, menos erros.

Obrigado pelo seu tempo.

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.