Refatorando com confiança!

Você já encontrou algum código e pensou: “Nossa, que código bom!”? (obs: ler com voz irônica).

Talvez sim, mas provavelmente a proporção de base de códigos bom vs ruins é bem baixa. O que leva alguém a escrever um código ruim? Com certeza a habilidade e experiência da pessoa influencia na qualidade do seu código, meus primeiros códigos não tinham a mesma qualidade dos que escrevo hoje (ainda bem!).

Eu acredito que o principal fator que torna um código ruim não é a pessoa que escreveu, mas sim as várias pessoas que passaram por aquele código e não o melhoraram quando necessário (e já adianto que nem vou falar sobre a falta de tempo).

Qual o melhor momento para refatorar código?

Quando você precisa mudar um código para implementar uma nova funcionalidade. Não, não é durante o code review e nem fora das horas de trabalho.

Refatorar fora das horas de trabalho (horas que o time concorda em estar junto trabalhando) cria a cultura do heroísmo onde uma pessoa salva o projeto. O problema é que precisa de um herói para terminar o trabalho de outro herói, e ai já viu né.

Durante o code review é comum fazermos pequenas sugestões, alertar sobre código duplicado ou até conflitos de funcionalidade.

Durante a revisão de código quem está revisando provavelmente não sabe como aquele código vai precisar evoluir, então qualquer sugestão é baseada apenas na experiência da pessoa, é só um chute. Se aquela parte do código nunca mais for tocada, não importa se o código tá duplicado ou se a modelagem está bem feita, se os testes passam e o código funciona tá tudo bem.

Na hora de mudar vai tá tudo uma bagunça

Sim, provavelmente, e isso é bom! Na hora que precisar mudar você vai ter mais contexto de como o código precisa evoluir e qual seria um bom design, o que é bem melhor do que só um chute.

(Essa ideia de que revisão de código aumenta a qualidade do código é o que mais me incomoda na cultura de Pull Requests, mas essa polêmica fica pra outro post)

O que realmente causa a bagunça não é quem escreveu o código (a coitada da pessoa que escreveu não tinha como prever o futuro, certo?) é quem passa por ele e não se preocupa em melhorar.

Uma ideia interessante do livro 99 Bottles of OOP (um excelente livro da Sandi Metz e Katrina Owen, super recomendo) é o Shameless Green, que basicamente segue a ideia do TDD a risca. Escreva o código que faz os testes passarem e pronto.

Não adianta especular generalizações e nem forçar todas as abstrações de domínio possíveis. Apenas se preocupe em escrever o código que faz os testes passarem e confie que quando uma mudança for necessária, a pessoa que estiver escrevendo o código vai melhorá-lo.

Regras de negócio mudam, pessoas ganham mais conhecimento do negócio, da aplicação e de escrita de código em geral, é normal que o código precise de pequenas (ou grandes) melhorias de vez em quando.

Refatore com confiança!

Considerando que você tem mais contexto de quem escreveu o código original é hora de procurar por pontos de melhoria, e ai entram os code smells!

Eu já devo ter escrito por aqui outras vezes mas é sempre bom relembrar: identifique code smells primeiro refatore depois.

Um ponto importante sobre code smells é que eles não são estáticos (apesar de existirem ferramentas de análise estática de código que funcionam muito bem). Pense por um instante: qual é o real problema com o código duplicado?

Eu não acho que é ter duas (ou mais) partes parecidas de código fazendo a mesma coisa, mas sim alguma abstração do domínio que precisa ser identificada.

Aliar o contexto de negócio, sabendo como o código precisa evoluir, com o conhecimento de escrita de software é crucial.

Se você quiser conhecer mais sobre refatoração, padrões de projeto e design orientado a objetos colocando a mão na massa, dá uma olhada nos livros Refatorando com Padrões de Projeto (com versão em Ruby e Java).

Anúncios

Novo livro: Refatorando com Padrões de Projeto, um guia em Java!

Se você gosta de padrões de projeto e curte as publicações do blog, o livro Refatorando com Padrões de Projeto, um guia em Java é perfeito para você!

