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!

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.

[Refatorando Tudo!] Não use parâmetros como retorno

Sabe quando você precisa encontrar aquele bug demoníaco e não faz a menor ideia de onde ele está? Ai você começa a debugar o código e põe break point em tudo que é lugar pra tentar entender o que está acontecendo. A pior coisa que você pode encontrar são métodos com efeitos colaterais.

Dano Colateral

Imagine que vamos processar a criação de um pedido de compras. Para isso precisamos calcular o valor do frete e verificar se o usuário fazendo a compra faz parte do programa premium da empresa para marcar o produto como entrega expressa. O código atual parece algo assim:

class PedidoController
  def criar_pedido
    pedido = Pedido.criar_pedido_do_carrinho
    usuario = Usuario.buscar_por_id(params.id_usuario)
    valor_frete = calcular_frete(pedido, usuario)
  end
# ...
  def calcular_frete(pedido, usuario)
    frete = pedido.valor_total > VALOR_COM_FRETE_GRATIS ?
              0 : pedido.valor_total * 0.10
    pedido.entrega_expressa = usuario.premium? && frete == 0
    transportadoras.enviar_aviso_entrega_expressa(pedido) if pedido.entrega_expressa?
    pedido.remover_produtos_estoque
    frete
  end

Vamos entender o que está acontecendo no método calcular_frete:

Primeiro o valor do frete é definido baseado no preço do pedido.

Depois marcamos o produto como entrega expressa e enviamos essa informação para as transportadoras, dependendo do valor do frete calculado antes.

Por fim os produtos do pedido são removidos do estoque e o valor do frete é retornado.

Bom, só pela explicação já deu pra perceber que esse método faz coisa demais né? Mas o principal problema, que provavelmente vai nos pegar no futuro são as alterações no objeto produto que acontecem dentro de um método chamado calcular_frete.

Imagine que a lógica para marcar o pedido como entrega expressa está errada e você precisa corrigi-lá. Não é intuitivo achar que no método calcular_frete o pedido é marcado como entrega expressa. Esse é o principal problema de métodos com efeitos colaterais.

Identificando qual anti-padrão estamos lidando

A regra mais óbvia para se seguir é:

Não altere objetos que são passados como parâmetro

Mas, uma vez que estamos nessa situação, como sair dela? Para isso precisamos entender o motivo de o método está alterando os parâmetros. Aqui vão algumas possibilidades:

Violando o Princípio da Responsabilidade Única

Se você não conhece o Princípio da Responsabilidade Única (Single Responsibility Principle), veja esse outro post que tem mais detalhes.

Como falado anteriormente, apesar de ser um exemplo simples, só pela explicação já fica claro a quantidade de coisas que o método calcular_frete faz é grande demais para um método só.

No entanto eu acho difícil seguir esse princípio pois definir o que é uma responsabilidade é muito subjetivo. Processar pagamento é uma responsabilidade? Ou será que buscar os produtos de um pedido deveria ser uma responsabilidade separada?

Tentando dividir as responsabilidades, podemos ter o seguinte cenário:

class PedidoController
  def criar_pedido
    pedido = Pedido.criar_pedido_do_carrinho
    usuario = Usuario.buscar_por_id(params.id_usuario)
    valor_frete = calcular_frete(pedido, usuario)
    marcar_pedido_expresso(pedido, usuario, valor_frete)
    enviar_aviso_entrega_expressa(pedido)
    pedido.remover_produtos_estoque
  end

  def calcular_frete(pedido, usuario)
    pedido.valor_total > VALOR_COM_FRETE_GRATIS ?
              0 : pedido.valor_total * 0.10
  end

  def marcar_pedido_expresso(pedido, usuario, frete)
    pedido.entrega_expressa = usuario.premium? && frete == 0
  end

  def enviar_aviso_entrega_expresa(pedido)
    transportadoras.enviar_aviso_entrega_expressa(pedido) if pedido.entrega_expressa?
  end
end

