Руководство по стилю

Это руководство по стилю является адаптацией руководства для Invenia. Мы хотели бы выразить благодарность его авторам за возможность воспользоваться им. Но ознакомление с данным руководством не является обязательным для участия в разработке Turing. Никто не рассердится, если вы создадите запрос на вытягивание с нарушением изложенных в нем правил. Мы просто поможем вам поправить код перед его слиянием.

Эти соглашения изначально были выработаны в Invenia с опорой на различные источники, включая PEP8 для Python, замечания для разработчиков Julia и руководство по стилю Julia.

Представленный ниже материал представляет собой точную копию исходного руководства Invenia с нашими небольшими изменениями.

Замечание о согласованности

При использовании этого руководства важно понимать, что в нем приводятся рекомендации, а не правила. Лучше всего об этом сказано в PEP8:

Главная цель руководства по стилю — согласованность. Согласованность с этим руководством по стилю важна. Согласованность в рамках проекта важнее. Согласованность в пределах модуля или функции еще важнее.

Но самое важное — это знать, когда можно пренебречь согласованностью, ведь иногда руководство по стилю просто неприменимо. Если есть сомнения, полагайтесь на здравый смысл. Посмотрите другие примеры и решите, что подходит лучше всего. И не стесняйтесь задавать вопросы!

Краткий обзор

Старайтесь следовать одновременно рекомендациям по участию в разработке Julia, руководству по стилю Julia и данному руководству. Если рекомендации в них противоречат друг другу, приоритет имеет данное руководство (известные конфликты будут отмечены в нем).

  • Используйте 4 пробела на каждый уровень отступа. Не используйте символы табуляции.

  • Старайтесь соблюдать ограничение на длину строки в 92 символа.

  • Для имен модулей и типов придерживайтесь соглашения о регистре UpperCamelCase.

  • Для имен методов используйте нижний регистр с символами подчеркивания (но имейте в виду: в коде Julia предпочтительно использовать нижний регистр без символов подчеркивания).

  • Комментарии желательны: старайтесь объяснять назначение кода.

  • Используйте пробелы, чтобы код было удобнее читать.

  • В конце строки не должно быть пробела.

  • Не добавляйте пробелы после открывающей скобки и перед закрывающей, например, Int64(value) лучше, чем Int64( value ).

Настройка редактора

Настройки Sublime Text

Если вы пользуетесь Sublime Text, мы рекомендуем задать следующие настройки, относящиеся к синтаксису Julia. Чтобы изменить эти настройки, сначала откройте любой файл Julia (*.jl) в Sublime Text. Затем выберите Preferences > Settings - More > Syntax Specific - User

{
    "translate_tabs_to_spaces": true,
    "tab_size": 4,
    "trim_trailing_white_space_on_save": true,
    "ensure_newline_at_eof_on_save": true,
    "rulers": [92]
}

Настройки Vim

Если вы пользуетесь Vim, мы рекомендуем добавить следующие параметры в файл .vimrc.

set tabstop=4                             " Sets tabstops to a width of four columns.
set softtabstop=4                         " Determines the behaviour of TAB and BACKSPACE keys with expandtab.
set shiftwidth=4                          " Determines the results of >>, <<, and ==.

au FileType julia setlocal expandtab      " Replaces tabs with spaces.
au FileType julia setlocal colorcolumn=93 " Highlights column 93 to help maintain the 92 character line limit.

По умолчанию Vim распознает файлы .jl как написанные на Лиспе. Чтобы файлы Julia правильно распознавались в Vim, можно настроить проверку расширения .jl вручную, но лучше установить подключаемый модуль Julia-Vim, который также включает в себя выделение синтаксиса и ряд других полезных функций.

Настройки Atom

В Atom предпочтительная длина строки по умолчанию составляет 80 символов. В Julia она должна составлять 92 символа. Изменить эту настройку можно так:

  1. Выберите Atom -> Preferences -> Packages.

  2. Найдите пакет language-julia и откройте его настройки.

  3. В разделе Julia Grammar (Грамматика Julia) найдите параметр Preferred Line Length (Предпочтительная длина строки) и измените его значение на 92.

Форматирование кода

Имена функций

Имена функций должны описывать действие или свойство, но не тип аргумента; эта информация указывается в самом типе аргумента. Например, вместо buyfood(food) должно быть buy(food::Food).

Имена функций, как правило, должны состоять из одного или двух слов в нижнем регистре. В идеале следует использовать имя buyfood, а не buy_food, но если имя функции хуже читается без символов подчеркивания, добавьте их.