livro java

Sobre o livro

Se você já leu a série Mão na Massa sobre Padrões de Projeto aqui do blog, você vai curtir ler o livro. Segui a mesma ideia de ter exemplos práticos para utilização dos padrões, mas adicionei um pensamento mais crítico para a implementação através de técnicas de refatoração.

O conteúdo é o mesmo da edição em Ruby, mas adaptado ao contexto da linguagem Java, explorando as diferenças de linguagens estaticamente tipadas mas compartilhando as mesmas ideias: aplicar pequenas mudanças com visando alcançar um design melhor com Padrões de Projeto.

Público Alvo

Ter um entendimento bom de Orientação a Objetos é importante, mas muitas vezes é difícil encontrar exemplos práticos tanto de problemas quanto de boas soluções para o seu design.

Se você se encontra nesse meio termo, alguém que já passou pelo básico e está procurando um material mais prático, o livro vai te ajudar a entender o papel dos Padrões de Projeto no design e como Refatoração auxilia na hora de por a mão na massa!

Developer Experience

Já parou para pensar nas ferramentas que você, desenvolvedor, usa no dia-a-dia? Do editor de texto/IDE até como o software é implantado em produção, o que você utiliza precisam oferecer uma boa experiência, assim como um novo modelo de celular.

A evolução da Experiência de Uso (User eXperience – UX) nos proporciona maneiras cada vez melhores de interagir com nossos dispositivos, software e até nós mesmos. De maneira semelhante a evolução da Experiência de Desenvolvimento muda a maneira como o software é desenvolvido e como interagimos em equipe.

A revolução da Nuvem

Um grande benefício, poucas vezes reconhecido, de utilizar um serviço como Amazon ou Heroku é poder criá-las sem muita burocracia. Um git push e sua aplicação está rodando no Heroku, sem dependências com um time de infraestrutura.

De maneira semelhante, a popularização de contêineres com o Docker permite que você consiga rodar na sua máquina uns dois servidores Phoenix com um NGINX para balancear a carga, um Mongo para guardar dados, Memcached para fazer caching e um Nagios para monitorar tudo isso. Tá, talvez não tudo isso, mas dá pra endenter a ideia 🙂

O benefício de poder experimentar com tecnologias diferentes e colocá-las em produção sem muitas adaptações na infraestrutura aumenta muito a experiência de desenvolvimento.

O tempo para Hello World

O Hello World é a maneira mais fácil de experimentar uma ferramenta nova, não é nada complexo complexa e dá uma boa visão sobre como configurar um ambiente. O framework Rails ficou bem conhecido pela sua capacidade de permitir experimentação muito rápido, basta rodar alguns simples comandos e você tem uma estrutura completa, com views e banco de dados 100% funcionais.