As responsabilidades estão bem separadas agora, mas será que realmente melhoramos o design da aplicação? Olhe o tamanho do método criar_pedido agora, ainda continuamos alterando um parâmetro no método marcar_pedido_expresso e será que esses outros métodos realmente fazem parte do PedidoController?

Ter o princípio em mente é uma boa maneira de guiar a refatoração, mas na prática vale mais a pena combiná-lo com outros princípios para decidir como refatorar o código.

Objetos com Inveja

O code smell Feature Envy mostra uma situações onde, para implementar certa funcionalidade, um objeto precisa consulta muito mais outros objetos do que a si mesmo.

Uma maneira bem fácil de perceber essa situação é olhar o método sem focar em uma linha específica. Volte ao código anterior e tente olhar todo o corpo do método buscando padrões e repetições.

Fica bem claro ver a quantidade de chamadas ao objeto pedido, seja para ler informações dele, passá-lo como parâmetro ou até mesmo modificá-lo. Esse é um sinal de que o PedidoController está com inveja das funcionalidades de Pedido.

Podemos refatorar o código movendo essas responsabilidades de PedidoController para Pedido:

class PedidoController
  def criar_pedido
    pedido = Pedido.criar_pedido_do_carrinho
    usuario = Usuario.buscar_por_id(params.id_usuario)
    valor_frete = calcular_frete(pedido, usuario)
    pedido.marcar_como_entrega_expressa(usuario, valor_frete)
    enviar_aviso_entrega_expressa(pedido)
    pedido.remover_produtos_estoque
  end

  def enviar_aviso_entrega_expresa(pedido)
    transportadoras.enviar_aviso_entrega_expressa(pedido) if pedido.entrega_expressa?
  end
end

class Pedido
  def marcar_como_entrega_expressa(usuario, frete)
    self.entrega_expressa = usuario.premium? && frete == 0
  end
end

Agora não temos mais o efeito colateral de marcar o pedido como entrega expressa em um lugar onde não fazia sentido. Caso essa lógica precise mudar ela está concentrada em um método dentro da classe Pedido.

Violando o Diga, não Pergunte!

No post anterior vimos o princípio Diga, não Pergunte, que tenta evitar que o comportamento dos objetos fique separado das informações.

Se o código consulta as informações de um objeto para decidir o que fazer com ele, é sinal de que o princípio está sendo violado. Podemos mover então a responsabilidade de falar com as transportadoras para a classe pedido, já que ela possui a informação necessária:

class PedidoController
  def criar_pedido
    pedido = Pedido.criar_pedido_do_carrinho
    usuario = Usuario.buscar_por_id(params.id_usuario)
    valor_frete = calcular_frete(pedido, usuario)
    pedido.marcar_como_entrega_expressa(usuario, valor_frete)
    pedido.enviar_aviso_entrega_expressa(transportadoras)
    pedido.remover_produtos_estoque
  end
end

class Pedido
  def marcar_como_entrega_expressa(usuario, frete)
    self.entrega_expressa = usuario.premium? && frete == 0
  end

  def enviar_aviso_entrega_expresa(transportadoras)
    transportadoras.enviar_aviso_entrega_expressa(self) if entrega_expressa?
  end
end

Mesmo após todas essas refatorações ainda continuamos com o método criar_pedido tendo muitas responsabilidades e acionando bastante o objeto pedido, apesar de que agora ele não o modifica diretamente.

Como poderíamos melhorar o design desse código? Pense um pouco em como você melhoraria esse código e deixa as suas ideias nos comentários abaixo.

Compartilhe o texto com seus colegas para ajudá-los a melhorar o código deles também e continue acompanhando os posts da série [Refatorando tudo!] para melhorar ainda mais seu código!

Deixe seus comentário aqui no post ou fale direto comigo no twitter em @marcosbrizeno!

 

 

[Refatorando Tudo!] Diga, não pergunte!

Esse são objetos burros, não devem ter nenhuma lógica

Ah… quantas vezes eu já ouvi isso… Geralmente esse argumento é reforçado por termos como beans ou POJOs (ou o equivalente da linguagem que você utiliza).