Определения методов

Используйте определения функций в краткой форме, только если они помещаются в одной строке:

# Да:
foo(x::Int64) = abs(x) + 3
# Нет:
foobar(array_data::AbstractArray{T}, item::T) where {T<:Int64} = T[
    abs(x) * abs(item) + 3 for x in array_data
]

# Нет:
foobar(
    array_data::AbstractArray{T},
    item::T,
) where {T<:Int64} = T[abs(x) * abs(item) + 3 for x in array_data]
# Да:
function foobar(array_data::AbstractArray{T}, item::T) where T<:Int64
    return T[abs(x) * abs(item) + 3 for x in array_data]
end

В определениях функций в развернутой форме всегда используйте ключевое слово return:

# Да:
function fnc(x)
    result = zero(x)
    result += fna(x)
    return result
end
# Нет:
function fnc(x)
    result = zero(x)
    result += fna(x)
end

# Да:
function Foo(x, y)
    return new(x, y)
end
# Нет:
function Foo(x, y)
    new(x, y)
end

Если строка параметров в определении функции длиннее 92 символов, выносите каждый параметр в новую строку с отступом на один уровень:

# Да:
function foobar(
    df::DataFrame,
    id::Symbol,
    variable::Symbol,
    value::AbstractString,
    prefix::AbstractString="",
)
    # код
end

# Приемлемо:
function foobar(df::DataFrame, id::Symbol, variable::Symbol, value::AbstractString, prefix::AbstractString="")
    # код
end
# Нет:
function foobar(df::DataFrame, id::Symbol, variable::Symbol, value::AbstractString,
    prefix::AbstractString="")

    # код
end
# Нет:
function foobar(
        df::DataFrame,
        id::Symbol,
        variable::Symbol,
        value::AbstractString,
        prefix::AbstractString="",
    )
    # код
end

Именованные аргументы

При вызове функции всегда отделяйте именованные аргументы от позиционных точкой с запятой. Это позволит избежать ошибок в неоднозначных ситуациях (например, при распаковке Dict).

# Да:
xy = foo(x; y=3)
# Нет:
xy = foo(x, y=3)

Пробел

Избегайте лишних пробелов в следующих ситуациях.

  • Сразу после открывающей круглой, квадратной или фигурной скобки и перед закрывающей:

Yes: spam(ham[1], [eggs])
No:  spam( ham[ 1 ], [ eggs ] )
  • Перед запятой или точкой с запятой:

Yes: if x == 4 @show(x, y); x, y = y, x end
No:  if x == 4 @show(x , y) ; x , y = y , x end
  • При указании диапазонов, если только не используются дополнительные операторы:

Yes: ham[1:9], ham[1:3:9], ham[1:3:end]
No:  ham[1: 9], ham[1 : 3: 9]
Yes: ham[lower:upper], ham[lower:step:upper]
Yes: ham[lower + offset : upper + offset]
Yes: ham[(lower + offset):(upper + offset)]
No:  ham[lower + offset:upper + offset]
  • Не используйте несколько пробелов перед оператором присваивания (либо другим оператором) или после него для выравнивания с другим оператором:

# Да:
x = 1
y = 2
long_variable = 3

# Нет:
x             = 1
y             = 2
long_variable = 3
  • При использовании параметрических типов:

# Да:
f(a::AbstractArray{T, N}) where {T<:Real, N} = ...
g(a::AbstractArray{<:Real, N}) where {N} = ...

# Нет:
f(a::AbstractArray{T, N}) where {T <: Real, N} = ...
g(a::AbstractArray{<: Real, N}) where {N} = ...
  • Пробел должен стоять с каждой стороны следующих бинарных операторов: присваивание ( ), операторы изменения ( , и т. д.), операторы сравнения чисел ( , , , и т. д.). Обратите внимание, что данная рекомендация не относится к присваиваниям в определениях методов.

Yes: i = i + 1
No:  i=i+1

Yes: submitted += 1
No:  submitted +=1

Yes: x^2 < y
No:  x^2<y
  • В присваиваниях с развернутой формой записи массива, кортежа или функции первая открывающая скобка должна находиться в той же строке, что и оператор присваивания, а закрывающая скобка должна иметь тот же уровень отступа, что и присваивание. Присваивание можно записать и в одной строке, если оно достаточно короткое:

