cond
Diferente de case/2 que verificamos o valor e escolhemos o caso especifico para ela, cond/1 verificamos a condição, como feito no if/2 com o adicional de termos várias condições alinhadas onde retornamos a primeira que for verdadeira.
A definição dele segue a palavra chave cond seguido de do. Todo o controle de fluxo é tido dentro dele, exemplo:
x = 3 # declaração de váriavel que será usada no cond
cond do
x == 5 ->
"Isso não é verdadeiro" # não entrará nesse escopo
(x + 8) > 10 ->
"Nem isso" # nem nesse
x == 3 ->
"Mas isso será" # Essa condição é verdadeira pois x é igual a 3 então esse bloco será executado.
true ->
"Nunca virá aqui, pois a clausura anterior é verdadeira"
endVamos a um exemplo mais útil. Você é um cientista em uma grande empresa farmacêutico. Temos vários nÃveis de acesso que vão de 1 a 3, sendo 1 o nÃvel de visitantes e 3 o nÃvel de acesso a armas biológicas.
Precisamos então dar permissão a cada área de acordo com seu nÃvel de acesso.
Lembrando que estamos falando de autorização e não autenticação.
Nossos critérios de aceite serão:
Quando usuário tiver acesso 1, exibir mensagem "Bem vindo {nome}, sinta-se à vontade em nossas estruturas!"
Quando usuário tiver acesso 2, mostrar ultima pendência no setor da saúde junto com o nome do usuário "Bem vindo João, o Ibuprofeno x42 esta nas fases finais"
Quando o usuário tiver acesso 3, mostrar ultimo status do nemesis. "Sr. Iago, já mandamos nemesis para Raccoon city"
Vamos começar criando nosso teste:
Recebemos um relatório dizendo que Authorization.access não está disponÃvel. Claramente, por que não possuÃmos esse modulo. Criaremos ele
Criamos o modulo de Authorization com uma função chamada access/1 que contém um variável _user. Se rodarmos o teste conseguiremos outro erro.
Recebemos agora outro erro, dizendo que precisamos de um texto, mas o que recebemos foi um nil. Isso por que não estamos retornando nada da função chamada. Vamos fazer o mÃnimo para passar esse teste, retornaremos da função, o texto completo.
Se rodarmos agora, os testes irão passar.
Uma vez os testes passando podemos refatorar. O primeiro passo, é deixa-lo dinamico. Vou adicionar uma nova validação junto no mesmo teste, apenas para comprovar que está correto. Vamos alterar nosso teste:
Se rodarmos o teste assim ele acusará um erro
Isso por que, em nossa implementação não utilizamos a variável que mandamos por parâmetro. Logo, ela está fixado como Paulo. Vamos arrumar isso.
Removemos o underline (_) de user e utilizamos na interpolação da linha 3. Se rodarmos agora esse teste, teremos sucesso.
Agora possuÃmos apenas mais um ponto para ver. O que acontece caso mandamos um map incorreto para dentro da função access? Por exemplo Authorization.access("lalala").
Vamos validar isso no terminal iterativo do elixir. Para isso queremos que esse projeto que estamos mexendo compile junto para que possamos usar o modulo Authorization. Para isso, basta executar na pasta raiz de seu projeto iex -S mix e o terminal iterativo será aberto:
Caso não saiba o que é isso, vá para a sessão iEX do livro.
Vamos executar a função Authorization.access/1 utilizando como parâmetro algo incorreto, como "lalala"
Isso acontece porque na função access/1 tentamos acessar um elemento chamado name dentro do map user. Já que user agora é "lalala", uma string, ele não possui esse elemento. Ele nem é um map, logo, não temos como acessa-lo. Temos várias formas de resolver esse problema.
Primeira coisa que iremos fazer é criar um teste para entender o que precisamos que seja retornado caso seja invalido. Criaremos um teste chamado invalid_params e adicionaremos o cenário de falha ali.
Criamos um teste simples para garantir o comportamento. Se você analisar o código, verá que agora adicionamos a convenção de :ok/:error para conseguirmos facilmente identificar quando algo da certo e quando algo da errado.
Se rodarmos o teste, vamos ver que conseguimos reproduzir o erro no teste, o que é um ótimo sinal.
Agora precisamos lidar com ele. Para isso, utilizaremos a validação de tipo usando uma estrutura que ja possuÃmos no projeto. A estrutura User. Nela já possuÃmos o name.
Caso você não a tenha por algum motivo, vou colocar aqui para facilitar
Tendo a estrutura, precisamos garantir que nossa função access aceite receber apenas ela. Vamos atualizar nosso código.
Se rodar novamente o erro, teremos um relatório diferente
Recebemos dois erros, vamos focar no primeiro agora. Já que nossa função access permite apenas receber a estrutura User agora, precisamos alterar nossos dados de testes para enviar o User para access.
Recebemos um outro erro, dizendo que a key :level not found. Quando utilizamos structure, precisamos utilizar apenas a estrutura de dados criada nele. Em user não possuÃmos um campo :level e isso faz com que a compilação falhe.
A utilização de estrutura engessa os dados nele, garantindo que a estrutura seja sempre a mesma. Isso é um bom ganho quando tratamos com dados.
Precisamos adicionar :level em User. É uma simples mudança
Para garantir consistência e segurança em nosso projeto, podemos configurar que, caso não passado o level, ele será automaticamente 1, que significa que ele é visitante.
Podemos agora rodar os testes
Resolvemos o primeiro erro. Agora vamos para o proximo tratamento. Esse aqui esta falando que "lalala" não é uma estrutura User e ele esta certo. Agora precisamos fazer com que o erro seja retornado. Já possuÃmos o texto que precisamos no teste criado em authorization_test.exs.
Se você analisar, estamos usando pattern matching no parâmetro da função e devido não ter sido correspondido ele não entrou na primeira função access. Podemos então criar uma nova função de mesmo nome, fazer dar matching e então retornar a mensagem de erro. Vamos lá
Se rodarmos o teste agora, teremos sucesso.
Tudo finalizado no primeiro caso, porém, temos um inconsistência. Quando temos sucesso, recebemos a message, mas quando temos um erro temos uma estrutura diferente {:error, reason}. Devemos deixar elas similares e como estamos usando o padrão de tuplas de ok/error, devemos alterar a resposta de sucesso para recebermos {:ok, result}. Mudança simples, apenas alterar primeiro os testes e depois a implementação.
Feito isso, so rodar os testes para garantir que esta tudo certo.
Uma pausa rápida
Sei que não vimos nada sobre cond/0 até então, mas isso foi um bom exercÃcio para pensar em como desenvolver em elixir. Espero que me perdoem por essa volta, mas as vezes o atalho tem mais perigos que a longa jornada.
Seguimos
Conseguimos finalizar o primeiro ponto, vamos seguir para o ponto dois.
Quando usuário tiver acesso 1, exibir mensagem "Bem vindo {nome}, sinta-seà vontadeem nossas estruturas!"Quando usuário tiver acesso 2, mostrar ultima pendência no setor da saúde junto com o nome do usuário "Bem vindo João, o Ibuprofeno x42 esta nas fases finais"
Quando o usuário tiver acesso 3, mostrar ultimo status do nemesis. "Sr. Iago, já mandamos nemesis para Raccoon city"
Vamos agora para o nÃvel 2, a autorização de um funcionário. Vamos agora criar um teste para lidar com esse cenário.
O teste agora quer garantir que usuários com nÃvel 2 de acesso, tenham informação sobre seu dia de trabalho, pois nÃvel dois se da a funcionários. Se rodarmos esse teste, teremos um relatório de erro.
A mensagem que esperavamos não foi entregue. Isso devido a função access so esta implementado para devolver uma mensagem de primeiro nÃvel. Vamos alterar utilizando cond/0 para tomar a decisão de fluxo que queremos.
Vamos alterar nosso código
Adicionamos a condicional cond/0 onde ela lida com o level de acesso do usuário. Percebe como fica simples adicionar novos desvios de fluxo?
Simples assim, fechamos mais um critério de aceite.
Quando usuário tiver acesso 1, exibir mensagem "Bem vindo {nome}, sinta-seà vontadeem nossas estruturas!"Quando usuário tiver acesso 2, mostrar ultima pendência no setor da saúde junto com o nome do usuário"Bem vindo João, o Ibuprofeno x42 esta nas fases finais"Quando o usuário tiver acesso 3, mostrar ultimo status do nemesis. "Sr. Iago, já mandamos nemesis para Raccoon city"
Espero que tenha feito o desafio, essas ações que fazem internalizarmos o conteúdo. Não irei implementar ele. Esse é o desafio.
Existe apenas mais uma coisa a se fazer. Vamos dizer que eu coloque o nÃvel de acesso 10. O que aconteceria? Vamos abrir novamente nosso terminal iterativo iex -S mix e executaremos a chamada da função usando o level 10.
Recebemos um relatório de erro. A parte importante é no cond clause evaluated to a truthy value. Nenhuma condição foi saciada no cond, sem opção do que fazer, ele quebra. Temos que ter pelo menos uma condição que verdadeira para não causar problemas a nos. Fora que precisamos avisar ao usuário que é melhor ele se retirar antes que chame a segurança. Vamos primeiro criar nosso teste.
Execute esse código e verá que conseguimos reproduzir o problema que enfrentamos no terminal iterativo.
Precisamos cuidar disso agora, mas seremos defensivos. Ao invez de fazer alguma regra, deixaremos uma clausura true no final de cond, onde, se nenhuma clausura for saciada, a ultima sera e uma mensagem de erro aparecerá.
Adiconamos uma condição que será sempre true, caso nenhuma condição acima seja true, ele caira nessa última e a mensagem de erro vai ser gerada. Uma solução simples, que funciona bem para nós aqui.
Conclusão
Nesse capÃtulo, seguimos um fluxo de desenvolvimento comum em elixir, onde criar e refatoramos uma implementação. Também entendemos a utilidade da condicional cond/0 e fizemos um desafio. Lembrem-se, sempre existe outras formas de atender a uma expectativa, podendo muitas vezes, utilizar os mesmos recursos. Tente e re-tente criar de formas diferentes para conseguir extrair os melhores resultados.
Last updated
Was this helpful?