Voltando um pouco nos conceitos de Orientação a Objetos, a ideia básica é esconder os dados e expor comportamento. Assim, os objetos conseguem se comunicar e manter seu estado interno, o que facilitaria a manutenção do código.

Então ter “objetos burros” não só vai totalmente contra os princípios da orientação a objetos como também empurra lógica para classes onde elas não fazem sentido.

Dados andam junto com a lógica que precisa deles

Para exemplificar essa situação vamos exibir os detalhes de um pedido, como valor total, forma de pagamento utilizada, endereço de entrega etc. A informação de um pedido vem de um serviço, que retorna um JSON com todos os dados.

Utilizando um biblioteca que para mapear JSON para um objeto, como Jackson por exemplo, criamos a seguinte classe:
(para reduzir a quantidade de código focamos apenas no método de pagamento utilizado na compra)

public class Pedido {
// Algumas propriedades...
  @JsonProperty("metodo_pagamento")
  private MetodoDePagamento metodoDePagamento;
// Outras propriedades...
}

public class MetodoDePagamento {
// Algumas propriedades...
  @JsonProperty("cartao")
  private CartaoDeCredito cartaoDeCredito;
}

public class CartaoDeCredito {
  @JsonProperty("bandeira")
  private String bandeira;

  @JsonProperty("numero")
  private String numero;
}

Perfeito, agora para exibir as informações do cartão de créditos nós precisamos esconder uma parte dos dados e exibir apenas os 4 primeiros e 4 últimos dígitos.

Não vamos simplesmente adicionar essa lógica na view, certo? Mas o objeto que faz o mapeamento do JSON deve ser um “objeto burro” então a lógica também não pode ficar lá, apesar de fazer todo sentido. Então surgem os “utils”.

public class CartaoDeCreditoUtil {
  public static String formatarNumero(String numeroCartaoDeCredito) {
    return numeroCartaoDeCredito.substring(0, 3) +
              "-xxxx-xxxx-" + numeroCartaoDeCredito.substring(11);
  }
}

A chamada desse código lá na view seria algo assim:

Pedido pedido = servicoPedido.buscaDetalhes(numeroDoPedido)
//...
CartaoDeCreditoUtil.formatarNumero(
  pedido.getMetodoDePagamento().getCartaoDeCredito().getNumero())

Em um time pequeno pode até ser que todos saibam que devem utilizar essa classe para formatar o número do cartão de crédito. Mas imagine isso em um software que é mantido por anos, qual a probabilidade de alguém que chegou agora para dar manutenção saber que a lógica para formatar o cartão de crédito não está junto com o número do cartão? Qual a probabilidade dessa pessoa criar outra classe para manter essa lógica?

Diga ao objeto o que fazer

O exemplo acima é bem simples, mas já mostra os problemas de separar os dados do comportamento de um objeto. Mas e agora, como sair dessa situação?

O primeiro passo é encontrar a fonte de dado para os famigerados métodos da class “util”. Dependendo do seu contexto a refatoração pode ser mais complicada. No exemplo acima podemos simplesmente mover o método para dentro da classe CartaoDeCredito e criar um método getNumeroFormatado:

public class CartaoDeCredito {
// código anterior
  public String getNumeroFormatado() {
    return numero.substring(0, 3) + "-xxxx-xxxx-" + numero.substring(11);
  }
}

No entanto caso o método util dependa de dados que venham de objetos diferentes, pode ficar mais complicado decidir qual deles deve assumir a responsabilidade.

Outra situação complicada é se, apesar de ser um mesmo dado, eles vierem de lugares diferentes. Considerando o exemplo anterior, imagine que mais de uma classe tem um número de cartão de crédito, onde colocar essa lógica?

A opção mais óbvia é duplicar o código, embora ela também vá causar mais problemas. Se a sua linguagem possui alguma forma de mixin, como traits em Scala ou modules em Ruby, é possível extrair esse comportamento e adicioná-lo aonde for necessário.

Uma outra opção também é fazer com esse número de cartão de crédito seja mais do que apenas uma simples String, então poderemos adicionar o comportamento nele e utilizá-lo aonde for necessário lidar com o número do cartão.