Hoje em dia praticamente qualquer framework oferece alguma maneira de criar uma estrutura básica, com diretórios e configurações básicas. A linguagem Crystal (https://crystal-lang.org) já possui um comando que inicializa os diretórios, cria arquivos básicos, testes e inclusive configurações de um repositório Git – isso não é uma biblioteca, o executável da linguagem oferece isso!

Outro bom exemplo é o http://tryruby.org/ que permite aprender sobre a linguagem Ruby direto no navegador, sem instalar nada. Essa ideia levou o primeiro contato com a linguagem para outro nível de facilidade e hoje em dia várias linguagens/ferramentas possuem algo do tipo, como o tutorial de Go https://tour.golang.org/.

Esses são só alguns exemplos de como a preocupação em melhorar a vida dos desenvolvedores leva a criação de novas ferramentas que simplificam e facilitam seu uso. Nos próximos posts vamos explorar melhor o impacto dessas facilidades nos times de software, então acompanhe o blog e fique ligado 🙂

3 dicas para utilizar Padrões de Projeto

Aplicar um Padrão de Projeto pode ser um processo não muito simples, então siga essas dicas para ter mais confiança e entendimento antes de começar. Se quiser ir mais afundo no tema e entender como Padrões de Projeto podem ajudar, leia o livro Refatorando com Padrões de Projeto.

  1. Identifique a oportunidade de melhoria: Aplicar um Padrão de Projeto aumenta a complexidade do código, portanto é preciso ter certeza que vale a pena aplicá-lo. Comece pensando no que o código ganharia se o padrão for aplicado, pode ser tanto um code smell que será resolvido quanto uma alteração futura que será facilitada.
  2. Entenda o contexto do código: Cada Padrão de Projeto possui um contexto onde ele faz sentido e de fato resolve o problema, apesar de aumentar a complexidade. Aplicar um padrão fora de contexto vai causar tanto dificultar sua implementação quanto dificultar o entendimento dos outros. O coitado do Builder que o diga.
  3. Aplique pequenas mudanças nos testes e no código: No post anterior falei sobre a diferença entre refatorar e mudar o código, se quiser aplicar um padrão refatore! Essas mudanças em pequenos passos, além de te dar mais segurança, também servem para te dar um entendimento melhor do design que você está propondo. Pode ser que no meio da refatoração você descubra que apenas modificar um pouco o design resolve o problema, sem adicionar toda a complexidade dos padrões.

Lembre-se sempre que Padrões de Projeto adicionam complexidade ao código, então é preciso entender se vale a pena aplicá-los ou não. Se decidir por aplicá-los, siga sempre refatorando o código, evite fazer grandes mudanças no código de qualquer jeito, seja um profissional disciplinado!

Refatoração não é só mudar o código!

Você provavelmente já ouviu alguém falando em refatoração, mas será que a pessoa realmente estava falando de refatoração? Qual a diferença entre refatorar e apenas modificar o código? Se quiser ir mais afundo no tema e entender como Padrões de Projeto podem ajudar, leia o livro Refatorando com Padrões de Projeto.

É importante entender a diferença, apesar de parecer algo mais conceitual, pois a mentalidade ao refatorar o código é diferente de quando implementamos uma funcionalidade nova ou estamos corrigindo um problema.

Ao refatorar buscamos maneiras de (1) melhorar o design existente, (2) aplicando mudanças em pequenos passos e (3) evitando deixar o sistema quebrado. Vamos falar sobre essas três partes em mais detalhes.

Ao buscar maneiras de melhorar o design existente devemos pensar em novas soluções que podem melhorar o código (um pensamento bem diferente de quando estamos corrigindo um bug).

A segunda parte é muito importante pois ao aplicar mudanças em pequenos passos garantimos a consistência e ritmo de desenvolvimento. Quanto menos mudanças fizermos mais fácil é evitar problemas.

E a terceira parte é bem ligada a segunda pois, ao fazer mudanças pequenas, podemos executar todos os testes e validar o que está sendo feito. Se o tempo entre ter testes verdes for muito grande, é um sinal de que talvez as mudanças devam ser menores.

As técnicas de refatoração descritas por Martin Fowler seguem esse modelo pois descrevem um passo-a-passo onde aplicamos uma mudança pequena e executamos os testes até alcançar o objetivo final da refatoração.

Se você quer refatorar o código o primeiro passo deve ser identificar o code smell e qual ação quer tomar.

Digamos que você encontrou um método que tem muitos argumentos e quer pensar numa maneira de melhorá-lo. Você pode criar um novo objeto para armazenar esses dados e passar apenas esse objeto, outra opção seria quebrar o método e dividir a lógica e parâmetros, ou ainda mover alguns desses parâmetros para um construtor.

Existem várias opções ao refatorar um código então é muito importante entender o contexto do código e decidir o caminho antes de começar as modificações.

Em seguida vamos aplicar a refatoração, sempre lembrando de fazer mudanças pequenas e garantir que os testes passem. Se no meio de uma refatoração você encontrar uma oportunidade de corrigir um problema, anote em algum lugar e continue. Lembre-se que queremos diminuir a quantidade de mudanças.

Uma vez que a refatoração foi aplicada podemos criar um commit pois deixamos o sistema com todos os testes passando. Agora podemos voltar ao modo de corrigir bugs ou implementar funcionalidades.

Nos próximos posts vamos ver como Padrões de Projeto podem nos ajudar na hora de decidir o caminho das refatorações. Até lá.

Aprendendo TDD! Mais sobre dublês de teste

No post anterior vimos como os dublês de testes, mais conhecidos por mocks, podem nos ajudar a testar alguns cenários “impossíveis”. Vamos então entender como deixar nossos testes mais confiáveis, isolados e rápidos com outros tipos de dublês!

O artigo The Little Mocker, de Uncle Bob, é leitura recomendadíssima para aprender mais sobre os diferentes tipos de dublês.

Como exemplo vamos imaginar que queremos escrever testes que dependem da seguinte classe que é responsável por autorizar o pagamento de um cartão de crédito:

class ServicoValidacaoPagamento
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    # Lógica complexa para autorizar um pagamento
  end
end

Stub

Utilizamos um Stub quando não precisamos nos preocupar com a execução de um método, apenas com o resultado que ele retorna. Para criar um stub para o código anterior, poderíamos fazer algo como:

class ServicoValidacaoPagamentoStub
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    true
  end
end

Sempre que utilizarmos ServicoValidacaoPagamentoStub todos os pagamentos serão autorizados, assim para os nossos testes não precisamos nos preocupar com a sua lógica para retornar o que queremos.

Para testar que um pedido é finalizado quando o pagamento é validado poderíamos utilizar o stub assim:

it "marca pedido como finalizado quando pagamento eh aprovado" do
  pedido = Pedido.new(ServicoValidacaoPagamentoStub.new)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  pedido.estado.must_equal EstadoPedidos::FINALIZADO
end

Spy

Um Spy é utilizado quando queremos validar que métodos foram chamados, quantas vezes, com quais argumentos e quaisquer outras informações que nos interesse.

Supondo que agora queremos validar que, ao finalizar o pedido, estamos utilizando os dados corretos do cartão de crédito para validar o pagamento. Esse é um spy que poderíamos utilizar:

class ServicoValidacaoPagamentoSpy
  attr_reader :numero_cartao, :mes_expiracao, :ano_expiracao, :autorizacao_executada
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    @autorizaca_executada = true
    @numero_cartao = numero_cartao
    @mes_expiracao = mes_expiracao
    @ano_expiracao = ano_expiracao
    true
  end
end

Com o Spy, além de forçar o valor de retorno também estamos guardando informações se o método foi chamado e quais argumentos foram passados. O teste utilizaria o Spy da seguinte forma:

it "chama servico de validacao ao finalizar pagamento" do
  spy = ServicoValidacaoPagamentoSpy.new
  pedido = Pedido.new(spy)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  spy.autorizaca_executada.must_equal true
spy.numero_cartao.must_equal cartao_de_credito.numero
spy.mes_expiracao.must_equal cartao_de_credito.mes_expiracao
spy.ano_expiracao.must_equal cartao_de_credito.ano_expiracao
end

Utilizando um spy quando queremos validar informações relacionadas a como o método foi executado.

Mock

Assim como o Spy, o Mock também valida métodos que foram chamados. A diferença é bem sútil, um Mock sabe o que deve ser testado, ao contrário do Spy que apenas guarda informação da execução.

class ServicoValidacaoPagamentoMock
  attr_reader :autorizacao_executada
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    @autorizaca_executada = true
    true
  end

def verifica_execucao()
autorizacao_executada.true?
end
end 

Um mock possui a lógica de validação dentro dele, assim o teste fica mais simples. No entanto a lógica de verificação pode ser mais complexa do que apenas se o método foi executado, como mostra o exemplo. O teste ficaria assim:

it "chama servico de validacao ao finalizar pagamento" do
  mock = ServicoValidacaoPagamentoMock.new
  pedido = Pedido.new(mock)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  mock.verifica_execucao.must_equal true
end

Stub, Spy ou Mock?

A razão pelo qual Dublês de Testes são conhecidos como mocks é que a diferença entre eles é bem sutil e muitas vezes os frameworks vão empacotar todas essas funcionalidades e chamar de mock.

Na prática utilizamos na maioria das vezes stubs, para apenas substituir o comportamento de um método por seu valor fixo, ou um spy, para avaliar informação relacionadas a chamadas de método. Mas como os autores dos frameworks decidiram chamar tudo de mock ¯_(⌣̯̀⌣́)_/¯

Se quiser ir mais a fundo para entender a diferença entre eles, sugiro o artigo Mocks Aren’t Stubs de Martin Fowler que contém vários exemplos e mais detalhes.

Dummy

Um Dummy é utilizado para substituir um objeto quando você não precisa se importar como ele será utilizado. Geralmente ele é utilizado para melhorar a legibilidade, já que não possuem nenhum comportamento. Esse seria um Dummy para o serviço de autorização de pagamento:

class ServicoValidacaoPagamentoDummy
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
end
end

O Dummy recebe chamadas, mas não faz nada com elas. O principal objetivo é melhorar a legibilidade, já que sempre que criamos um pedido precisamos passar um serviço podemos utilizar o Dummy ao invés de um nil:

it "adicionar produtos ao pedido" do
  pedido = Pedido.new(ServicoValidacaoPagamentoDummy.new)
  pedido.adicionar_produtos(produtos)

pedido.quantidade_produtos.must_equal 1
end

Fake

Semelhante ao Dummy, o Fake é utilizado para substituir objetos no entanto ele possui uma determinada lógica que é será utilizada em diferentes cenários de teste. Por exemplo:

class ServicoValidacaoPagamentoFake
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    numero_cartao == "1234-1234-1234-1234"
  end
end 

Assim, podemos utilizar nos testes e simular a falha/sucesso do pagamento sem precisar mudar o dublê.

it "marca pedido como finalizado quando pagamento eh aprovado" do
  pedido = Pedido.new(ServicoValidacaoPagamentoFake.new)
pedido.adicionar_produtos(produtos)
pedido.forma_de_pagamento(cartao_de_credito_valido)
# cartao_de_credito_valido possui o numero esperado pelo Fake

pedido.finalizar # vai chamar o serviço para validar o pagamento

pedido.estado.must_equal EstadoPedidos::FINALIZADO
end

it "marca pedido como pendente quando pagamento nao eh aprovado" do pedido = Pedido.new(ServicoValidacaoPagamentoFake.new)
pedido.adicionar_produtos(produtos)
pedido.forma_de_pagamento(cartao_de_credito_invalido)
# cartao_de_credito_invalido possui outro numero

pedido.finalizar # vai chamar o serviço para validar o pagamento

pedido.estado.must_equal EstadoPedidos::PENDENTE
end

Dublês de Teste na vida real

Na maioria das vezes que utilizei dublês foi através de uma biblioteca que cria esses objetos. Apesar da facilidade que a biblioteca proporciona é importante entender o motivo da utilização do dublê e também saber que escrever seu próprio não é algo tão difícil assim.

Espero que tenha gostado desse post e que consiga utilizar dublês de teste com mais efetividade! Apesar de ser uma excelente ferramenta eles precisam ser utilizados com cuidado e bastante consciência.

Continue acompanhando a série com posts semanais Aprendendo TDD! Compartilhe com amigos e colegas que querem aprender. Se tem alguma sugestão, dúvida ou comentário use o espaço aqui do blog ou me procure no twitter em @marcosbrizeno. Até a próxima!

Aprendendo TDD! Utilizando Dublês de Teste

No posto anterior fizemos o famoso FizzBuzz utilizando os testes como guia e analisando as decisões de design tomadas ao longo do desenvolvimento. No entanto, no mundo real nem tudo consegue ser testado diretamente e são nesses casos que precisamos utilizar os dublês de teste!

O que são dublês de testes?

Precisamos alertar que a conta do usuário vai expirar caso o pagamento do último mês não tenha sido efetuado. Como adicionaríamos testes para o seguinte código?

class Usuario
  attr_reader :ultimo_pagamento
  UM_MES = 60*60*24*30

  def realizar_pagemento()
    @ultimo_pagamento = Time.now
  end

  def deve_expirar_conta()
    @ultimo_pagamento <= Time.now - (UM_MES)
  end
end

Obviamente não conseguimos fazer o pagamento e esperar um mês para verificar se a conta foi expirada, então precisamos utilizar um dublê de teste para fingir a passagem do tempo.

Dublês de testes são mais conhecidos como mocks, mas existem outros tipos. No artigo The Little Mocker, Uncle Bob explica de maneira bem fácil de acompanhar quais são os vários tipos de dublês, incluindo o mock, e quando é o melhor momento para utilizar cada um deles. No próximo post vamos entrar em mais detalhes sobre os diferentes dublês.

Para o código anterior nós vamos utilizar um stub, que permite alterar o comportamento de um método para ignorar sua lógica e retornar um valor específico. O que é exatamente o que precisamos para testar o código anterior, ao invés de Time.now retornar o tempo atual, vamos retornar uma data que permita exercitar o código.

Testes com dublês

A sintaxe para criar stubs e outros dublês muda um pouco de biblioteca para biblioteca, no caso do MiniTest, definimos o método que desejamos substituir o comportamento, o valor que deve ser retornado e o bloco de código onde aquele stub será válido.

require 'minitest/autorun'
require 'minitest/spec'
require_relative '../lib/usuario.rb'

describe Usuario do
  UM_MES =(60*60*24*30)
  it "expira conta quando pagamento nao foi feito" do
    usuario = Usuario.new
Time.stub(:now, Time.now() - UM_MES) do
      usuario.realizar_pagemento()
    end

usuario.deve_expirar_conta.must_equal true
  end
end

Além do tempo existem outras situações onde não queremos utilizar a implementação real dos métodos como por exemplo, chamadas de serviços externos pela rede, leitura de arquivos locais ou até mesmo chamadas ao banco de dados.

A principal vantagem que eu vejo de utilizar dublês ao fazer TDD é que eu consigo isolar melhor os meus testes e não preciso me preocupar muito com a maneira como outros objetos funcionam. Assim posso focar apenas no design do código que estou desenvolvendo e passar pelo ciclo do TDD mais facilmente. No entanto é preciso tomar cuidado para não utilizá-los de maneira errada!

Quando não utilizar

No artigo Mockar ou não mockar, eis a questão,  Fabio Pereira explora bem os malefícios de utilizar muitos dublês de testes no seu código.

Uma das guerras sagradas no desenvolvimento de software é definir o que é um teste unitário, pois algumas pessoas restrigem ao ponto de utilizar dublês para quaisquer outros objetos, isolando a classe que está sendo testada, outros sugerem que até mesmo chamadas ao banco de dados podem ser feitas em testes unitário.

Independente da sua definição de teste unitário, ao utilizarmos um dublê estamos ignorando toda a lógica implementada naquele método/classe em favor de isolar o teste. Caso essa lógica mude o teste não vai saber e é aí onde mora o perigo.

Devido a maneira como ruby lida com objetos, é possível que o método nem sequer exista mas que ainda assim o teste funcione. O código a seguir utiliza um mock para criar um objeto e simular o seu comportamento, mas a asserção é feita sobre o mock.

it "mata um panda" do
  usuario = MiniTest::Mock.new
  usuario.expect :metodo_inexistente, true

  usuario.metodo_inexistente.must_equal true
end

Utilizar dublês é necessário e tem muitos benefícios, como vimos no exemplo de testes com o tempo. No entanto é preciso ficar atento pois podemos utilizar tantos dublês que no final das contas o teste não testa nada, como nesse exemplo anterior.

No próximo post vamos explorar mais sobre dublês de testes e situações onde podemos utilizá-los para nossos testes.

Compartilhe os posts da série Aprendendo TDD! com seus amigos e colegas que também estão buscando aprender mais sobre essa técnica. E acompanhe o blog pois toda semana tem post novo! Dúvidas, sugestões e comentário podem ser feitas aqui no blog ou no twitter em @marcosbrizeno.