Skip to content

Latest commit

 

History

History
1180 lines (819 loc) · 43.8 KB

lua-guidelines.md

File metadata and controls

1180 lines (819 loc) · 43.8 KB

Lua coding guidelines

Приоритеты

  • Читаемость кода (в том числе единообразие).
  • Уменьшение вероятности возникновения ошибок.
  • Минимизация числа изменённых строк с точки зрения системы контроля версий при модификации кода.
  • Производительность кода.

Старый код

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

Code conventions

Именование переменных

Никакой "венгерской нотации". (Имеется в виду запрет на формальную Systems Hungarian нотацию, в противовес содержательной Apps Hungarian. http://en.wikipedia.org/wiki/Hungarian_notation#Systems_vs._Apps_Hungarian)

Имена переменных не должны перекрывать стандартное пространство имен (table, string, os и т.д.)

В именах переменных не рекомендуется использовать неустоявшиеся сокращения. Рекомендуется использовать идиоматические имена переменных в подходящих случаях. Некоторые примеры:

local res, err = foo()
local ok, res, err = xpcall(function() return foo() end)

Константы именуются ЗАГЛАВНЫМИ_БУКВАМИ.

THE_ULTIMATE_ANSWER = 42

Обычные переменные, имена функций и проч. — через_подчёркивание, без заглавных букв.

SomeLongName(args) -> some_long_name(args)

Имя переменной, обычно, существительное.

Имя переменной, содержащей объект, должно содержать название этого объекта (см. XIV.3. Фабрики). (Обычно такая переменная — таблица.)

Имя переменной (таблицы), содержащей в себе коллекцию объектов, обычно содержит существительное во множественном числе (foos — коллекция объектов foo).

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

Имя булевой переменной, обычно, is_* (are_*, has_* и проч. формы to be), have_*, need_*, should_*, must_*, in_*, not_* и т.п.

Количество объектов: num_*.

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

Имя функции должно заключать в себе глагол либо заканчиваться на существительное с окончанием -er (чаще всего когда подразумевается "функтор"), кроме специальных случаев и декларативного кода.

Предикаты: is_ Фабрики: make_<object_name>

Запрещается использовать зарезервированный под фабрики префикс make_ для функций, не имеющих отношения к созданию объектов. Для таких функций рекомендуется префикс create_.

Кавычки

Рекомендуемые кавычки по-умолчанию — апострофы. Если в строке содержатся апострофы — использовать для данной строки кавычки по своему усмотрению.

Пробельные символы

В коде нельзя использовать символы табуляции (кроме мэйкфайлов). Если в строковом литерале нужен символ табуляции, используйте

"\t".

В коде нужно использовать отступы шириной два пробела ("единичный отступ"), кроме кода, который будет использоваться внутри Metalua. Такой код нужно обозначить в комментариях в начале файла и использовать отступы в три пробела.

В конце строки не должно быть пробельных символов.

Максимальная длина строки — 80 символов.

Нельзя ставить подряд больше одного пробела (кроме как для отступов в начале строки и между "столбцами" в "табличных" данных).

Между кодом и комментарием в той же строке обычно идёт один пробел, если только идущие подряд комментарии не выстроены по вертикали по какой-то причине.

После каждой запятой и точки с запятой должен стоять пробел. Перед запятой и точкой с запятой — не должно быть пробела.

a, b, c

Все бинарные операторы (=, ~=, <, >, +, -, *, /, and, or, ..) должны отделяться пробелами слева и справа.

a = b
a > b
a .. b

Оператор # и унарный минус не отделяются пробелом от операнда, но отделяются пробелом слева.

a > #b
a = -b

Оператор not отделяется пробелами слева и справа.

a = not b

Нельзя отделять пробелами скобки при вызове функций (кроме специальных декларативных форм записи).

a() + b()

После открывающей и перед закрывающей скобками пробел не ставится.

a(b)

После открывающей фигурной скобки и перед закрывающей фигурной — ставится.

a({ b })
{ a, b }

Файл должен завершаться символом перевода строки.

Пустые строки

Нельзя делать подряд больше одной пустой строки.

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

Настоятельно рекомендуется пустая строка до и после begin-end-подобных конструкций, исключая случаи, когда begin-ы или end-ы идут подряд:

if alpha then
  if beta then
    gamma()

    for delta = 1, 100 do
      epsilon()
    end

    while zeta() do
      if eta() then
        if theta() then
          iota()
        end

        kappa()
      end
    end
  end
end

Правила расстановки отступов

По умолчанию следует избегать разницы в отступах на соседних непустых строках большей чем в один отступ (кроме специально оговорённых случаев).

При "разрыве" выражения и переносе части его на следующую строку в ней делается один дополнительный отступ (кроме специально оговорённых случаев). Если выражение разбито на несколько строк, все строки кроме самой первой начинаются с одного дополнительного отступа.

some_long_name_operand = some_long_name_operand_number_one
  + some_long_name_operand_number_two + some_long_name_operand_number_three
  + some_long_name_operand_number_four

В табличных, столбчатых и прочих подобных данных допускается расстановка отступов по необходимости для сохранения столбцов, если это повышает читаемость.

some_key       = "some_typical_value_1"
some_other_key = "some_typical_value_2"
third_key      = "some_typical_value_3"
...
last_key       = "some_typical_value_4"

При многострочной записи код внутри function-end, do-end, then-end, repeat-until и подобных конструкций сдвигается вправо на один отступ.

while
  some_long_name_operand ==
    other_long_name_operand another_long_name_operand
do
  some_stuff()
end

При многострочной записи конструктора таблицы открывающая и закрывающая фигурные скобки пишутся на новых строках с отступом, равным отступу первой непустой строки перед открывающей скобкой.

local t =
{
  some_key       = "some_typical_value_1";
  some_other_key = "some_typical_value_2";
  third_key      = "some_typical_value_3";
  ...
  last_key       = "some_typical_value_4";
}

Допускается сдвиг вправо на один отступ кода, находящегося между begin-end-подобными парными конструкциями.

TODO: example

При разделении вызова функции на несколько строк аргументы функции сдвигаются на один отступ. Открывающую скобку желательно ставить на той же строке, что и имя функции. Желательно в этом случае писать каждый аргумент функции на новой строке. Отступы в объявлении функции оформляются аналогично.

long_function_name(
  with,
  many,
  many,
  parameters
)

Если при вызове функции ей передаётся единственный аргумент, и этот аргумент — анонимная функция, создаваемая конструкцией function-end непосредственно в момент вызова, тело функции сдвигается на ОДИН отступ (а не на два). Ключевое слово function пишется непосредственно после открывающей скобки вызова, без пробела между ними. Соответствующее ключевому слову function ключевое слово end пишется с тем же отступом, что и строка, на которой написано function. Закрывающая скобка вызова ставится непосредственно после end-а, без пробела между ними. Допускается запись в одну строку.

fn(function(args)
  return some_stuff()
end)

foo(function(args) body() end)

Некоторы комплексные и смешанные примеры (добавляем по мере возникновения вопросов):

assert(
  action_handlers[action.tool],
  "unknown tool"
)(
  manifest,
  cluster_info,
  param,
  machine,
  role_args,
  action
)
machine.installed_rocks_set, machine.duplicate_rocks_set
    = luarocks_parse_installed_rocks(
        remote_luarocks_list_installed_rocks(
          machine.external_url
        )
      )
  1. Допускается свешивание .. при переносах:

    writeln_flush(
        "-> blablablablablablablablablabla "
     .. blabla_blabla
     .. "': blablablablablablablablablabla"
    )
    
longname_a = longname_b
  [logic_operand] longname_c
if
  long_function_name(
    with,
    many,
    many,
    parameters
  ) == other_long_function(
    the,
    same
  )
then
  body
end
local func_name = function(
  param1,
  param2,
  longname3
)
  body
end

Директива import

Для разбиения по строкам директивы import применяется специальное форматирование, сложившееся исторически.

Каждая присваиваемая переменная пишется на отдельной строке. Список переменных выравнивается по имени первой переменной.

Сама директива import пишется на отдельной строке вместе с именем импортируемой библиотеки.

Открывающая и закрывающая фигурные скобки для списка импорта располагаются на отдельных строках с отступом предыдущей строки.

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

Пример:

local arguments,
      optional_arguments,
      method_arguments
      = import 'lua-nucleo/args.lua'
      {
        'arguments',
        'optional_arguments',
        'method_arguments'
      }

Если список импорта состоит из одного элемента, то директива import может форматироваться либо в одну строку, с соблюдением всех общих правил, либо разбиваться по строкам, как описано выше.

Пример:

local is_table = import 'lua-nucleo/type.lua' { 'is_table' }

local is_table
      = import 'lua-nucleo/type.lua'
      {
        'is_table'

Многострочные константы

Для строк, заключенных в long brackets, правила для отступов не действуют. Отступы расставляются так, чтобы при выводе строка выглядела так, как нужно.

Пример:

  io.stdout:write([[

Config format:

]])

  io.stdout:flush()

Другие многострочные конструкции

Многострочный return с конструкцией ... and ... or ... выравнивается одним из двух способов:

Правильно:

return foo
  and bar
   or baz

Тоже правильно:

return foo
   and bar
    or baz

Многострочные условия if-then, while-do и for-do оформляются либо с одинарным отступом, либо с выравниванием по пробелу после логического оператора:

Правильно:

if
  foo and
  bar
then
  ...
end

Тоже правильно:

if
      foo
  and bar
then
  ...
end

При переносе конкатенации .. допускается нестандартный сдвиг с выравниванием по аргументам:

log(
    "bar"
 .. " baz"
)

Избыточный синтаксис

Нельзя ставить скобки вокруг условия в if-then, while-do, for-do, repeat-until.

if(a == b) then -> if a == b then

Но следует ставить скобки вокруг булевых операций в других случаях:

return (foo and bar)
local a = (foo or bar)

но

f(foo BOOLOP bar, baz)

Синтаксический сахар

Если функция не рекурсивная, используется форма определения a = function() end.

local a = function(args)
  ...
end

Если функция рекурсивная — function a() end.

local function a(args)
  ...
end

При вызове методов нужно пользоваться оператором :.

t.method(t, args) -> t:method(args)

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

TODO: example

В коде, написанном в декларативном стиле, желательно опускать скобки при передаче строковых литералов и конструкторов таблиц в качестве аргументов функций. (В т. ч. import.)

Скобки при вызове функции с табличным литералом рекомендуется опускать в следующих случаях

  1. При декларативной записи.
  2. Когда функция трансформирует таблицу в другую таблицу (пример: tset())
  3. Когда функция сериализует таблицу (пример: luabins.save())

Нельзя опускать скобки, когда функция просто работает с таблицей (к примеру, count_number_of_keys()).

Правильно:

api:input { };

api:input
{
};

Неправильно:

api:input
{ };

Циклы

  • Запрещается использовать ipairs(). Вместо него — numeric for.

  • По возможности нужно избегать конкатенации в цикле с использованием переменной-аккумулятора. Вместо этого используйте concatter.

    Нежелательно:

      local s = ""
      for ... do
        s = s .. "something" .. foo()
      end
      return s
    

    Желательно:

      local cat, concat = make_concatter()
      for ... do
        cat "something" (foo())
      end
      return concat()
    
  • Численный цикл в Lua, если нет особых оснований делать по-другому, начинается с 1. Мотивация: идиома языка, в Lua считают с 1.

  • Не рекомендуется начинать численный цикл с 0. В исключительных случаях, когда это нужно, обязателен развёрнутый комментарий, поясняющий, почему нарушена рекомендация.

    Нежелательно:

      for i = 0, limit do ... end
    

    Желательно:

      for i = 1, limit + 1 do ... end
    

    Мотивация: в Lua лимит численного цикла проверяется на <=, в других языках общепринято <, если цикл начинается с 0, сложно заметить, что цикл имеет на одну итерацию больше.

  • Если подряд идёт два сходных цикла, зависящих от общего лимита, рекомендуется начинать второй цикл с того места, где остановился первый:

      for i = 1, half_limit do ... end
      for i = half_limit + 1, limit do ... end
    

    Мотивация: более наглядное представление назначения второго цикла в коде и связи первого цикла со вторым.

  • Настоятельно не рекомендуется использовать цикл repeat ... until.

    Мотивация: плохая читаемость, необходимо проглядывать всё тело цикла, чтобы понять, что происходит.

    В исключительных случаях, когда repeat ... until необходим для повышения читаемости кода, необходимо оставить развёрнутый комментарий с обоснованием.

    Избавление от дублирования кода не признаётся обоснованием для использования repeat ... until.

Функции

Проверка аргументов

Штатная проверка аргументов

local is_log_enabled_raw = function(
  modules_config,
  levels_config,
  module_name,
  level
)
  arguments(
    "table", levels_config,
    "table", modules_config,
    "string", module_name,
    "number", level
  )
  ...
end

Аргументы по умолчанию

local foo = function(bar)
  bar = bar or BAZ
  arguments(
    "quo", bar
  )

Конструкторы таблиц

Если таблица создаётся на одной строке, желательно отделять её элементы запятыми. Если список разделён запятыми, в конце списка "висящая" запятая не ставятся.

local ks = { "k1", "k2", "k3" }

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

local items =
{
  "item1";
  "item2";
  "item3";
}

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

local letters =
{
  "a", "b", "c", "d", "e";
  "f", "g", "h", "i", "j";
  "k", "l", "m", "n", "o";
  "p", "q", "r", "s", "t";
  "u", "v", "w", "x", "y";
  "z";
}

-- Конкретно это должно было бы быть записано при помощи tset().
local lua51_keywords =
{
  ["and"] = true,    ["break"] = true,  ["do"] = true;
  ["else"] = true,   ["elseif"] = true, ["end"] = true;
  ["false"] = true,  ["for"] = true,    ["function"] = true;
  ["if"] = true,     ["in"] = true,     ["local"] = true;
  ["nil"] = true,    ["not"] = true,    ["or"] = true;
  ["repeat"] = true, ["return"] = true, ["then"] = true;
  ["true"] = true,   ["until"] = true,  ["while"] = true;
}

Видимость переменных

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

Глобальные переменные запрещены (кроме специальных случаев). Любой случай введения новой глобальный переменной должен утверждаться отдельно и документироватья в коде.

Видимость переменных в глобальном scope файла следует максимально ограничивать блоками do-end.

local some_stuff
do
  local some_local_func = function(...)
  end

  some_stuff = function(args)
    ...
    some_local_func(...)
    ...
  end
end

Не следует повторно использовать одну и ту же переменную для разных целей (кроме документированных случаев, где это необходимо из-за сверхжёстких требований по производительности — на практике таких случаев пока не было).

Имена переменных не должны перекрывать имена переменных во внешних scope'ах (допускаются обоснованные исключения, например в случаях, когда это улучшает читаемость кода).

Стандартные глобальные переменные (например, print, error) желательно "кешировать" в локальные в начале файла: local print, error = print, error. Это позволяет создать код, который будет без "из коробки" работать внутри sandbox'а с заменённым глобальным окружением.

local type, setmetatable, tostring, select, assert, unpack
    = type, setmetatable, tostring, select, assert, unpack

local os_time, os_date
    = os.time, os.date

Описание объектов

Для создания объектов с однотипным поведением (методами) cледует использовать фабрики, которые должны быть написаны по следующему формату:

local make_myobject
do
  local method = function(self, args)
  end

  make_myobject = function(args)
    return
    {
      method = method;
      --
      private_variable_ = 42;
    }
  end
end

Поля таблицы с подчеркиванием в конце — приватные данные.

В особых случаях, с разрешения техлида допускаются способы с использованием метатаблиц или upvalue.

Пример с метатаблицей:

local make_myobject
do
  local method = function(self, args)
  end

  local mt =
  {
    method = method;
  }
  mt = { __index = mt }

  make_myobject = function(args)
    return setmetatable(
      {
        private_variable_ = 42;
      },
      mt
    )
  end
end

Пример с upvalue:

local make_myobject
do
  make_myobject = function(args)

    local self =
    {
      private_variable_ = 42;
    }

    return
    {
      method = function(args)
      end;
    }
  end
end

FFI

Основы

Объявление

Введение

--------------------------------------------------------------------------------
-- ffi/myfoo.lua: libmyfoo bindings
-- <стандартная шапка>
--------------------------------------------------------------------------------

local ffi = require 'ffi'

--------------------------------------------------------------------------------

Если биндим стандартные библиотеки, уже прилинкованные к luajit2 (libc, libm):

local myfoo = ffi.C

Если биндим внешнюю библиотеку libmyfoo.so:

local myfoo = ffi.load('libmyfoo.so.0')

Далее

--------------------------------------------------------------------------------
-- Based on myfoo.h
--------------------------------------------------------------------------------

ffi.cdef([[
... adapted myfoo.h content
]])

--------------------------------------------------------------------------------

return
{
  myfoo = myfoo;
}

Использование

local myfoo = import 'ffi/myfoo.lua' { 'myfoo' }
myfoo.foofunc(...)

Все определения FFI должны быть сосредоточены в библиотечном коде. Запрещается прямое использование ffi.C.* и подобных вызовов в клиентском коде.

Этот документ пока не определяет, как быть с низкоуровневыми функциями FFI, такими как ffi.string, ffi.copy и т.п.

Модули

Импортируемые модули следует кешировать локально.

Пример:

  local socket = require 'socket'

Разделение кода на файлы

TODO

Общая культура программирования

  • В коде следует избегать использования безымянных "магических констант": http://en.wikipedia.org/wiki/Magic_number_(programming)#Unnamed_numerical_constants

  • Запрещается пренебрегать штатными средствами экранирования при динамическом составлении запросов в базу.

Статические переменные и объекты

  • Не рекомендуется использовать статические переменные.

    Нежелательно:

    local state
    ...
    local foo = function(bar)
      state = bar
    end
    ...
    local baz = function()
      print(state)
    end
    

    Исключение — для системных объектов-синглтонов, с соответствующим комментарием, при наличии разрешения техлида.

    Мотивация — вредит реюзу кода.

  • Настоятельно не рекомендуется использовать статические объекты.

    Нежелательно:

    local fooer
    do
      fooer =
      {
        bar_ = 42;
      }
    end
    

    У объекта в обязательном порядке должна быть фабрика.

    Мотивация — вредит реюзу кода, усложняет рефакторинг.

Тесты

Именование тестов

Имя теста должно описывать, что проверяет тест. Имя должно писаться без пробелов; отдельные слова в имени разделяются дефисом.

Примеры:

test:case "cookie-management" (function()
  ...

test "POST-user-defined-Content-Type" (function()
  ...

Идентификаторы в имени теста пишутся как есть.

Пример:

test:case "shell_escape-empty-string" (function()
  ensure_strequals("shell_escape for an empty string", shell_escape(""), "''")
end)

Правильной записью для конструктора сюиты является:

local test = (...)("TEST_NAME"[, exports])

Пример:

local test = (...)("apigen")

Имена файлов с тестами начинаются с порядкового четырехзначного номера вида NNNM, в котором старшие три разряда NNN используются для упорядочивания групп тестов, а младший M — для задания порядка тестов в одной группе. (В большинстве случаев используется один файл в группе, и фактически идет нумерация с шагом 10. В случае сомнений лучше спросить лида).

Номера вида 000N зарезервированы для "does it work" тестов.

Пример:

test/cases/0010-example.lua
test/cases/0020-first-test-in-group.lua
test/cases/0021-second-test-in-group.lua

Следует использовать префикс ensure_ для библиотечных функций проверки каких-либо утверждений с первым аргументом – текстом утверждения.

  local ensure_strequals = function(msg, actual, expected) ... end

Префикс check_ следует использовать в остальных случаях библиотечных тестовых функций, часто являет собой набор вызовов ensure_*-like функций.

Отступы при оформлении тестов

Если у теста очень длинное имя и function() не помещается на той же строке, рекомендуется как-нибудь сократить имя, чтобы function() всё-таки уместилось.

Не рекомендуется:

test:case "test-case-with-a-really-really-really-...-really-long-name" (
  function()
    ...
  end
)

Рекомендуется:

test:case "same-test-but-the-name-is-shortened-a-little" (function()
  ...
end)

Если тест создаётся с декораторами, и декоратор слишком длинный, такой что строка :with(...DECORATOR...) (function() не умещается в допустимую длину строки, рекомендуется разбить этот декоратор на строки как function call.

Нежелательно (слишком большой отступ у тела теста):

test:case "some-test-case"
  :with(temporary_directory("tmpdir", "tmp", "/long/long/path/to/dir")) (
    function(env)
      ...
    end
  )

Тоже нежелательно (неправильный отступ у анонимной функции):

test:case "some-test-case"
  :with(temporary_directory("tmpdir", "tmp", "/long/long/path/to/dir")) (
function(env)
  ...
end)

Хорошо:

test:case "some-test-case"
  :with(
    temporary_directory(
      "tmpdir",
      "tmp",
      "/long/long/path/to/dir"
    )
  ) (function(env)
  ...
end)

Тестовые значения и сообщения об ошибках

В коде тестов рекомендуется вместо assert() использовать специализированные функции из lua-nucleo/ensure.lua.

Для тестовых строковых значений рекомендуется использовать что-нибудь всем хорошо знакомое, например "Lorem ipsum...".

local x = "Lorem " .. "ipsum"
ensure_equals("concatenation works", x, "Lorem ipsum")

Сообщение в ensure*() должно отражать суть проверяемого факта:

Плохо:

local status = os.execute("./script")
ensure_equals("okay", status, 0)

Хорошо:

local status = os.execute("./script")
ensure_equals("script executed successfully", status, 0)

Если по сценарию теста необходимо (например, для тестирования обработки ошибок) вызвать программную ошибку (error, return nil, err и подобные), особенно такую, обработка которой вызывает запись в лог, то в тексте такой ошибки нужно явно указывать, что ошибка — ожидаемая (при наличии такой возможности).

Пример:

ensure_returns(
  "some check",
  2,
  { false, "expected error" },
  xpcall(
    function()
      error("expected error")
    end,
    function(msg)
      log_error("foo failed:",  debug.traceback(foo))
      return foo
    end
  )
)

Устаревшие и запрещенные выражения языка

Текущим стандартом языка является Lua 5.1. Нельзя использовать в коде выражения и методы, которые были объявлены в нем устаревшими, даже если они еще работают. К таким в частности относятся:

  • arg (таблица переменных, переданных в функцию открытым списком) — вместо неё надо использовать select(n, ...)
  • string.len — вместо него надо использовать оператор длины #
  • table.getn — вместо него надо использовать оператор длины #
  • ipairs — вместо него следует использовать numeric for

Некоторые выражения и методы нельзя использовать в коде по соображениям его безопасности. К таким в частности относятся:

Подключение логгеров

Логгеры должны присутствовать в каждом файле каждой библиотеки ниже lua-aplicado.

Логгеры подключаются в самом начале файла так:

local log, dbg, spam, log_error
     = import 'LIBRARY/log.lua' { 'make_loggers' } (
         "update-subtrees", "UST"
       )

Логгеры в файлах тестов именуются аналогично примеру ниже:

local log, dbg, spam, log_error
     = import 'LIBRARY/log.lua' { 'make_loggers' } (
         "test/config_dsl", "T001"
       )

Вместо LIBRARY подставьте имя библиотеки с нужным вариантом make_loggers.

Если правите файл, в котором они подключены по старой схеме (с отдельным импортом make_loggers), желательно отдельным коммитом привести его в современный вид.

Использование логгеров

В логгерах нет необходимости использовать функции преобразования к строке. Это происходит автоматически

Неверно:

local t = { key = "value" }
local n = 123
local b = false
log(tstr(t), tostring(n), tostring(b))

Верно:

local t = { key = "value" }
local n = 123
local b = false
log(t, n, b)

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

Неверно:

log("values:\nb =", b, "\n c =", c)

Верно:

log("values: b =", b, "; c =", c)

Используйте адекватные ситуации логгеры, для ошибок — log_error, для рутиннной информации — log, для отладки — dbg.

Неверно:

log("values: b =", b)
log("THIS IS ERROR!!! b =", b)
log("debug output: b =", b)

Верно:

log("values: b =", b)
log_error("wrong value b =", b)
dbg("b =", b) -- TODO: remove after debug is over

По возможности не используйте конкатенацию внутри логгеров, передавайте информацию в виде набора переменных.

Неверно:

log("values: b = " .. b .. "; c = " .. c)

Верно:

log("values: b =", b, "; c =", c)

Нежелательно писать содержимое переменной в лог без поясняющего текста.

Неверно:

log(b, c)

Верно:

log("values: b =", b, "; c =", c)

Обязательное логирование

  1. math.randomseed при его установке в какое-либо значение

Обработка ошибок

При вызове ошибки стоит использовать error(...), а не assert(nil, ...). Данная рекомендация введена для улучшения читаемости кода и не имеет иного практического смысла.

Рекомендуется оставлять комментарий o сути произошедшей ошибки.

Неверно:

assert(a)

Верно:

assert(a, "some bad things happened")

Если вам пришёл баг-репорт со стектрейсом, в котором стоит assert без error message, необходимо в обязательном порядке добавить в код error message.

Если комментарий в assert(...) требует конкатенации, то из соображений производительности стоит использовать if not + log_error + error.

Неверно:

assert(
  a == b,
  "value of a: " .. tstr(a) .. "  is not equal to value of b: " .. tstr(b)
)

Верно:

if not a == b then
  log_error("value of a:", a, "value of b:", b)
  error("a not equal b")
end

luarocks rockspecs

Пустой сорс разрешен только с комментарием:

source =
{
  url = ""; -- Installable with `luarocks make` only
}

Для форматирования рокспеков использовать те же стандарты, что используются для прочего луашного кода.

Пути к файлам/директориям и работа с ними

  • Пути к файлам/директориям записываются без финального слэша.

  • Код, обрабатывающий пути, не должен ожидать, что путь заканчивается на слэш, и строить на этом свою логику.

  • Запрещается использовать конкатенацию для склеивания строк, представляющих пути к файлам или директориям. Вместо этого надо использовать join_path.

    Неправильно:

    doc_md_filename = PROJECT_PATH .. "doc/apigen/client_api.md"
    

    Правильно:

    doc_md_filename = join_path(PROJECT_PATH, "doc/apigen/client_api.md")
    

    Мотивация: избавляемся от проблем, связанных с ожиданием финального слэша в путях.

If restrictions

  • Не разрешено писать условные конструкции в одну строку:

    Правильно:

    if var then
      print(var)
    end
    

    Неправильно:

    if var then print(var) end