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

Refatorando Código Legado: Parte 7 - Identificando a Camada de Apresentação

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Refactoring Legacy Code.
Refactoring Legacy Code: Part 6 - Attacking Complex Methods
Refactoring Legacy Code: Part 8 - Inverting Dependencies for a Clean Architecture

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.

Usaremos um tipo diferente de refatoração nesta sétima parte da nossa série. Nas partes anteriores, observamos que há código da parte da apresentação misturado por todo o resto do código legado. Tentaremos identificar todos os códigos relativos à parte de apresentação que pudermos e tomaremos os passos necessários para separá-los da lógica de negócios.

A Força Motriz

Toda vez que realizar alguma alteração por conta da refatoração em nosso código, fazemos guiados por alguns princípios. Esses princípios e regras ajudam-nos a identifica os problemas e, em vários casos, elas nos apontam na direção correta para deixarmos o código melhor.

O Princípio da Responsabilidade Única (SRP)

O Princípio da Responsabilidade Única é um dos princípios SOLID que falamos em mais detalhes em um tutorial anterior: Princípios SOLID Parte 1 - O Princípio da Responsabilidade Única. Se quiser saber mais detalhes, recomendo que leia o artigo, caso contrário, continue a leitura e veja o resumo desse princípio logo abaixo.

O SRP, basicamente, afirma que qualquer módulo, classe ou método deve ter apenas uma única responsabilidade. Tal responsabilidade é definida como o eixo para mudança. Então, o SRP diz que nossa classe só pode ter um único motivo para mudar.

Embora isso pareça bem simples, como podemos definir "um motivo para mudar"? Temos de pensar nisso do ponto de vista dos usuários do nosso código, tanto os normais quantos outros departamentos relacionados a programação. Esses usuários podem ser representados como atores. Quando um ator quer que alteremos nosso código, o motivo de alteração que determinará o eixo da mudança. Tal requerimento só deverá afetar apenas um de nossos módulos, classes ou mesmo métodos, se possível.

Um exemplo bem óbvio seria se nossa equipe de design de Interfaces de Usuário requisitasse todas as informações necessárias para apresentação, de forma que nossa aplicação pudesse ser entregue na forma de uma página HTML, ao invés da atual interface de linha de comando.

Como nosso código está hoje, poderíamos apenas enviar todo o texto para algum objeto inteligente externo, que o transformaria em HTML. Mas isso pode funcionar porque o HTML é, basicamente, texto. E se nossa equipe de design decidir criar uma interface de usuário para desktop do nosso jogo, com janelas, botões e várias tabelas?

E se nossos usuários quiserem ver o jogo em um tabuleiro de virtual, com cidades e ruas, e os jogadores andando pelos quarteirões?

Poderíamos identificar essas pessoas como um Ator de Interface. E devemos perceber que, da forma que nosso código está hoje, precisaríamos alterar nossa classe principal e praticamente todos os seus métodos. Faz sentido modifica o método wasCorrectlyAnswered() da classe Game se eu quiser corrigir um erro de digitação no texto apresentado, ou se eu quiser colocar o jogo em um tabuleiro virtual? Não. Absolutamente, não.

Arquitetura Limpa

Arquitetura Limpa é um conceito bastante promovido por Robert C. Martin. Basicamente, ele diz que nossa lógica de negócios deveria ser bem definida e com separação bem delimitada de outros módulos não relacionados à funcionalidade principal do sistema. Isso leva a um código altamente dissociado e testável.

Talvez tenha visto esse desenho em meus outros tutoriais e cursos. Considero ele tão importante, que nunca codifico ou escrevo sobre programação sem pensar nele. Ele mudou totalmente o modo que programo na Syneto, e como nossos projetos são. Antes, todo nosso código usava um framework MVC com a lógica de negócios atrelada aos modelos. Isso tanto era difícil de entender quanto difícil de testar. Além disso, a lógica do negócio estava totalmente atrelada àquele framework MVC. Embora possa funcionar para projetos pequenos, quando se trata de projetos grandes, dos quais o futuro da empresa depende, incluindo o futuro de todos os funcionários nesse meio, você deve começar a pensar como organizar seu código. Uma vez feito isso, e feito certo, não precisará voltar a arquiteturar seus projetos como antes.

