Home AboutRSS

Elixir - Usando Pattern Matching para melhorar seu código

Pattern Matching é o tipo de recurso que se leva um tempo para aprender e perceber o seu real valor. Felizmente temos vários materiais e artigos que nos ajudam nesta tarefa. Se você não tem ideia do que é Pattern Matching recomendo ler os links antes de continuar.

Neste artigo vou mostrar uma forma de usar Pattern Matching que me fez perceber como este recurso é poderoso e elegante. Vamos começar com um exemplo de código simples.

Quem nunca escreveu um código assim ?

cliente = Cliente.new(id: 1000, cartao_fidelidade: true, email: 'cliente@pizzaria.com')
desconto_pedido = desconto(produto: pizza, cliente: cliente)

# Aplica 5% de desconto no valor do produto caso o cliente possua o cartão fidelidade.
# Caso não possua, envia email de marketing para oferecer o cartão fidelidade.
# Retorna o valor calculado do desconto
def desconto(produto:, cliente:)
    if cliente.cartao_fidelidade
        desconto = calcular_valor_desconto(produto, 5)
    else
        desconto = 0
        if cliente.email
            enviar_marketing_fidelidade(cliente.email)
        end
    end
    desconto
end

À primeira vista é um código inofensivo, mas este projeto irá crescer e o excesso de condicionais pode se tornar um problema na manutenção e evolução da base de código. Imagine que no futuro o sistema precise calcular desconto no primeiro pedido do cliente, ou que além de email deve enviar SMS, ou ainda aplicar desconto progressivo no desconto… acho que ficou claro onde mora o perigo.

Você já sabe que Pattern Matching, como o próprio nome diz, serve para casar padrões. Então como podemos reescrever nosso método desconto para que fique mais legível e compreensível ? Pattern Matching!

cliente = %{id: 1000, cartao_fidelidade: true, email: 'cliente@pizzaria.com'}
pedido = desconto(pizza, cliente)

def desconto(produto, %{cartao_fidelidade: false, email: nil}) do
    0
end

def desconto(produto, %{cartao_fidelidade: false, email: email_cliente}) do
    enviar_marketing_fidelidade(email_cliente)
    0
end

def desconto(produto, %{cartao_fidelidade: true}) do
    calcular_valor_desconto(produto, 5)
end

O map cliente possui as chaves cartao_fidelidade e email, que serão usadas para casar com os parâmetros de cada função:

# Qual das funções casa/combina com este cliente ?
cliente = %{cartao_fidelidade: false, email: nil}

A grande diferença entre as duas versões é que usando Pattern Matching seu código se torna explícito adotando lógicas isoladas para cada condição, este é um efeito colateral típico de linguagens funcionais.

No primeiro caso a complexidade é maior pois temos que lidar com o estado dos atributos do objeto cliente, porém o que importa para o processamento da lógica são os dados, e eles devem ser explícitos.

elixir functional-programming