Pessoal, puxei o Raul pra conversar sobre os pontos que ele levantou, acho pertinente compartilhar aqui, e talvez até alterar a decisão tomada anteriormente. Vai ficar um post meio grande, mas vamo nessa. ## Com a decisão atual (passando pelo construtor), teriamos uma classe no seguinte estilo: ``` Ruby= class DecidePendencyTypeService def initialize(author:, pendency_type: nil) @author = author @pendency_type = pendency_type end def call return @pendency_type if pendency_type_known? define_pendency_by_role end private def define_pendency_by_role return 'evaluation' if @author.roles.map(&:to_s).include?('evaluator') return 'operation' if @author.roles.map(&:to_s).include?('operator') return 'antifraud' if @author.roles.map(&:to_s).include?('analyst') end private def pendency_type_known? ProposalNote.all_pendency_type.map(&:to_s).include?(@pendency_type) end end ``` Nesse caso temos uma classe bem estruturada e até simples, porém tem alguns pontos que levantaram atenção: 1) Não estamos incentivando o uso de funções puras. 2) A existência do initialize pode gerar ambiguidade sobre onde chamar as funções (no call ou no próprio initialize), mesmo que tenhamos o controle disso no review de PRs e ADRs, o simples fato de existir o initialize definido já é uma brecha. ## Caso optemos por passar todos os parâmetros no call teríamos a mesma classe mais ou menos assim: ``` Ruby= class DecidePendencyTypeService def call(author:, pendency_type: nil) return pendency_type if pendency_type_known?(pendency_type) define_pendency_by_role(author) end private def define_pendency_by_role(author) return 'evaluation' if author.roles.map(&:to_s).include?('evaluator') return 'operation' if author.roles.map(&:to_s).include?('operator') return 'antifraud' if author.roles.map(&:to_s).include?('analyst') end private def pendency_type_known?(pendency_type) ProposalNote.all_pendency_type.map(&:to_s).include?(pendency_type) end end ``` Nesse caso o código pra mim me pareceu um pouco mais limpo. 1) Incentiva o uso de funcoes puras 2) Remove a possibilidade de ter comportamento espalhado (initialize e call) Essas melhorias já seriam bons pontos que favoreceriam a adoção desse padrão. Mas olhando mais a fundo a gente ainda conseguiu encontrar algumas melhorias com o uso de mocks nos testes. ## Como isso ajuda na hora de fazer testes? Se quisermos testar quem interage com `DecidePendencyTypeService` e precisarmos saber mockar os retornos para que se torne um teste unitario, e garantir que o teste seja paralelizável. No case dos parâmetros enviados via construtor vamos precisar testar da seguinte forma: ```ruby= instance_of_service = InstanceDouble(DecidePendencyTypeService) allow(DecidePendencyTypeService) .to receive(:new) .with(author: author) .and_return(instance_of_service) allow(instance_of_service) .to receive(:call) .and_return(pendency_type) ``` Nesse caso tivemos que definir a instancia que o `new` retornara e dar um allow nessa instancia a retornar o valor que queremos para o metodo call. ```ruby= allow_any_instance_of(DecidePendencyTypeService) .to receive(:call) .with(author: author) .and_return(pendency_type) ``` Acabamos mockando menos e deixando o setup mais simples e objetivo.