Жидкая архитектура. Это как джаз - вы импровизируете, работаете вместе, взаимодействуете друг с другом, вы создаете что-то, они создают что-то.
—Френк Гери
Стиль имеет значение. Elixir имеет много стилей, но, как и все языки, его можно задушить. Не душите стиль.
Это перевод на русский язык руководства по написанию кода от сообщества языка программирования Elixir. Не стесняйтесь вносить предложения и создавать pull requests, и присоединяйтесь к живому сообществу Elixir.
Если вы ищете другие проекты, в которые можно внести свой вклад, посетите сайт менеджера пакетов Hex.
Переводы руководства доступны на следующих языках:
В Elixir v1.6 был представлен форматировщик кода и команда Mix format. Для всех новых проектов и исходного кода предпочтительнее использовать форматировщик.
Правила, перечисленные в этом разделе, применяются автоматически форматировщиком кода, здесь же они представлены в качестве примеров предпочтительного стиля.
– Избегайте пробелов в конце строки. [ссылка]
-
Заканчивайте каждый файл пустой строкой. [ссылка]
-
Используйте символы новой строки в стиле Unix (*BSD/Solaris/Linux/OSX по умолчанию, пользователи Windows должны быть особенно осторожны). [ссылка]
-
Если вы используете Git, вы можете добавить следующую конфигурационную настройку, чтобы защитить свой проект от появления символов новой строки Windows: [ссылка]
git config --global core.autocrlf true
-
Ограничивайте длину строк 98 символами или установите параметр :line_length в вашем файле .formatter.exs. [ссылка]
-
Используйте пробелы вокруг операторов, после запятых, двоеточий и точек с запятой. Не ставьте пробелы вокруг парных символов, таких как кавычки, скобки и т.д. Пробелы (в основном) несущественны для Elixir во время выполнения, но их правильное использование является ключом к написанию легко читаемого кода. [ссылка]
sum = 1 + 2
{a, b} = {2, 3}
[first | rest] = [1, 2, 3]
Enum.map(["one", <<"two">>, "three"], fn num -> IO.puts(num) end)
-
Не используйте пробелы после операторов, которые принимают только один аргумент, или вокруг оператора range. [ссылка]
0 - 1 == -1 ^pinned = some_func() 5 in 1..10
-
Используйте пустые строки между def для разбиения функции на логические параграфы. [ссылка]
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
-
Не ставьте пустую строку после defmodule. [ссылка]
-
Если заголовок функции и do:-клауза слишком длинные и не помещаются на одной строке, поместите do: на новую строку, с отступом на один уровень больше, чем на предыдущей строке. [ссылка]
def some_function([:foo, :bar, :baz] = args), do: Enum.map(args, fn arg -> arg <> " is on a very long line!" end)
Когда do:-клауза начинается с новой строки, следует относиться к ней как к многострочной функции, разделяя её пустыми строками.
# нежелательно def some_function([]), do: :empty def some_function(_), do: :very_long_line_here # предпочтительно def some_function([]), do: :empty def some_function(_), do: :very_long_line_here
-
Добавьте пустую строку после многострочного присвоения как визуальный критерий того, что присвоение завершено. [ссылка]
# нежелательно some_string = "Hello" |> String.downcase() |> String.trim() another_string <> some_string # предпочтительно some_string = "Hello" |> String.downcase() |> String.trim() another_string <> some_string
# также нежелательно something = if x == 2 do "Hi" else "Bye" end String.downcase(something) # предпочтительно something = if x == 2 do "Hi" else "Bye" end String.downcase(something)
-
Если список, ассоциативный массив или структура занимает несколько строк, помещайте каждый элемент, а также открывающую и закрывающую скобки, на отдельных строках. Сделайте отступ для содержимого на 1 уровень относительно открывающей и закрывабшей скобки. [ссылка]
# нежелательно [:first_item, :second_item, :next_item, :final_item] # предпочтительно [ :first_item, :second_item, :next_item, :final_item ]
-
Когда вы присваиваете список, ассоциативный массив или структуру, поместите открывающую скобку на ту же строку, что и присваивание. [ссылка]
# нежелательно list = [ :first_item, :second_item ] # предпочтительно list = [ :first_item, :second_item ]
-
Если любой из клауз case или cond требует более одной строки (из-за длины строки, нескольких выражений в теле клаузы и т.д.), используйте многострочный синтаксис для всех клауз и разделяйте их с помощью пустой строки.[ссылка]
# нежелательно case arg do true -> IO.puts("ok"); :ok false -> :error end # нежелательно case arg do true -> IO.puts("ok") :ok false -> :error end # предпочтительно case arg do true -> IO.puts("ok") :ok false -> :error end
-
Размещайте комментарии над строкой, которую они комментируют. [ссылка]
String.first(some_string) # нежелательно # предпочтительно String.first(some_string)
-
Используйте один пробел между ведущим символом # комментария и текстом комментария. [ссылка]
#not preferred String.first(some_string) # предпочтительно String.first(some_string)
-
Выравнивайте последовательные with-клаузы и делайте для них одинаковый отступ. Помещайте аргумент do:-клаузы на новую строку, выровненную с предыдущими клаузами. [ссылка]
with {:ok, foo} <- fetch(opts, :foo), {:ok, my_var} <- fetch(opts, :my_var), do: {:ok, foo, my_var}
-
Если блок with содержит несколько строк или имеет вариант else, используйте многострочный синтаксис. [ссылка]
with {:ok, foo} <- fetch(opts, :foo), {:ok, my_var} <- fetch(opts, :my_var) do {:ok, foo, my_var} else :error -> {:error, :bad_arg} end
-
Используйте скобки для функций с одним аргументом при использовании оператора конвейера (pipe operator) (|>). [ссылка]
# нежелательно some_string |> String.downcase |> String.trim # предпочтительно some_string |> String.downcase() |> String.trim()
-
Никогда не ставьте пробел между именем функции и открывающей скобкой. [ссылка]
# нежелательно f (3 + 2) # предпочтительно f(3 + 2)
-
Используйте скобки в вызовах функций, особенно внутри конвейера (pipeline). [ссылка]
# нежелательно f 3 # предпочтительно f(3) # нежелательно и читается как rem(2, (3 |> g)), а не как задумано. 2 |> rem 3 |> g # предпочтительно 2 |> rem(3) |> g()
-
Опустите квадратные скобки в ключевых списках, когда они необязательны.[ссылка]
# нежелательно some_function(foo, bar, [a: "baz", b: "qux"]) # предпочтительно some_function(foo, bar, a: "baz", b: "qux")
Правила в этом разделе могут не применяться форматировщиком кода автоматически, однако обычно эти правила являются предпочтительной практикой.
-
Выравнивайте однострочные выражения
def
, которые относятся к одной функции, друг за другом, а многострочные выраженияdef
разделяйте пустой строкой. [ссылка]def some_function(nil), do: {:error, "No Value"} def some_function([]), do: :ok def some_function([first | rest]) do some_function(rest) end
-
Если у вас больше одного многострочного выражения
def
, не используйте однострочныеdef
. [ссылка]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
-
Используйте оператор конвейера |> для объединения функций в цепочку. [ссылка]
# нежелательно String.trim(String.downcase(some_string)) # предпочтительно some_string |> String.downcase() |> String.trim() # Многострочные конвейеры (pipelines) не требуют дополнительного отступа. some_string |> String.downcase() |> String.trim() # Многострочные конвейеры (pipelines) на правой стороне сопоставления с образцом # следует выравнивать по отступу на новой строке с правой частью выражения сопоставления. sanitized_string = some_string |> String.downcase() |> String.trim()
Хотя это предпочтительный метод, следует учитывать, что при копировании и вставке многострочных конвейеров в IEx может возникнуть синтаксическая ошибка, так как IEx будет оценивать первую строку, не осознавая, что следующая строка содержит конвейер. Чтобы избежать этого, можно обернуть вставленный код в скобки.
-
Избегайте использования оператора pipe |> только однократно. [ссылка]
# нежелательно some_string |> String.downcase() System.version() |> Version.parse() # предпочтительно String.downcase(some_string) Version.parse(System.version())
-
Используйте простые (а не вызовы функций) переменные в начале цепочки функций. [ссылка]
# нежелательно String.trim(some_string) |> String.downcase() |> String.codepoints() # предпочтительно some_string |> String.trim() |> String.downcase() |> String.codepoints()
-
Используйте скобки при объявлении
def
, если у функции есть аргументы, и не используйте скобки, если аргументов нет. [ссылка]# нежелательно def some_function arg1, arg2 do # body omitted end def some_function() do # body omitted end # предпочтительно def some_function(arg1, arg2) do # body omitted end def some_function do # body omitted end
-
Используйте
do:
: для однострочных операторовif/unless
. [ссылка]# предпочтительно if some_condition, do: # some_stuff
-
Никогда не используйте
unless
сelse
. Перепишите такие случаи в "положительном" виде с использованиемif
. [ссылка]# нежелательно unless success do IO.puts('failure') else IO.puts('success') end # предпочтительно if success do IO.puts('success') else IO.puts('failure') end
-
Используйте
true
в качестве последнего условия для специальной формыcond
, когда вам нужна клауза, которая всегда срабатывает. [ссылка]# нежелательно cond do 1 + 2 == 5 -> "Nope" 1 + 3 == 5 -> "Uh, uh" :else -> "OK" end # предпочтительно cond do 1 + 2 == 5 -> "Nope" 1 + 3 == 5 -> "Uh, uh" true -> "OK" end
-
Используйте скобки при вызове функций с нулевым арностью, чтобы они можно было отличить от переменных. Начиная с версии Elixir 1.4, компилятор будет предупреждать вас о местах, где существует неоднозначность. [ссылка]
defp do_stuff, do: ... # нежелательно def my_func do # is this a variable or a function call? do_stuff end # предпочтительно def my_func do # this is clearly a function call do_stuff() end
Это руководство следует соглашениям об именовании из документации Elixir, включая использование snake_case
и CamelCase
для описания правил написания имен.
-
Используйте
snake_case
для атомов, функций и переменных. [ссылка]# нежелательно :"some atom" :SomeAtom :someAtom someVar = 5 def someFunction do ... end # предпочтительно :some_atom some_var = 5 def some_function do ... end
-
Используйте
CamelCase
для модулей (оставляйте аббревиатуры, такие как HTTP, RFC, XML, в верхнем регистре).[ссылка]# нежелательно defmodule Somemodule do ... end defmodule Some_Module do ... end defmodule SomeXml do ... end # предпочтительно defmodule SomeModule do ... end defmodule SomeXML do ... end
-
Функции, которые возвращают булевое значение (
true
илиfalse
), должны иметь название с вопросительным знаком в конце. [ссылка]def cool?(var) do String.contains?(var, "cool") end
-
Логические проверки, которые можно использовать в ограничителях (guard clauses), должны иметь префикс
is_
. Для списка разрешенных выражений см. Ограничители в документации. [ссылка]defguard is_cool(var) when var == "cool" defguard is_very_cool(var) when var == "very cool"
-
Не следует давать приватным функциям то же имя, что и у публичных функций. Кроме того, не рекомендуется использовать шаблон
def name
иdefp do_name
.
Обычно можно попытаться найти более описательные имена, сосредоточившись на различиях. [ссылка]
def sum(list), do: sum_total(list, 0)
# приватные функции
defp sum_total([], total), do: total
defp sum_total([head | tail], total), do: sum_total(tail, head + total)
-
Пишите выразительный код и старайтесь передать намерения вашей программы через управляющие конструкции, структуру и названия. [ссылка]
-
Комментарии длиннее одного слова должны начинаться с заглавной буквы, а предложения в них должны иметь знаки препинания. Используйте один пробел после точек. [ссылка]
# нежелательно # этот комментарий начинается с маленькой буквы и не содержат знаков препинания # предпочтительно # Начинайте комментарий с заглавной буквы, используйте знаки препинания.
-
Ограничьте длину комментариев до 100 символов. [ссылка]
-
Аннотации обычно должны быть написаны на строке непосредственно над соответствующим кодом. [ссылка]
-
Ключевое слово аннотации пишется с заглавной буквы, за которым следует двоеточие и пробел, а затем примечание, описывающее проблему. [ссылка]
# TODO: Deprecate in v1.5. def some_function(arg), do: {:ok, arg}
-
В случаях, когда проблема настолько очевидна, что любая документация будет избыточной, аннотации могут оставаться без примечания. Однако это должно быть исключением, а не правилом. [ссылка]
start_task() # FIXME Process.sleep(5000)
-
Используйте
TODO
, чтобы отметить отсутствующие функции или функциональность, которые должны быть добавлены в будущем. [ссылка] -
Используйте
FIXME
для отметки о неисправном коде, который нуждается в исправлении. [ссылка] -
Используйте
OPTIMIZE
, чтобы отметить медленный или неэффективный код, который может вызвать проблемы с производительностью. [ссылка] -
Используйте
HACK
, чтобы указать на "запахи кода", где были использованы сомнительные методы написания кода и их следует переработать. [ссылка] -
Используйте
REVIEW
, чтобы отметить все, что должно быть рассмотрено, чтобы убедиться, что оно работает по назначению. Например:REVIEW: Мы уверены, что это то, как клиент сейчас делает X?
[ссылка] -
Используйте другие пользовательские ключевые слова аннотации, если они кажутся уместными, но не забудьте задокументировать их в файле
README
вашего проекта или аналогичном файле. [ссылка]
-
Задавайте один модуль на файл, если только модуль не используется только внутри другого модуля (например, внутри теста). [ссылка]
-
Задавайте имена файлов в
snake_case
для имен модулей вCamelCase
. [ссылка]# Файл называется some_module.ex. defmodule SomeModule do end
-
Каждый уровень вложенности в имени модуля должен быть представлен в виде отдельной директории. [ссылка]
# file is called parser/core/xml_parser.ex defmodule Parser.Core.XMLParser do end
-
Список атрибутов модуля, директив и макросов должен быть упорядочен следующим образом: [ссылка]
@moduledoc
@behaviour
use
import
require
alias
@module_attribute
defstruct
@type
@callback
@macrocallback
@optional_callbacks
defmacro
,defmodule
,defguard
,def
, и т.д.
Добавляйте пустую строку между каждой группой и сортируйте термины (такие как имена модулей) в алфавитном порядке. Вот общий пример того, как должны быть упорядочены элементы в ваших модулях:
defmodule MyModule do
@moduledoc """
Пример модуля
"""
@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 """
Определяет, когда терм равен :ok. Разрешено использование в ограничениях (guards).
"""
defguard is_ok(term) when term == :ok
@impl true
def init(state), do: {:ok, state}
# Определите другие функции здесь.
end
-
Используйте псевдопеременную
__MODULE__
, когда модуль ссылается на самого себя. Это позволяет избежать необходимости обновлять ссылки на себя при изменении имени модуля. [ссылка]defmodule SomeProject.SomeModule do defstruct [:name] def name(%__MODULE__{name: name}), do: name end
-
Если вам нужно более удобное имя для ссылки на сам модуль, можно настроить псевдоним (alias). [ссылка]
defmodule SomeProject.SomeModule do alias __MODULE__, as: SomeModule defstruct [:name] def name(%SomeModule{name: name}), do: name end
-
Избегайте повторения фрагментов в названиях модулей и пространств имен. Это повышает общую читаемость и устраняет неоднозначность псевдонимов. [ссылка]
# нежелательно defmodule Todo.Todo do ... end # предпочтительно defmodule Todo.Item do ... end
Документация в Elixir (когда оно либо читается в iex
с помощью h
, либо генерируется с помощью ExDoc), использует атрибуты модуля @moduledoc
и @doc
.
-
Всегда используйте атрибут
@moduledoc
в строке сразу послеdefmodule
в вашем модуле. [ссылка]# нежелательно defmodule AnotherModule do use SomeModule @moduledoc """ О модуле """ ... end # предпочтительно defmodule AThirdModule do @moduledoc """ О модуле """ use SomeModule ... end
-
Используйте
@moduledoc false
, если не планируете документировать модуль. [ссылка]defmodule SomeModule do @moduledoc false ... end
-
Отделяйте код после
@moduledoc
пустой строкой. [ссылка]# нежелательно defmodule SomeModule do @moduledoc """ О модуле """ use AnotherModule end # предпочтительно defmodule SomeModule do @moduledoc """ О модуле """ use AnotherModule end
-
Для документирования используйте
heredoc-синтаксис
(код "как есть") с разметкойmarkdown
. [ссылка]
# нежелательно
defmodule SomeModule do
@moduledoc "О модуле"
end
defmodule SomeModule do
@moduledoc """
О модуле
Examples:
iex> SomeModule.some_function
:result
"""
end
# предпочтительно
defmodule SomeModule do
@moduledoc """
О модуле
## Examples
iex> SomeModule.some_function
:result
"""
end
Аннотации типов - это нотация для объявления типов и спецификаций, используемая для документирования или для использования инструмента статического анализа Dialyzer.
Пользовательские типы должны быть определены вверху модуля вместе с другими директивами (см. Модули).
-
Располагайте определения @typedoc и @type вместе, а каждую пару разделяйте пустой строкой. [ссылка]
defmodule SomeModule do @moduledoc false @typedoc "The name" @type name :: atom @typedoc "The result" @type result :: {:ok, term} | {:error, term} ... end
-
Для длинных объединённых типов (union type), которые не умещаются на одной строке, следует помещать каждую часть типа на отдельной строке с отступом на один уровень больше имени типа. [ссылка]
# нежелательно @type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_type # предпочтительно @type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_type
-
Назовите основной тип модуля
t
, например типовую спецификацию структуры (struct). [ссылка]defstruct [:name, params: []] @type t :: %__MODULE__{ name: String.t() | nil, params: Keyword.t() }
-
Типы и спецификации следует размещать перед определением функции, после
@doc
, без разделения их пустой строкой. [ссылка]@doc """ Описание функции. """ @spec some_function(term) :: result def some_function(some_data) do {:ok, some_data} end
-
Используйте список атомов для того, чтобы задать поля структур со значением по умолчанию
nil
, а поля с другими значениями указывайте после. [ссылка]# нежелательно defstruct name: nil, params: nil, active: true # предпочтительно defstruct [:name, :params, active: true]
-
Опускайте квадратные скобки, когда аргументом
defstruct
является ключевой список. [ссылка]# нежелательно defstruct [params: [], active: true] # предпочтительно defstruct params: [], active: true # required - brackets are not optional, with at least one atom in the list defstruct [:name, params: [], active: true]
-
Если определение структуры занимает несколько строк, разместите каждый элемент на своей собственной строке, сохраняя выравнивание элементов. [ссылка]
defstruct foo: "test",
bar: true,
baz: false,
qux: false,
quux: 1
Если многострочное определение структуры требует скобок, отформатируйте его как многострочный список:
defstruct [
:name,
params: [],
active: true
]
-
Названия исключений должны заканчиваться на
Error
. [ссылка]# нежелательно defmodule BadHTTPCode do defexception [:message] end defmodule BadHTTPCodeException do defexception [:message] end # предпочтительно defmodule BadHTTPCodeError do defexception [:message] end
-
Используйте строчные буквы в сообщениях об ошибках, когда вызываете исключения, и не используйте знаки препинания.[ссылка]
# нежелательно raise ArgumentError, "This is not valid." # предпочтительно raise ArgumentError, "this is not valid"
-
Всегда используйте специальный синтаксис для ключевых списков. [ссылка]
# нежелательно some_value = [{:a, "baz"}, {:b, "qux"}] # предпочтительно some_value = [a: "baz", b: "qux"]
-
Используйте сокращенный синтаксис "ключ: значение" для ассоциативных массивов, когда все ключи являются атомами. [ссылка]
# нежелательно %{:a => 1, :b => 2, :c => 0} # предпочтительно %{a: 1, b: 2, c: 3}
-
Используйте для ассоциативных массивов подробный синтаксис "ключ => значение", если хотя бы один ключ не является атомом. [ссылка]
# нежелательно %{"c" => 0, a: 1, b: 2} # предпочтительно %{:a => 1, :b => 2, "c" => 0}
-
Сопоставляйте строки, используя оператор конкантенации строк (<>), а не бинарные шаблоны: [ссылка]
# нежелательно <<"my"::utf8, _rest::bytes>> = "my string" # предпочтительно "my" <> _rest = "my string"
Никаких рекомендаций для регулярных выражений пока не добавлено.
- Избегайте ненужного метапрограммирования. [ссылка]
-
При написании утверждений ExUnit помещайте проверяемое выражение слева от оператора, а ожидаемый результат - справа, если только утверждение не является сопоставлением с образцом. [ссылка]
# предпочтительно assert actual_function(1) == true # нежелательно assert true == actual_function(1) # необходимо, т.к. утверждение является сопоставлением с образцом assert {:ok, expected} = actual_function(3)
-
Руководство по стилю Elixir от Алексея Магусева — Руководство по стилю Elixir, основанное на стиле кодирования, принятом в библиотеках ядра Elixir. Разработано Алексеем Магусевым и Андреа Леопарди, членами основной команды Elixir. Хотя проект Elixir не придерживается какого-либо конкретного руководства по стилю, это наиболее приближенное к его конвенциям руководство.
-
Credo's Elixir Style Guide - Руководство по стилю для языка Elixir, реализованное с помощью инструмента статического анализа кода Credo.
Обратитесь к разделу Awesome Elixir за библиотеками и инструментами, которые могут помочь с анализом кода и линтингом стилей.
Мы надеемся, что это станет центральным узлом для обсуждения сообществом лучших практик Elixir. Не стесняйтесь открывать тикеты или отправлять пулл-реквесты. Заранее спасибо за вашу помощь!
Для получения дополнительной информации ознакомьтесь с руководством по участию в проекте.
Руководство по стилю сообщества бессмысленно без поддержки сообщества. Пожалуйста, напишите в твиттере, star, и сообщите программистам Elixir об этом руководстве, чтобы они могли внести свой вклад.
This work is licensed under a Creative Commons Attribution 3.0 Unported License
Структура этого руководства, фрагменты кода примеров и многие исходные положения, изложенные в этом документе, были заимствованы из руководства по стилю сообщества Ruby. Многие вещи применимы к Elixir, что позволило нам быстрее выпустить этот документ, чтобы начать дискуссию.
Вот список людей, которые внесли любезный вклад в этот проект.