Observando a União

Já começamos a separar nossa lógica de negócios da parte de apresentação nos artigos anteriores. Algumas vezes, percebemos a existência de algumas funções de impressão e as extraímos em métodos privados, dentro da classe Game. Essa guerra era inconsciente, fazendo-nos separar a apresentação da lógica de negócios no nível do método.

Agora, é hora de analisar e observar.

Essas é a lista de todas as variáveis, métodos e funções de nosso arquivo Game.php. Aquilo marcado com um "f" laranja, são variáveis. Os marcados com um "m" vermelho, são métodos. Se for seguido por um cadeado verde, significa que é público. Se for seguido por um cadeado vermelho, significa que é privado. E, dessa lista, temos interesse é na seguinte parte:

Todos os métodos selecionados tem algo em comum. Todos eles tem uma nomeação parecida, começando com "display". Eles são métodos relacionados à impressão de coisas na tela. Identificamos todos nos tutoriais anteriores e extraídos um a um. Agora, devemos observar que eles são um grupo de métodos que devem ficar juntos. Um grupo que realiza algo específico, satisfaz o princípio da responsabilidade única: imprimir informação na tela.

A Refatoração Através da Extração de Classe

Apesar de ser melhor exemplificado e explicado no livro Refactoring - Improving the Design of Existing Code, de Martin Fowler, a ideia básica da Refatoração da Extração de Classe é que, após ver que sua classe funciona, o trabalho dela deveria ser dividido entre duas outras classes. Há meios específicos para isso, como explicado na citação abaixo, retirada do livro mencionado mais acima.

  • Decida como dividir as responsabilidades entre as duas classes;
  • Crie uma nova classe que expresse a responsabilidade extraída;
    • Se as responsabilidades da classe anterior não corresponder ao seu nome, renomeie a classe;
  • Crie um elo entre a classe antiga e a nova;
    • Talvez precise de um elo de mão dupla. Mas não crie o elo de retorno até ter certeza que precisará dele;
  • Use o processo de Movimentação de Campo em cada campo que desejar mover para nova classe;
  • Compile e teste a cada movimentação;
  • Use a movimentação de Métodos para mover os métodos da classe antiga para a nova. Comece por métodos de baixo nível (os invocados ao invés dos que invocam) e construa o nível mais alto;
  • Compile e teste após cada movimentação;
  • Revise e reduza as interfaces de cada classe;
    • Se você tinha elos de mão dupla, veja se tem como deixar um elo de mão única, apenas;
  • Decida o que expor da nova classe. Se expuser a classe, decida se deve expô-la como uma referência de objeto ou um objeto de valor imutável.

Aplicando a Extração de Classe

Infelizmente, até o momento da escrita deste artigo, não há qualquer IDE para PHP que seja capaz de extrair uma classe a partir da seleção de alguns métodos e do uso de algumas opções em algum menu.

Como não mata saber o mecanismo do processo, realizaremos os passos descritos acima, um por um, e aplicaremos a extração de classe em nosso código.

Decidindo Como Dividir as Responsabilidades

Nós já sabemos isso. Queremos separar a apresentação da lógica de negócios. Queremos pegar as funções de impressão e outros códigos do tipo, e movê-las para outro lugar.

Crie uma Nova Classe

Nossa primeira ação é criar uma nova classe vazia.

Sim. Isso é tudo por hora. E encontrar um nome apropriado para ela também foi bem fácil. Display é uma palavra que começa o nome de todos os métodos que estamos interessados. É o denominador comum dos nomes deles. É uma sugestão muito importante sobre o comportamento comum deles, comportamento que usamos para nomear nossa classe.

Se preferir e sua linguagem de programação der suporte (o PHP suporta), você pode criar uma classe dentro do mesmo arquivo da classe antiga. Ou, você pode criar um novo arquivo para ela e começar do zero. Pessoalmente, nunca achei uma resposta definitiva para escolher um dos dois caminhos em detrimento do outro. Então, cabe a você escolher um.

Ligando a Classe Antigo à Nova Classe

Esse passo talvez não pareça tão familiar. O que queremos fazer é declarar uma variável de classe na classe antiga e atribuí-la uma instância da nova classe.