# Да:
arr = [
    1,
    2,
    3,
]
arr = [
    1, 2, 3,
]
result = Function(
    arg1,
    arg2,
)
arr = [1, 2, 3]


# Нет:
arr =
[
    1,
    2,
    3,
]
arr =
[
    1, 2, 3,
]
arr = [
    1,
    2,
    3,
    ]
  • При записи массивов или кортежей в развернутой форме открывающая и закрывающая скобки должны находиться на одном уровне отступа:

# Да:
x = [
    [
        1, 2, 3,
    ],
    [
        "hello",
        "world",
    ],
    ['a', 'b', 'c'],
]

# Нет:
y = [
    [
        1, 2, 3,
    ], [
        "hello",
        "world",
    ],
]
z = [[
        1, 2, 3,
    ], [
        "hello",
        "world",
    ],
]
  • При записи массивов, кортежей или функций в развернутой форме обязательно добавляйте запятую в конце. Это позволит в дальнейшем легко перемещать или добавлять элементы. При записи в одной строке запятая не нужна:

# Да:
arr = [
    1,
    2,
    3,
]
result = Function(
    arg1,
    arg2,
)
arr = [1, 2, 3]

# Нет:
arr = [
    1,
    2,
    3
]
result = Function(
    arg1,
    arg2
)
arr = [1, 2, 3,]
  • В тройных кавычках используется отступ строки с наименьшим отступом (кроме открывающих тройных кавычек). Это означает, что закрывающие тройные кавычки должны быть выровнены по строке с наименьшим отступом. Для тройных обратных апострофов следует придерживаться того же стиля несмотря на то, что отступы в этом случае не имеют значения.

# Да:
str = """
    hello
    world!
    """
str = """
        hello
    world!
    """
cmd = ```
    program
        --flag value
        parameter
      ```

# Нет:

str = """
    hello
    world! """

Комментарии

Comments should be used to state the intended behaviour of code. This is especially important when the code is doing something clever that may not be obvious upon first inspection. Avoid writing comments that state exactly what the code obviously does.

# Да:
x = x + 1      # Компенсация границы

# Нет:
x = x + 1      # Увеличиваем x

Лучше совсем не добавлять комментарии, чем использовать комментарии, противоречащие коду. Обязательно обновляйте комментарии при изменении кода!

Комментарии должны быть полными предложениями. Если комментарий представляет собой фразу или предложение, он должен начинаться с заглавной буквы, если только первым словом не является имя объекта, начинающееся с прописной буквы. Ни в коем случае не изменяйте регистр имен объектов!

В конце краткого комментария можно не ставить точку. Многострочные комментарии обычно состоят из одного или нескольких абзацев, состоящих из полных предложений. В конце каждого предложения необходимо ставить точку.

Комментарии должны отделяться от выражения по крайней мере двумя пробелами, а после символа # должен быть один пробел.

Имейте в виду, что при упоминании Julia в документации «Julia» означает язык программирования, а «julia» (обычно внутри обратных апострофов, например julia) означает исполняемый файл.

# Комментарий
code

# Еще один комментарий
more code

TODO

Документация

Для большинства модулей, типов и функций рекомендуется добавлять строки docstring. Однако документировать требуется только экспортируемые функции. Не документируйте такие методы, как ==, так как встроенные строки docstring для таких функций уже содержат необходимую информацию. По возможности старайтесь документировать функцию, а не отдельные методы, так как обычно у всех методов одинаковые строки docstring. При добавлении метода для функции, определенной в Base или другом пакете, добавляйте строку docstring только в том случае, если поведение вашей функции отличается от описанного в существующей строке docstring.

Строки docstring пишутся на языке Markdown и должны быть краткими. Строки в docstring следует переносить после 92 символов.

"""
    bar(x[, y])

Compute the Bar index between `x` and `y`. If `y` is missing, compute the Bar index between
all pairs of columns of `x`.
"""
function bar(x, y) ...

Если у типа или метода много параметров, краткого docstring может быть недостаточно. В таких случаях рекомендуется использовать приведенные ниже шаблоны. Обратите внимание: если раздел неприменим или избыточен (например, раздел «Выдает», если функция не выдает исключений), его можно исключить. Содержимое рекомендуется отделять от заголовка пустой строкой, если оно достаточно длинное. Старайтесь придерживаться единообразия при использовании дополнительных пробелов в docstring. Имейте в виду, что дополнительные пробелы отображаются только в исходном тексте на языке Markdown, но не при выводе.