Se você quer ir mais afundo no assunto, sugiro o post TellDontAsk de Martin Fowler e o Tell, Don’t Ask da Prag Prog. Esse último artigo vai um pouco além e também fala de outros princípios, que serão detalhados aqui também em outros posts, a Lei de Demeter e a separação entre Comando e Busca (Command/Query).

Compartilhe o texto com seus colegas para ajudá-los a melhorar o código deles também e continue acompanhando os posts da série [Refatorando tudo!] para melhorar ainda mais seu código!

Deixe seus comentário aqui no post ou fale direto comigo no twitter em @marcosbrizeno!

[Refatorando Tudo!] Representando nada com o Padrão de Projeto Null OBject

No post anterior nos falamos sobre a importância de não retornar nulos nas funções. Uma maneira de evitar esse problema é utilizando um objeto válido, mas que não possui nenhuma informação. Nesse post vamos entrar mais em detalhes sobre o Padrão Null Object.

Nada é alguma coisa

No post anterior já dei essa dica, mas pra quem perdeu, essa apresentação da Sandi Metz Nothing is Something é muito boa e explica as implicações de design de retornar nulo.

A ideia básica do padrão é criar um objeto que é válido, ou seja não vai lançar nenhuma exceção ou causar problemas, mas que não possui nenhuma informação.

class Pedido
  attr_reader :total, :imposto, :numero
  def initialize(total, imposto, numero)
    @total, @imposto, @numero = total, imposto, numero
  end
end

class PedidoInvalido < Pedido
  def initialize
    super(0, 0, 0)
  end
end

Mas qual a vantagem de retornar um novo objeto ao invés de retornar nulo, que já existe?

Se você programa em uma linguagem Orientada a Objetos, provavelmente conhece o conceito de que objetos trocam mensagens entre si. A principal vantagem de criar um objeto invalido é que ele segue a mesma interface do objeto original, enquanto um nulo não conhece nada sobre o objeto original. Assim podemos continuar enviando a mesma mensagem, sem se preocupar se o objeto vai entender ou não.

Então mesmo que pareça fazer sentido utilizar um valor nulo para representar a falta de algum dado, estamos obrigando quem utiliza nossa função a prover um comportamento extra. Em algum lugar do código eventualmente vamos precisar fazer algo assim:

pedidos = Pedido.obter_historico_de_pedidos(usuario)
valor_total = pedidos.reduce(0) do |total, pedido|
  total += pedido.nil? ? 0 : pedido.total
end

Aquele nil? no meio da sua lógica tende a se espalhar em todo lugar que precisa de um pedido. Então, a menos que o método obter_historico_de_pedidos retorne um objeto nulo, quando não houver um pedido, toda parte do código vai precisar lidar com a checagem de nulo.

Apesar de parecer um trabalho grande criar um novo objeto só para representar um valor inválido, a manutenção fica fácil pois tudo fica concentrado em um lugar só. E utilizar um objeto é bem mais fácil do que utilizar a lógica toda.

Cada caso é um caso

O que o objeto nulo vai fazer depende muito da sua aplicação. No post anterior discutimos quando faz mais sentido retornar um objeto nulo ou lançar uma exceção, se você ainda não viu, corre lá pra evitar utilizar o padrão em um cenário onde ele não faz sentido.

Em alguns contexto o objeto nulo precisa ser mais complexo, como por exemplo quando existem outro objetos aninhados e eventualmente precisamos criar objetos nulos para eles também.

No entanto o objeto nulo também pode ser mais simples, digamos que você tenha uma lista de objetos, basta criar um lista vazia para garantir que o objeto será válido mas sem nenhum dado.

Compartilhe o texto com seus colegas para ajudá-los a melhorar o código deles também e continue acompanhando os posts da série [Refatorando tudo!] para melhorar ainda mais seu código!

Deixe seus comentário aqui no post ou fale direto comigo no twitter em @marcosbrizeno!

Gotas de Elixir – Pattern Matching em argumentos

