Refatorando GildedRose
Recentemente tive a oportunidade de estudar um formato de refactoring bem interessante sugerido por Boddy Johson e expandido para mais linguagens por Emily Bache. O objetivo desse refactoring é ajustar um programa existente para que seja possível controlar a qualidade de alguns itens vendidos por um Goblin, ele determina algumas regras pra isso, o repositorio original é https://github.com/emilybache/GildedRose-Refactoring-Kata.
A descrição básica do problema pode ser encontrada em diversos idiomas, vamos dar uma breve introdução do nosso sistema:
- Todos os itens (classe
Item
) possuem uma propriedade chamadaSellIn
que informa o número de dias que temos para vende-lo - Todos os itens possuem uma propriedade chamada
quality
que informa o quão valioso é o item. - No final do dia, nosso sistema decrementa os valores das propriedades
SellIn
equality
de cada um dos itens do estoque através do métodoupdateQuality
.
Bastante simples, não é? Bem, agora que as coisas ficam interessantes:
- Quando a data de venda do item tiver passado, a qualidade (
quality
) do item diminui duas vezes mais rápido. - A qualidade (
quality
) do item não pode ser negativa - O “Queijo Brie envelhecido” (
Aged Brie
), aumenta sua qualidade (quality
) em1
unidade a medida que envelhece. - A qualidade (
quality
) de um item não pode ser maior que 50. - O item “Sulfuras” (
Sulfuras
), por ser um item lendário, não precisa ter uma data de venda (SellIn
) e sua qualidade (quality
) não precisa ser diminuida. - O item “Entrada para os Bastidores” (
Backstage Passes
), assim como o "Queijo Brie envelhecido", aumenta sua qualidade (quality
) a medida que o dia da venda (SellIn
) se aproxima; - A qualidade (
quality
) aumenta em2
unidades quando a data de venda (SellIn
) é igual ou menor que10
. - A qualidade (
quality
) aumenta em3
unidades quando a data de venda (SellIn
) é igual ou menor que5
. - A qualidade (
quality
) do item vai direto à0
quando a data de venda (SellIn
) tiver passado.
Nós recentemente assinamos um suprimento de itens Conjurados Magicamente. Isto requer que nós atualizemos nosso sistema:
- Os itens “Conjurados” (
Conjured
) diminuem a qualidade (quality
) duas vezes mais rápido que os outros itens.
Sinta-se livre para fazer qualquer alteração no método updateQuality
e adicionar código novo contanto que tudo continue funcionando perfeitamente. Entretanto, não altere o código da classe Item
ou da propriedade Items
na classe GildedRose
pois elas pertencem ao Goblin que irá te matar com um golpe pois ele não acredita na cultura de código compartilhado.
Notas Finais
Para esclarecer: Um item não pode ter uma qualidade (quality
) maior que 50
, entretanto as "Sulfuras" por serem um item lendário vão ter uma qualidade imutavel de 80
.
O arquivo que devemos refatorar é a execução do update da qualidade dos itens:
O GildedRoseApp fica responsável por efetuar o update dos itens com os dias definidos na variável days.
Iniciando o Refactoring
A primeira análise não me direcionou diretamente para o arquivo de testes, mas esse deveria ser meu primeiro passo, tive ajuda de alguns colegas de profissão após um rápido bate papo, eles me sugeriram que o arquivo de teste deveria ser o principal arquivo para facilitar a compreenssão do problema.
Mas o arquivo de testes não me ajudaria muito no inicio da fase de refactoring, minha mente ainda trabalhava numa forma de categorizar os itens que seriam cadastrados no estoque do Goblin, o fato dele não acreditar em código compartilhado me posicionava como um extensor da funcionalidade pois, eu não poderia alterar o arquivo Item nem a propriedade items dentro da classe GildedRose. Voltando para o arquivo de testes:
Nada no projeto de testes me trouxe ao comportamento esperado, não existiam implementações que direcionassem o refactoring ou expusessem de forma clara as regras adotadas. Se me baseasse na técnica de TDD, deveria escrever os testes necessários para garantir a regra de sucesso e para quebrar essa regra. Somente após esse step eu deveria alterar o restante do projeto. Confesso que esse não foi o caminho que adotei, e ao final do refactoring, escrevendo esse diário me deparei com erros que ajustei durante essa escrita, por justamente subjulgar a importância dessa técnica.
Categorias de items
Tive muitas incertezas relacionadas às categorias, e ao final do refactoring ainda continuei com elas, não ficou claro se foi a melhor solução, mas conforme o refactoring seguia a estrutura me levou para um modelo que extendia a regra de negócio da aplicação.
Pelas regras determinadas, itens comuns não podem ter qualidade superior a 50 nem inferior a 0, minha primeira tentativa foi criar um propertyWrapper, semelhante ao que eu visitei nesse artigo do NSHipster.
Esse objecto era responsável por limitar a quantidade de qualidade de um item, restringindo à um range pré definido. Essa ideia não foi totalmente abandonada, mas após a primeira herança ficou impossível sobreescrever a propriedade quality com os limites de outros tipos de produto.
A abordagem seguinte foi puxar essa regra para um wrapper computado, que permitiu a sobreescrita do range de qualidade.
A regra do qualityAssure é utilizar a quality definida na categoria do produto, essa regra seria extendida por mais 4 categorias de produtos LegendaryItem, Aged, Pass e Conjured.
Esses itens especializados iriam me ajudar a refactorar a classe que para mim tinha alguns problemas:
- Comparação de strings
- Complexidade alta nas regras de negócio
- Encadeamento de regras
- Itens conjurados não funcionavam corretamente
O refactoring
O processo que adotei foi criar um método especializado em cada um dos objetos, mas primeiramente vamos retornar à um dos requisitos solicitados pelo Goblin, não altere a propriedade items na classe GildedRose, seguindo essa solicitação e um pouco de SOLID, o príncipio de substituição de Liskov me permitiu adicionar especialização sem alterar a classe GildedRose.
A lista de Item não alterou sua origem, o código funcionaria da mesma forma caso o método updateQuality não fosse refatorado. A especialização somente me permitiu dar uma visão de uma implementação de um pattern no futuro, o Factory seria utilizado para instanciar a classe de objeto correto baseado na sua categoria.
O método fico bem mais simples e efetua verificações simples, caso o item não seja lendário altere a data de venda do item, de acordo com o tipo de item, altere sua qualidade. Vamos analisar os métodos de ajuste de qualidade um pouco mais de perto.
São bem simples, itens comuns reduzem sua qualidade em 1 conforme os dias passam, quando atingir a data limite de venda, ele dobra a quantidade de qualidade reduzida. Items conjurados reduzem sua qualide 2 vezes mais que itens comuns. Itens com qualidade incremental conforme o tempo passa, acompanham uma regra simples, a mais complexa seria a dos itens do tipo passagem para os bastidores essa regra foi ajustada com multiplicadores, caso a data seja menor ou igual a zero ele reduz a qualidade diretamente a zero.
Os testes
Teste são as porcentagens de códigos mais valiosas que escrevemos durante a carreira, isso garante algumas boas horas de sono quando bem escritos, em conjunto com testes funcionais e regressivos, incrementam a qualidade de um projeto em 200% (número baseado nada, só achismo mesmo). Escrever esses testes foram bem simples, mas como mencionei pouco acima, rolou alguns ajustes durante este artigo e aposto que precisaria revisitar mais vezes até chegar na cobertura ideal.
Para itens lendários o teste é simples, nada precisa ser alterado, nem qualidade nem data de venda.
Para os itens que aumentam sua qualidade conforme aumenta sua idade, o importante é garantir o limite de 50 na qualidade do item.
Para os itens do tipo PassItem que aumentam sua qualidade de acordo com a proximidade do evento, mas após a data do evento sua qualidade reduz a zero.
Para itens do tipo conjurados, o padrão de comportamento é garantir que sua qualidade reduza 2 vezes mais rápido que um item comum.
Considerações finais
Sou muito grato quando recebo um exercício tão delicado e ao mesmo tempo tão complexo, isso me garantiu mais algumas horas de estudo em uma direção extremamente valorosa. Fiquei feliz com o resultado final, não sei se o Goblin gostou do resultado, mas acredito que com muito esforço um dia poderei chegar no nível que desejo, ser especialista não deve ser nada fácil, dominar arquitetura, padrões de projeto, conhecimento de negócio e liderança de pessoas são itens essenciais para a maioria dos profissionais, mas muito difíceis de masterizar, cada minuto de estudo conta e cada direção apontada conta ainda mais. Obrigado aos responsáveis por esse desafio.