DEV Community

Cover image for Por que usar @typespecs em seu código Elixir?
Rômulo Silva
Rômulo Silva

Posted on • Edited on

Por que usar @typespecs em seu código Elixir?

Elixir é uma linguagem dinâmica e concisa, mas nem sempre é fácil garantir a segurança e a legibilidade do código. Felizmente, Elixir oferece uma ferramenta poderosa para ajudar a resolver esses problemas: os @typespecs.

Os @typespecs são anotações de tipo opcionais que podem ser adicionadas a funções e módulos em Elixir. Eles permitem especificar os tipos de argumentos e valores de retorno de uma função, tornando mais fácil garantir que seu código esteja correto e fácil de entender.

Neste artigo, discutiremos o que são os @typespecs, especificações e tipos, suas vantagens, desvantagens, e como eles podem ser usados para tornar o seu código Elixir mais seguro e legível.

O que são @typespecs?

Como dito anteriormente, os @typespecs são anotações de tipo opcionais que podem ser adicionadas a funções e módulos em Elixir e usamos ​​para especificar os tipos de argumentos e valores de retorno de uma função.

Os @typespecs são escritos como comentários acima da função ou módulo que eles descrevem. Por exemplo, a especificação de tipo para uma função que adiciona dois números inteiros seria a seguinte:

@spec add(integer, integer) :: integer def add(x, y), do: x + y 
Enter fullscreen mode Exit fullscreen mode

Aqui, @spec indica que estamos adicionando uma especificação de tipo para a função add. O tipo de argumentos é especificado como integer, integer, e o tipo de retorno é especificado como integer.

Especificações e tipos

Os @typespecs também podem ser usados ​​para especificar tipos de valores de retorno que podem ser nil. Vamos ver alguns exemplos a seguir.

@spec find_user(user_id) :: map | nil def find_user(user_id), do: ... 
Enter fullscreen mode Exit fullscreen mode

Aqui, especificamos que a função find_user recebe um argumento do tipo user_id e retorna um map ou nil.

@type user_id :: integer @type user :: %{id: user_id, name: String.t(), email: String.t(), age: integer} @spec find_user(user_id) :: user | nil def find_user(user_id), do: ... 
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo, definimos dois tipos: user_id e user. O user_id é um alias para o tipo integer, que representa o ID de um usuário. O tipo user é um map que contém informações sobre o usuário, como o ID, o nome, o email e a idade.

Na especificação de tipo para a função find_user, usamos o tipo user_id como argumento e especificamos que a função pode retornar um map user ou nil. Isso significa que, se a função encontrar um usuário com o ID fornecido, ela retornará um map user. Caso contrário, ela retornará nil.

@spec get_names() :: [String.t()] def get_names do ["Tomate", "Elixir", "Erlang"] end 
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo, estamos especificando o tipo de retorno de uma função que retorna uma lista de strings.

Da mesma forma, você pode especificar o tipo de retorno de uma função que retorna um map da seguinte forma:

@type user_info :: %{name: String.t(), age: integer} @spec get_user_info(user_id :: integer) :: user_info def get_user_info(user_id) do %{name: "Floki", age: 4} end 
Enter fullscreen mode Exit fullscreen mode

Os @typespecs também podem ser usados para especificar tipos como listas de maps ou maps de listas. Por exemplo:

@type address :: %{street: String.t(), city: String.t()} @type person :: %{name: String.t(), age: integer, addresses: [address]} @spec get_person() :: person def get_person do %{ name: "Floki", age: 4, addresses: [ %{street: "123 Rua A", city: "Rio de Janeiro"}, %{street: "456 Rua B", city: "Angra dos Reis"} ] } end 
Enter fullscreen mode Exit fullscreen mode

Podemos também combinar para criar definições de tipos mais complexas. Por exemplo:

@type coordinates :: {integer, integer} @type circle :: %{center: coordinates, radius: integer} @type rectangle :: %{top_left: coordinates, bottom_right: coordinates} @type shape :: circle | rectangle @spec draw_shape(shape) :: :ok def draw_shape(shape) do # ... end 
Enter fullscreen mode Exit fullscreen mode

Neste exemplo, criamos definições de tipos para coordenadas, círculos e retângulos e, em seguida, combinamos essas definições de tipos para criar um tipo de forma que pode ser um círculo ou um retângulo. A função draw_shape aceita qualquer tipo de forma e retorna :ok.

O mesmo se aplica quando queremos usar os @typespecs para especificar retornos de funções que podem resultar em erros ou mensagens personalizadas:

Retornando erros com @typespecs

@spec get_user(user_id :: integer()) :: {:ok, map()} | {:error, atom()} def get_user(user_id) do case Repo.get(User, user_id) do %User{} = user -> {:ok, user} nil -> {:error, :not_found} end end 
Enter fullscreen mode Exit fullscreen mode

A função get_user retorna uma tuple com :ok e um map contendo as informações do usuário, ou uma tuple com :error e um átomo representando o tipo de erro ocorrido.

Retornando um Ecto.Changeset com @typespecs:

