Arquitectura líquida. Es como el jazz — improvisar, trabajar juntos, tocar los unos con los otros, haces algo, hacen algo.
—Frank Gehry
El estilo importa. Aunque Elixir tiene mucho estilo, como ocurre con todos los lenguajes, puede estropearse. No estropees el estilo.
Esta es la guía de estilo de la comunidad para el lenguaje de programación Elixir. Por favor, siéntete libre de abrir pull requests y sugerencias, y ¡forma parte de la vibrante comunidad de Elixir!
Si estás buscando otros proyectos en los que contribuir, por favor ve al sitio web de Hex package manager.
Las traducciones de la guía están disponibles en los siguientes lenguajes:
Elixir v1.6 introduce el formateador de código ( Code Formatter ) y la tarea de mix format. Se debe preferir el formateador para todos los proyectos nuevos y el código fuente.
El formateador de código aplica automáticamente las reglas de esta sección, pero se proporcionan aquí como ejemplos del estilo preferido.
-
Evitar los espacios en blanco al final de línea. [enlace]
-
Terminar cada archivo con una nueva línea. [enlace]
-
Usar los finales de línea de Unix (Los usuarios de *BSD/Solaris/Linux/OSX ya están cubiertos por defecto, pero los usuarios de Windows tendrán que prestar especial atención). [enlace]
-
Al usar Git puede que quieras utilizar la siguiente configuración para protegerte de que se cuelen los finales de línea en Windows: [enlace]
git config --global core.autocrlf true
-
Limitar las líneas a 98 caracteres. De lo contrario, asignar la opción de
:line_length
en el archivo.formatter.exs
. [enlace] -
Usar espacios alrededor de operadores, después de comas, dos puntos y punto y coma. No colocar espacios alrededor de parejas como llaves, paréntesis, etc. Los espacios en blanco puede que sean (en la mayoría de casos) irrelevantes para Elixir en tiempo de ejecución, pero su uso apropiado es clave para escribir código fácilmente legible. [enlace]
sum = 1 + 2 {a, b} = {2, 3} [first | rest] = [1, 2, 3] Enum.map(["one", <<"two">>, "three"], fn num -> IO.puts(num) end)
-
No usar espacios después de operadores que no sean una palabra y que sólo reciben un argumento; o alrededor del operador de rango. [enlace]
0 - 1 == -1 ^pinned = some_func() 5 in 1..10
-
Utilizar líneas en blanco entre
def
s para separar las funciones en párrafos lógicos. [enlace]def some_function(some_data) do some_data |> other_function() |> List.first() end def some_function do result end def some_other_function do another_result end def a_longer_function do one two three four end
-
No dejar líneas en blanco después de
defmodule
. [enlace] -
Si se utiliza la sintaxis
do:
con funciones y la línea que constituye el cuerpo de la función es demasiado larga, se debe poner eldo:
en una nueva línea con un nivel de indentación más que la línea anterior. [enlace]def some_function([:foo, :bar, :baz] = args), do: Enum.map(args, fn arg -> arg <> " is on a very long line!" end)
Cuando la cláusula
do:
comienza en su propia línea, se trata como una función multilínea separándola con líneas en blanco.# no recomendado def some_function([]), do: :empty def some_function(_), do: :very_long_line_here # recomendado def some_function([]), do: :empty def some_function(_), do: :very_long_line_here
-
Agregar una línea en blanco tras una "asignación" multilínea como una pista visual de que ha terminado. [enlace]
# no recomendado some_string = "Hello" |> String.downcase() |> String.trim() another_string <> some_string # recomendado some_string = "Hello" |> String.downcase() |> String.trim() another_string <> some_string
# tampoco recomendado something = if x == 2 do "Hi" else "Bye" end String.downcase(something) # recomendado something = if x == 2 do "Hi" else "Bye" end String.downcase(something)
-
Si una lista, mapa o estructura abarca varias líneas, colocar cada elemento, así como los corchetes de apertura y cierre, en su propia línea. Sangrar cada elemento un nivel, pero no los corchetes. [enlace]
# no recomendado [:first_item, :second_item, :next_item, :final_item] # recomendado [ :first_item, :second_item, :next_item, :final_item ]
-
Al definir una lista que ocupa varias líneas, iniciar la lista en una nueva línea, e indentar los elementos para mantenerlos alineados. [enlace]
# no recomendado list = [:first_item, :second_item, :next_item, :last_item] # no recomendado list = [:first_item, :second_item, :next_item, :last_item] # no recomendado list = [ :first_item, :second_item ] # recomendado list = [ :first_item, :second_item ]
-
Si alguna cláusula
case
ocond
necesita más de una línea (debido a la longitud de la línea, múltiples expresiones en el cuerpo de la cláusula, etc.), utilice la sintaxis de varias líneas para todas las cláusulas y separe cada una con una línea en blanco. [enlace]# no preferido case arg do true -> IO.puts("ok"); :ok false -> :error end # no preferido case arg do true -> IO.puts("ok") :ok false -> :error end # recomendado case arg do true -> IO.puts("ok") :ok false -> :error end
-
Colocar los comentarios sobre la línea que se comenta. [enlace]
String.first(some_string) # no recomendado # recomendado String.first(some_string)
-
Utilizar un espacio entre el carácter introductorio del comentario
#
y el resto del texto del comentario. [enlace]#no recomendado String.first(some_string) # recomendado String.first(some_string)
-
Indentar y alinear las cláusulas
with
sucesivas. Poner el argumentodo:
en una nueva línea, indentada normalmente. [enlace]with {:ok, foo} <- fetch(opts, :foo), {:ok, my_var} <- fetch(opts, :my_var), do: {:ok, foo, my_var}
-
Si la expresión
with
tiene un bloquedo
con más de una línea, o tiene una opciónelse
, utiliza la sintaxis multilínea. [enlace]with {:ok, foo} <- fetch(opts, :foo), {:ok, my_var} <- fetch(opts, :my_var) do {:ok, foo, my_var} else :error -> {:error, :bad_arg} end
-
Usar paréntesis para las funciones de aridad uno
function/1
cuando se usa el operador de tubería (pipe) (|>
). [enlace]# no recomendado some_string |> String.downcase |> String.trim # recomendado some_string |> String.downcase() |> String.trim()
-
Nunca dejar un espacio entre el nombre de la función y el paréntesis de apertura. [enlace]
# no recomendado f (3 + 2) # recomendado f(3 + 2)
-
Utilizar paréntesis en las llamadas a funciones, sobretodo dentro de un pipeline. [enlace]
# no recomendado f 3 # recomendado f(3) # no recomendado y además se interpreta como rem(2, (3 |> g)), # que no es lo que se quiere. 2 |> rem 3 |> g # recomendado 2 |> rem(3) |> g
-
Omitir los corchetes de las listas de keywords siempre que sean opcionales. [enlace]
# no recomendado some_function(foo, bar, [a: "baz", b: "qux"]) # recomendado some_function(foo, bar, a: "baz", b: "qux")
El formateador de código no puede aplicar las reglas de esta sección, pero son una práctica generalmente preferida.
-
Agrupar
def
s de una sola línea que coincidan en nombre de función y respuesta, pero separar losdef
s multilinea o no coincidentes con una línea en blanco. [enlace]def some_function(nil), do: {:error, "No Value"} def some_function([]), do: :ok def some_function([first | rest]) do some_function(rest) end
-
Si se tiene más de un
def
s multilínea, no usardef
s de una sola línea. [enlace]def some_function(nil) do {:error, "No Value"} end def some_function([]) do :ok end def some_function([first | rest]) do some_function(rest) end def some_function([first | rest], opts) do some_function(rest, opts) end
-
Usar el operador pipe (
|>
) para encadenar funciones una tras otra. [enlace]# no recomendado String.trim(String.downcase(some_string)) # recomendado some_string |> String.downcase() |> String.trim() # Los pipelines multilínea no se indentan a mayores some_string |> String.downcase() |> String.trim() # Los pipelines multilínea que estén en el lado derecho de un pattern match # deben ser indentados en una nueva línea sanitized_string = some_string |> String.downcase() |> String.trim()
Aunque este sea el método recomendado, se debe tener en cuenta que al copiar y pegar pipelines multilínea en IEx podría causar un error de sintaxis, ya que IEx evaluará la primera línea sin darse cuenta de que la siguiente línea tiene otro pipeline.
-
Evitar utilizar el operador pipe una única vez. [enlace]
# no recomendado some_string |> String.downcase() System.version() |> Version.parse() # recomendado String.downcase(some_string) Version.parse(System.version())
-
Utilizar variables simples (bare variables) como comienzo de una cadena de funciones. [enlace]
# ¡NUNCA HACER ESTO! # Realmente se interpretará como String.trim("nope" |> String.downcase). String.trim "nope" |> String.downcase # no recomendado String.trim(some_string) |> String.downcase() |> String.codepoints() # recomendado some_string |> String.trim() |> String.downcase() |> String.codepoints()
-
Usar paréntesis cuando
def
tenga argumentos, y omitir cuando no. [enlace]# no recomendado def some_function arg1, arg2 do # body omitted end def some_function() do # body omitted end # recomendado def some_function(arg1, arg2) do # body omitted end def some_function do # body omitted end
-
Utilizar
do:
para sentenciasif/unless
de una sola línea. [enlace]# recomendado if some_condition, do: # some_stuff
-
Nunca utilizar
unless
conelse
. Reescribir poniendo el caso positivo primero. [enlace]# no recomendado unless success do IO.puts('failure') else IO.puts('success') end # recomendado if success do IO.puts('success') else IO.puts('failure') end
-
Utilizar
true
como la última condición decond
cuando necesites una cláusula por defecto. [enlace]# no recomendado cond do 1 + 2 == 5 -> "Nope" 1 + 3 == 5 -> "Uh, uh" :else -> "OK" end # recomendado cond do 1 + 2 == 5 -> "Nope" 1 + 3 == 5 -> "Uh, uh" true -> "OK" end
-
Usar paréntesis para llamadas a funciones con aridad cero, de tal forma que puedan ser distinguidas de las variables. A partir de Elixir 1.4, el compilador avisará de los lugares en los que exista ambigüedad. [enlace]
defp do_stuff, do: ... # no recomendado def my_func do # ¿es una variable o una llamada a una función? do_stuff end # recomendado def my_func do # esto es claramente una llamada a una función do_stuff() end
Esta guía sigue las Convenciones de nomenclatura de la documentación de Elixir, incluido el uso de snake_case
y CamelCase
para describir las reglas de mayúsculas y minúsculas.
-
Usa
snake_case
para atoms, funciones y variables. [enlace]# no recomendado :"some atom" :SomeAtom :someAtom someVar = 5 def someFunction do ... end # recomendado :some_atom some_var = 5 def some_function do ... end
-
Usar
CamelCase
para módulos (mantener los acrónimos como HTTP, RFC, XML en mayúsculas). [enlace]# no recomendado defmodule Somemodule do ... end defmodule Some_Module do ... end defmodule SomeXml do ... end # recomendado defmodule SomeModule do ... end defmodule SomeXML do ... end
-
Funciones que regresan un boleano (
true
ofalse
) deben ser nombrados con un símbolo de interrogación. [enlace]def cool?(var) do String.contains?(var, "cool") end
-
Las comprobaciones boleanas que se pueden utilizar en guardias, deben nombrarse con un prefijo
is_
. Para una lista de las expresiones permitidas, ver la documentación de Guard. [enlace]defguard is_cool(var) when var == "cool" defguard is_very_cool(var) when var == "very cool"
-
Las funciones privadas no deben tener el mismo nombre que una función pública
. El patrón
def namey
defp do_name` ya no se recomienda.Usualmente uno puede tratar de encontrar m´ás nombres descriptivos concentrandose en [enlace]
def sum(list), do: sum_total(list, 0) # private functions defp sum_total([], total), do: total defp sum_total([head | tail], total), do: sum_total(tail, head + total)
-
Escribir código expresivo e intentar transmitir la intención del programa a través de flujos de control, estructura y nombrado. [enlace]
-
Los comentarios que sean más largos de una palabra se escribirán capitalizados, y las frases utilizarán signos de puntuación. Usa un espacio tras cada punto. [enlace]
# no recomendado # a estos comentarios en minúsculas les falta los signos de puntuación # recomendado # Ejemplo de capitalización # Usa signos de puntuación para frases completas.
- Limite las líneas de comentarios a 100 caracteres. [enlace]
-
Las anotaciones se escriben normalmente en la línea inmediatamente superior al código que anotan. [enlace]
-
La palabra clave para la anotación estará completamente en mayúsculas, seguida de dos puntos y un espacio, a continuación se añade la nota que describe el problema. [enlace]
# TODO: Deprecate in v1.5. def some_function(arg), do: {:ok, arg}
-
En casos en los que el problema sea tan obvio que cualquier tipo de documentación resultará redundante, puedes poner las anotaciones al final de la línea sin ningún tipo de nota. Este uso debería de ser la excepción y no la norma. [enlace]
start_task() # FIXME Process.sleep(5000)
-
Usar
TODO
para anotar código que falte o funcionalidades que deberán ser añadidas posteriormente. [enlace] -
Usar
FIXME
para denotar código que debe ser arreglado. [enlace] -
Usar
OPTIMIZE
para denotar código lento o ineficiente que pudiese llegar a causar problemas de rendimiento. [enlace] -
Usar
HACK
para denotar "code smells" en los que se hayan empleado prácticas de programación cuestionables y que deban ser refactorizados. [enlace] -
Usar
REVIEW
para denotar cualquier cosa que deba ser revisada para confirmar que funciona como se espera. Por ejemplo:REVIEW: Are we sure this is how the client does X currently?
[enlace] -
Usar claves de anotación propias si lo consideras oportuno, pero asegúrate de documentarlas en el archivo
README
de tu proyecto o similar. [enlace]
-
Usar un archivo por módulo a no ser que el módulo sea utilizado únicamente de manera interna por otro módulo (como en el caso de un test). [enlace]
-
Utilizar
snake_case
para el nombre del archivo yCamelCase
para el nombre del módulo. [enlace]# el archivo es llamado some_module.ex defmodule SomeModule do end
-
Representar cada nivel de anidación dentro del módulo como un directorio. [enlace]
# el archivo se llama parser/core/xml_parser.ex defmodule Parser.Core.XMLParser do end
-
Listar los atributos y directivas del módulo en el siguiente orden: [enlace]
@moduledoc
@behaviour
use
import
require
alias
@module_attribute
defstruct
@type
@callback
@macrocallback
@optional_callbacks
defmacro
,defguard
,def
, etc.
Añadir una línea en blanco entre cada grupo, y ordenar alfabéticamente los términos (como nombres de módulo). Aquí un ejemplo general de cómo se debe ordenar el código en los módulos:
defmodule MyModule do @moduledoc """ An example module """ @behaviour MyBehaviour use GenServer import Something import SomethingElse require Integer alias My.Long.Module.Name alias My.Other.Module.Example @module_attribute :foo @other_attribute 100 defstruct [:name, params: []] @type params :: [{binary, binary}] @callback some_function(term) :: :ok | {:error, term} @macrocallback macro_name(term) :: Macro.t() @optional_callbacks macro_name: 1 @doc false defmacro __using__(_opts), do: :no_op @doc """ Determines when a term is `:ok`. Allowed in guards. """ defguard is_ok(term) when term == :ok @impl true def init(state), do: {:ok, state} # Define other functions here. end
-
Usar la pseudo variable
__MODULE__
cuando un módulo se refiera a sí mismo. Esto evitará actualizar cualquier referencia cuando el nombre del módulo cambie. [enlace]defmodule SomeProject.SomeModule do defstruct [:name] def name(%__MODULE__{name: name}), do: name end
-
Si se prefiere un nombre más bonito para esta referencia, se puede definir un alias. [enlace]
defmodule SomeProject.SomeModule do alias __MODULE__, as: SomeModule defstruct [:name] def name(%SomeModule{name: name}), do: name end
-
Evitar repeticiones en los nombres de módulos y espacios de nombrado. Esto mejora la legibilidad global y elimina alias ambiguos. [enlace]
# no recomendado defmodule Todo.Todo do ... end # recomendado defmodule Todo.Item do ... end
La documentación en Elixir (ya sea cuando es leída en iex
mediante h
o cuando
es generada con ExDoc) utiliza los atributos de módulo
@moduledoc
y @doc
.
-
Incluir siempre un atributo
@moduledoc
en la línea inmediatamente posterior adefmodule
de un módulo. [enlace]# no recomendado defmodule AnotherModule do use SomeModule @moduledoc """ About the module """ ... end # recomendado defmodule AThirdModule do @moduledoc """ About the module """ use SomeModule ... end
-
Utilizar
@moduledoc false
si no se desea documentar un módulo. [enlace]defmodule SomeModule do @moduledoc false ... end
-
Separa el código después de
@moduledoc
con una línea en blanco. [enlace]# no recomendado defmodule SomeModule do @moduledoc """ About the module """ use AnotherModule end # recomendado defmodule SomeModule do @moduledoc """ About the module """ use AnotherModule end
-
Usar heredocs con markdown para la documentación. [enlace]
# no recomendado defmodule SomeModule do @moduledoc "About the module" end defmodule SomeModule do @moduledoc """ About the module Examples: iex> SomeModule.some_function :result """ end # recomendado defmodule SomeModule do @moduledoc """ About the module ## Examples iex> SomeModule.some_function :result """ end
Typespecs es una notación para declarar tipos y especificaciones, ya sea para documentación o para la herramienta de análisis estático Dialyzer.
Los tipos propios deben de ser definidos en la parte superior del módulo junto con las demás directivas (ver Módulos).
-
Situar las definiciones
@typedoc
y@type
juntas, y separar cada par con una línea en blanco. [enlace]defmodule SomeModule do @moduledoc false @typedoc "The name" @type name :: atom @typedoc "The result" @type result :: {:ok, term} | {:error, term} ... end
-
Si la unión de tipos es demasiado larga para caber en una sola línea, añadir una nueva línea e indentar con espacios para mantener los tipos alineados. [enlace]
# not preferred @type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_type # preferred @type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_type
-
Nombrar al tipo principal para un módulo
t
, por ejemplo: la especificación de tipo para una estructura (struct). [enlace]defstruct [:name, params: []] @type t :: %__MODULE__{ name: String.t() | nil, params: Keyword.t() }
-
Situar las especificaciones justo antes de la definición de la función después de
@doc
, sin separarlas con una línea en blanco. [enlace]@doc """ Some function description. """ @spec some_function(term) :: result def some_function(some_data) do {:ok, some_data} end
-
Usar una lista de atoms para los campos de la struct que tengan valor
nil
, seguida del resto de claves. [enlace]# no recomendado defstruct name: nil, params: nil, active: true # recomendado defstruct [:name, :params, active: true]
-
Omitir los corchetes cuando el argumento de
defstruct
sea una lista de keywords. [enlace]# no recomendado defstruct [params: [], active: true] # recomendado defstruct params: [], active: true # obligatorio - los corchetes no son opcionales cuando la lista tenga al menos # un átomo defstruct [:name, params: [], active: true]
-
Si la definición de una estructura abarca múltiples líneas, poner cada elemento en su propia línea, manteniendo los elementos alineados. [enlace]
defstruct foo: "test", bar: true, baz: false, qux: false, quux: 1
Si una estructura con múltiples líneas requiere corchetes, formatear como una lista de múltiples líneas:
defstruct [ :name, params: [], active: true ]
-
Hacer que los nombres de las excepciones terminen en
Error
. [enlace]# no recomendado defmodule BadHTTPCode do defexception [:message] end defmodule BadHTTPCodeException do defexception [:message] end # recomendado defmodule BadHTTPCodeError do defexception [:message] end
-
Usar mensajes de error en minúsculas cuando lances excepciones. No usar puntuación al final. [enlace]
# no recomendado raise ArgumentError, "This is not valid." # recomendado raise ArgumentError, "this is not valid"
-
Usar siempre la sintaxis especial para listas de keywords. [enlace]
# no recomendado some_value = [{:a, "baz"}, {:b, "qux"}] # recomendado some_value = [a: "baz", b: "qux"]
-
Usar la sintaxis abreviada de llave-valor para los mapas cuando todas las llaves son átomos. [enlace]
# no recomendado %{:a => 1, :b => 2, :c => 0} # recomendado %{a: 1, b: 2, c: 3}
-
Usar la sintaxis detallada de llave-valor para los mapas si alguna clave no es un átomo. [enlace]
# no recomendado %{"c" => 0, a: 1, b: 2} # recomendado %{:a => 1, :b => 2, "c" => 0}
-
Hacer coincidencia de cadenas utilizando la concatenación de string en lugar de patrones binarios: [enlace]
# no recomendado <<"my"::utf8, _rest::bytes>> = "my string" # recomendado "my" <> _rest = "my string"
Por el momento no se han añadido recomendaciones para expresiones regulares.
- Evita la metaprogramación innecesaria. [enlace]
-
Cuando se escriban aserciones con ExUnit, colocar la expresión que se está probando a la izquierda del operador, y el resultado esperado a la derecha, a menos que sea un patrón de coincidencia (pattern match) [Enlace]
# recomendado assert actual_function(1) == true # no recomendado assert true == actual_function(1) # obligatorio - la aserción es un pattern match assert {:ok, expected} = actual_function(3)
-
Aleksei Magusev's Elixir Style Guide — Guía que surge del estilo de programación utilizado en las librerías del core de Elixir. Desarrollada por Aleksei Magusev y Andrea Leopardi, miembros del equipo principal de Elixir. Aunque el proyecto Elixir no se adhiere a ninguna guía de estilo específica, esta es la guía más cercana a sus convenciones.
-
Credo's Elixir Style Guide — Guía de Estilo para el lenguaje Elixir, implementada para la herramienta de análisis estático de código Credo.
Dirigirse a Awesome Elixir para encontrar bibliotecas y herramientas que puedan ayudar con el análisis de código y corrección estilo.
Es nuestra esperanza que esto se convierta en un lugar central en el que la comunidad discuta las mejores prácticas en Elixir. Estás invitado a abrir tickets y a enviar pull requests con mejoras. ¡Gracias por tu ayuda por adelantado!
Revisa la guía para contribuir y el código de conducta (ambos en inglés) para más información.
Una guía de estilo de la comunidad no tiene sentido sin el soporte de la comunidad. Por favor tuitea, ponle star, y haz que otros programadores de Elixir conozcan esta guía de forma que puedan contribuir.
Este trabajo está hecho bajo licencia Creative Commons Attribution 3.0 Unported License
La estructura de esta guía, partes del código de ejemplo, y muchos otros puntos iniciales de este documento fueron tomados de la Ruby community style guide. Muchas cosas ya eran directamente aplicables a Elixir, lo que permitió sacar antes este documento y empezar más rápido.
Aquí está la lista de gente que ha contribuido amablemente a este proyecto.