Simples, não é? No construtor da classe Game, apenas inicializamos uma variável privada de classe que nomeamos igual à nova classe, display. Também tivemos de incluir o arquivo Display.php em nosso arquivo Game.php. Ainda não usamos um auto carregador. Talvez, em um tutorial futuro, introduzamos um, se precisarmos.

Como de costume, não esqueça de executar seus testes. Testes unitários são o suficiente nesse ponto, só para ter certeza que não há qualquer erro de digitação no código recém adicionado.

A Movimentação de Campo e a Compilação/Teste

Tomemos esses dois passos de uma só vez. Que campos podemos identificar que deveriam sair da classe Game e pertencer à Display?

Ao verificar a lista...

... não pudemos encontrar qualquer variável/campo que deva pertencer à classe Display. Talvez algum venha a surgir. Então, não há o que fazer para esse passo. E sobre os testes, nós já o executamos segundos atrás. Hora de seguir.

Movimentação de Métodos para a Nova Classe

Por si só, isso é outra refatoração. Você pode fazê-la de diversas formas e encontrar uma ótima definição sobre ela no livro mencionado masi cedo.

Como dito acima, devemos começar pelos métodos de mais baixo nível. Aqueles que não invocam outros métodos, mas, sim, são apenas invocados.

O método displayPlayersNewLocation() parece ser um bom candidato. Analisemos o que ele faz.

Podemos ver que ele não invoca quaisquer outros métodos da classe Game. Ao invés disso, ela usa três campos: players, currentPlayer e places. Eles podem virar dois ou três parâmetros. Por enquanto, tudo bacana. E em relação ao echoln(), a única chamada de método dentro deste método? De onde vem este echoln()?

Ele está no topo do nosso arquivo Game.php, fora da classe Game em si.

Ele realmente faz o que diz. Imprime uma nova cadeia de caracteres com um caractere de nova linha ao final. E isso é pura apresentação. Ele dever ir para a classe Display. Então, extraiamos para a nova classe.

Executemos nossos testes novamente. Podemos manter o resultado esperado desabilitado até que terminemos de extrair toda a parte de apresentação para a classe Display. A qualquer momento, se achar que o retorno foi modificado, torne a executar os testes do resultado esperado. Neste momento, os testes provarão que não adicionamos qualquer erro de digitação ou declarações de funções duplicadas ou quaisquer outros erros, ao copiar uma funçãod e um lugar para outro.

Agora, vá e remover o método echoln() do arquivo Game.php, execute os testes e tenha certeza que eles falharão.

Legal! Nossos testes unitários são de grande ajuda. Eles executam bem rápido e nos dizem a exata posição do problema. Hora de ir à linha 55.

Veja! há uma invocação ao método echoln() aqui. Os testes nunca mente. Ajustemos isso, fazendo uma invocação a $this->dipslay->echoln().

Isso faz com que o teste passe pela linha 55 e pare na linha 56.

E a solução é óbvia. Esse é um processo tedioso, porém, bem fácil.

Isso faz os três primeiros três testes passarem e também nos aponta o próximo lugar onde deveríamos alterar.

É lá no método wrongAnswer().

Ajustando essas duas invocações, leva a um erro na linha 228.

Um método com a palavra display! Talvez esse seja nosso primeiro método a mudar de lugar. Tentaremos seguir um pouco o desenvolvimento voltado a testes (TDD) aqui. E, quando os testes falham, não somos permitidos escrever quaisquer outros códigos de produção que não seja absolutamente necessário para o teste passar. E tudo isso que acarreta é apenas a alteração da invocação a echoln() até que os testes unitários passem.

Você pode acelerar esse processo, usando a funcionalidade de busca e substituição do seu editor ou IDE. Apenas execute todos os testes, incluindo o do resultado esperado após você realizar essa substituição. Nossos testes unitários não cobrem todo o código nem todas as chamadas a echoln().

Podemos começar nosso primeiro nosso candidato, displayCurrentPlayer(). Copie-o para a classe Display e execute os testes.

Depois, torne-o público na classe Display e no método displayCurrentPlayer() na classe Game, invoque $this->display->displayCurrentPlayer() ao invés de invocar diretamente echoln(). Por fim, execute seus testes.