Шаблон для типа (не следует использовать, если достаточно строки docstring конструктора):

"""
    MyArray{T,N}

My super awesome array wrapper!

# Поля
- `data::AbstractArray{T,N}`: stores the array being wrapped
- `metadata::Dict`: stores metadata about the array
"""
struct MyArray{T,N} <: AbstractArray{T,N}
    data::AbstractArray{T,N}
    metadata::Dict
end

Шаблон для функции (требуется только для экспортируемых функций):

"""
    mysearch(array::MyArray{T}, val::T; verbose=true) where {T} -> Int

Searches the `array` for the `val`. For some reason we don't want to use Julia's
builtin search :)

# Аргументы
- `array::MyArray{T}`: the array to search
- `val::T`: the value to search for

# Именованные аргументы
- `verbose::Bool=true`: print out progress details

# Возвращает
- `Int`: the index where `val` is located in the `array`

# Выдает
- `NotFoundError`: I guess we could throw an error if `val` isn't found.
"""
function mysearch(array::AbstractArray{T}, val::T) where T
    ...
end

Если у метода много аргументов или именованных аргументов, их можно исключить из сигнатуры метода в первой строке и использовать вместо этого args... и (или) kwargs....

"""
    Manager(args...; kwargs...) -> Manager

A cluster manager which spawns workers.

# Аргументы

- `min_workers::Integer`: The minimum number of workers to spawn or an exception is thrown
- `max_workers::Integer`: The requested number of worker to spawn

# Именованные аргументы

- `definition::AbstractString`: Name of the job definition to use. Defaults to the
    definition used within the current instance.
- `name::AbstractString`: ...
- `queue::AbstractString`: ...
"""
function Manager(...)
    ...
end

Несколько методов одной функции можно документировать в одной строке docstring. Но делать это следует только для собственных функций.

"""
    Manager(max_workers; kwargs...)
    Manager(min_workers:max_workers; kwargs...)
    Manager(min_workers, max_workers; kwargs...)

A cluster manager which spawns workers.

# Аргументы

- `min_workers::Int`: The minimum number of workers to spawn or an exception is thrown
- `max_workers::Int`: The number of requested workers to spawn

# Именованные аргументы

- `definition::AbstractString`: Name of the job definition to use. Defaults to the
    definition used within the current instance.
- `name::AbstractString`: ...
- `queue::AbstractString`: ...
"""
function Manager end

Если пункт списка в документации длиннее 92 символов, строку следует перенести с небольшим отступом. Не выравнивайте текст с символом :.

"""
...

# Именованные аргументы
- `definition::AbstractString`: Name of the job definition to use. Defaults to the
    definition used within the current instance.
"""

Дополнительные сведения о документировании кода в Julia см. в официальной документации.

Форматирование тестов

Наборы тестов

Julia предоставляет наборы тестов, которые позволяют разработчикам объединять тесты в логические группы. Наборы тестов можно вкладывать друг в друга, и в идеале у пакета должен быть только один «корневой» набор тестов. Желательно, чтобы в файле runtests.jl был корневой набор тестов, содержащий остальные тесты:

@testset "PkgExtreme" begin
    include("arithmetic.jl")
    include("utils.jl")
end

Структура файлов в папке test должна повторять структуру папки src. Для каждого файла в src необходим соответствующий файл в папке test, содержащий связанные тесты.

Сравнение

Большинство тестов пишутся в форме @test x == y. Так как функция == не учитывает типы, тесты наподобие следующего допустимы: @test 1.0 == 1. Не добавляйте ничего лишнего в тестовые сравнения:

# Да:
@test value == 0

# Нет:
@test value == 0.0

При проверке числовой допустимости оценочных значений параметров модели используйте функцию check_numerical из пакета test/test_utils/numerical_tests.jl. Она вычисляет оценочные значения параметров модели с использованием уровней погрешности atol и rtol. Тестирование будет производиться только в том случае, если вы запускаете набор тестов локально или если в Travis выполняется этап тестирования Numerical (Числовой).

Вот пример использования:

# Проверяем, находится ли отклонение m и s соответственно от 1,5 и 2,2 в пределах единицы.
check_numerical(chain, [:m, :s], [1.5, 2.2], atol = 1.0)

# Проверяет оценки для модели gdemo по умолчанию с использованием значений 1,5 и 2,0.
check_gdemo(chain, atol = 0.1)

# Проверяет оценки для модели MoG по умолчанию.
check_MoGtest_default(chain, atol = 0.1)