@spec create_user(user_params :: map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def create_user(user_params) do changeset = User.changeset(%User{}, user_params) case Repo.insert(changeset) do {:ok, user} -> {:ok, user} {:error, changeset} -> {:error, changeset} end end 
Enter fullscreen mode Exit fullscreen mode

A função create_user retorna uma tuple com :ok e um map contendo as informações do usuário criado com sucesso, ou uma tuple com :error e um Ecto.Changeset contendo informações sobre os erros de validação ocorridos durante a criação do usuário.

Retornando uma mensagem personalizada com @typespecs:

@spec validate_password(password :: String.t(), confirm_password :: String.t()) :: :ok | {:error, String.t()} def validate_password(password, confirm_password) do if password == confirm_password do :ok else {:error, "As senhas não coincidem."} end end 
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo, a função validate_password retorna :ok caso as senhas informadas sejam iguais, ou uma tuple com :error e uma mensagem personalizada caso as senhas não coincidam.

Vantagens dos @typespecs

Os @typespecs têm várias vantagens importantes:

  • Ajuda a garantir a segurança do seu código: Especificando tipos de argumentos e valores de retorno com @typespecs ajudando a garantir que seu código esteja correto e seguro. Ele também ajuda a detectar erros mais cedo no processo de desenvolvimento, antes que possam se transformar em problemas maiores.

  • Torna seu código mais explícito: Especificar tipos de argumentos e valores de retorno com @typespecs torna seu código mais fácil de entender. Isso ajuda a evitar confusão e erros causados por informações ambíguas ou mal documentadas.

  • Ajuda na documentação do código: Os @typespecs podem ser usados como parte da documentação do seu código. Isso pode tornar sua API mais fácil de entender e usar para outros desenvolvedores que possam trabalhar em seu projeto.

  • Facilita a manutenção do código: Os @typespecs podem ajudar a tornar a manutenção do seu código mais fácil e segura. Quando você altera uma função, pode verificar se a alteração afetou os tipos de argumentos ou valores de retorno da função e atualizar a especificação de tipo em conformidade. Isso ajuda a evitar quebras de código e problemas de integração.

Desvantagens dos @typespecs

Embora os @typespecs tenham muitas vantagens, também existem algumas desvantagens a serem consideradas:

  • Podem adicionar complexidade ao código: Especificar tipos de argumentos e valores de retorno pode adicionar complexidade ao código. Isso pode tornar o código mais difícil de entender ou ler para desenvolvedores que não estão familiarizados com os @typespecs.

  • Podem aumentar o tempo de desenvolvimento: Especificar tipos de argumentos e valores de retorno pode aumentar o tempo de desenvolvimento. Isso ocorre porque você precisa escrever e atualizar as especificações de tipo à medida que escreve o código.

  • Não podem garantir 100% de segurança: Embora os @typespecs possam ajudar a garantir a segurança do seu código, eles não podem garantir 100% de segurança. Você ainda precisa testar e verificar seu código para garantir que ele esteja correto e seguro.

Análise de tipos com Dialyzer

Os @typespecs podem ser usados em conjunto com a ferramenta Dialyzer do Erlang para fornecer uma análise estática de tipos ainda mais poderosa e detectar erros de tipos em tempo de compilação. O Dialyzer é uma ferramenta de análise de tipo estático que pode ser usada para verificar a correção do código Elixir. Ele analisa o código-fonte e os @typespecs em busca de inconsistências e gera avisos e erros se encontrar algum problema.

Para usar o Dialyzer, você precisa instalar o pacote dialyxir e executar o comando mix dialyzer. E então o Dialyzer irá examinar o seu código e fornecer informações detalhadas sobre quaisquer problemas de tipos encontrados.

Conclusão

Os @typespecs são uma ferramenta poderosa e valiosa para o desenvolvimento de código em Elixir. Eles ajudam a garantir a segurança e a legibilidade do código, facilitam a manutenção e a documentação do código e podem ser usados como parte da documentação da API.

Embora os @typespecs possam adicionar complexidade ao código e aumentar o tempo de desenvolvimento, os benefícios superam as desvantagens. Se você está desenvolvendo código em Elixir, é altamente recomendável considerar o uso de @typespecs em suas funções e módulos.

Referências

Elixir Typespecs
Elixir Typespecs and Behaviours
Elixir Typespecs Tutorial
Erlang Type Specifications and Dialyzer


Muito obrigado pela leitura até aqui e espero ter ajudado de alguma forma. Tem alguma sugestão ou encontrou algum problema? por favor deixe-me saber. 💜

Top comments (3)

Collapse
 
elixir_utfpr profile image
Elixir UTFPR (por Adolfo Neto)

Fiz um vídeo sobre este texto:

Por que usar @typespecs em seu código Elixir?, por Rômulo Silva

youtu.be/Kl5iStovPJo

Collapse
 
rohlacanna profile image
Rômulo Silva

Muito obrigado pelo vídeo professor! Anotei todos os feedbacks 💜

Collapse
 
jpramires profile image
João Pedro Ramires

Linguagem dinâmica tem dessas né, que bom que existe uma feature tão expressiva!