Eles falharão. Mas, ao fazer essas mudanças dessa forma, garantimos que apenas alteramos a única coisa que poderia falhar. Todos os outros métodos ainda invocam o método displayCurrentPlayer() da classe Game. E estamos coisa foi delegada à classe Display.

Nosso método usa campos de classe. Eles precisam virar parâmetros para a função. Se seguir os erros do seu teste, você acabará com algo mais ou menos assim, na classe Game.

E algo parecido a isso, na classe Display.

Substitua as invocações ao método local na classe Game pelos do método da classe Display. Não esqueça de mover os parâmetros em um nível, também.

Por fim, remova o método não usado na classe Game. E execute seus testes para garantir que tudo está certo.

Esse é um processo tedioso. Você pode acelerá-lo um pouco, levando vários métodos de uma só vez e usando qualquer coisa que sua IDE forneça para ajudar na mudança e substituição de código entre as classes. O resto dos métodos serão um exercício para você ou você ler mais desse tutorial para ver os destaques do processo. O código finalizado está atrelado a esse artigo e conterá a classe Display finalizada.

Ah, e não esqueça do código que ainda não foi extraído dos métodos "display" dentro da classe Game. Você pode mover as invocações a echoln() diretamente para a Display. Nosso objetivo é não invocar qualquer echoln() de dentro da classe Game e torná-la privada em Display.

Depois de aproximadamente uma hora de trabalho, a classe Display começa a ficar interessante.

Todos os métodos "display" da classe Game estão na classe Display. Agora, podemos procurar por todas as invocações a echoln que permanecem na classe Game e movê-los também. Os testes continuam passando, claro.

Mas, tão logo nos deparamos como método askQuestion(), percebemos que ele também é um método de apresentação. Isso significa que os vários vetores de perguntas também devem ir para a classe Display.

Parece certo. Perguntas são apenas cadeias de caracteres, que apresentamos e elas ficam melhor nessa classe. Quando realizamos esse tipo de refatoração, também é uma boa oportunidade de refatorar nosso código recém movimentado. Nós definimos valores iniciais na declaração dos campos, também os tornamos privados e criamos um método com o código que é precisamos executar para não ficar no método construtor. Ao invés disso, ele está escondido ao final da classe, fora de vista.

Após extrair os dois métodos, percebemos que é interessante renomeá-los, dentro da classe Display, sem o prefixo "display".

Com nossos testes verde e tudo correndo como esperado, podemos refatorar e renomear os métodos. A IDE PHPStorm é capaz de lidar com renomeação muito bem. Ela renomeará todas as invocações na classe Game, apropriadamente. Até que há esse trecho de código.

Atente bem à linha selecionada, a 119. Parece bastante com o nosso método recém extraído para Display.

Mas, se o invocarmos ao invés do código em si, o teste falhará. Sim! Há um erro de digitação! E, NÃO! Você não deve corrigi-lo. Estamos realizando refatorações. Devemos manter a funcionalidade intocada, mesmo que haja algum erro.

O resto do método não gera qualquer desafio para ajustes.

Revisão e Redução das Interfaces

Agora, toda que toda funcionalidade de apresentação está na classe Display, devemos revisar os métodos e manter públicos apenas aqueles usados na classe Game. Esse passo também é motivado pelo Princípio da Segregação de Interfaces que falamos em tutoriais passados.

Em nosso caso, a forma mais fácil de descobrir quais métodos precisam manter-se públicos ou provados, é torná-los todos privados, executar os testes e, se eles falharem, é preciso revertê-los para público.

Por conta da demora da execução dos testes do resultado esperado, também podemos depender de nosso IDE a nos ajudar a acelerar o processo. O PHPStorm é esperto o suficiente para descobrir se o método não é utilizado. Se tornarmos um método privado e ele, de repente, torna-se não usado, é claro que ele era usado fora da classe Display e precisa continuar como público.

Por fim, vemos a classe Display e arranjamos uma forma que os métodos privados fiquem ao final da classe.

Pontos Finais

Agora, o último passo do princípio de Refatoração da Extração de Classe é irrelevante para nós. Com isso, concluímos nosso tutorial, mas ainda não concluímos a série. Fique ligado por nosso próximo tutorial, onde trabalharemos a Arquitetura Limpa e inverter as dependências.

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.