Na série de posts Gotas de Elixir, vamos apresentar algumas características que tornam Elixir uma linguagem funcional bastante divertida de se aprender. Para mais informações sobre a linguagem visite a página oficial: elixir-lang.org

Na maioria das linguagens, o operador “=” é usado para atribuir valores para variáveis, em Elixir ele é utilizado para Pattern Matching.

Em uma olhada rápida, atribuir valores a uma variável é bem similar a outras linguagens, mas por baixo dos panos outras coisas acontecem.

a = 1

Fazendo o match de uma variável com o número 1, ligamos o valor para a variável. Podemos também tentar fazer o match de um número para uma variável que já tenha um valor atribuído:

a = 1
1 = a

Atenção, variáveis só podem ser atribuídas se estiverem no lado esquerdo do operador! Caso contrário, uma exceção será lançada pois o compilador vai entender que a “variável” é na verdade uma função:

1 = b
** (CompileError) iex:1: undefined function b/0

Caso não seja possível fazer o matching do lado direito com o lado esquerdo, uma exceção diferente será lançada:

a = 1
2 = a
** (MatchError) no match of right hand side value: 1

Agora vem a parte realmente legal, ao chamar uma função em Elixir o compilador tenta fazer o matching entre os valores recebidos e os parâmetros declarados na função. Assim, podemos evitar fazer comparações dentro do código e tratar casos diferentes em funções separadas.

O exemplo clássico é o cálculo do somatório da sequência de Fibonnaci. A ideia aqui é, dado um número inteiro e positivo, calcular a soma de todos os números seguindo a sequência de Fibonacci.

Em ruby, uma implementação poderia ser, dado um número n, validar se o número passado como parâmetro é 0 ou 1, retornando 0 e 1 respectivamente. Caso contrário, vamos somar o resultado da sequência de fibonacci de  n-1 e n-2:

def fibonacci(n)
  return n if n == 0 || n == 1
  fibonacci(n-1) + fibonacci(n-2)
end

Em elixir, podemos tirar vantagem do Pattern Matching em argumentos de funções e separar os comportamentos em funções diferentes. Para tratar os casos onde n é 0 ou 1, criamos as seguintes funções:

defmodule Fibonacci do
  def calcular_soma(0) do 0 end
  def calcular_soma(1) do 1 end
end

Em elixir podemos ter múltiplas definições de funções, com mesmo nome mas com regras de comparação de argumentos diferentes. Ao chamar a função Fibonacci.calcular_soma os valores passados como argumento serão verificados com as possíveis definições, assim não precisamos utilizar os ifs como na definição em ruby.

O último passo é implementar a função para outros valores de n. Basta adicionar uma nova definição da função que tem uma variável como parâmetro e em seguida seguir a lógica de somar a sequência de n-1 e n-2:

defmodule Fibonacci do
  def calcular_soma(0) do 0 end
  def calcular_soma(1) do 1 end
  def calcular_soma(n) do
    calcular_soma(n-1) + calcular_soma(n-2)
  end
end

Semelhante a ruby, funções retornam o valor da última linha, mesmo que não seja utilizado o “return”.

Outro ponto importante é notar que a ordem de declaração das funções dentro do módulo faz diferença. As definições específicas devem vir primeiro, pois ao executar Fibonacci.calcular_soma(1) o compilador deve encontrar primeiro a definição que recebe 1 como argumento, ao invés da genérica que recebe uma variável com valor qualquer.

O que aconteceria caso a ordem de definição das funções fosse a seguinte?

defmodule Fibonacci do
  def calcular_soma(n) do
    calcular_soma(n-1) + calcular_soma(n-2)
  end
  def calcular_soma(0) do 0 end
  def calcular_soma(1) do 1 end
end

Todas as chamadas à Fibonacci.calcular_soma iriam entrar em um loop infinito, pois ao tentar fazer o Pattern Matching com a primeiro definição da função, qualquer valor será compatível.

E ai, gostou de conhecer um pouco sobre Elixir? Nos próximos posts vamos mostrar como lidar com conjuntos pode ser bem fácil e divertido com Elixir!