Lua style guide | Tarantool
Документация на русском языке
поддерживается сообществом

Lua style guide

Для вдохновения:

Programming style is art. There is some arbitrariness to the rules, but there are sound rationales for them. It is useful not only to provide sound advice on style but to understand the underlying rationale behind the style recommendations:

The Zen of Python is good. Understand it and use wisely:

Красивое лучше, чем уродливое.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Сложное лучше, чем запутанное.
Плоское лучше, чем вложенное.
Разреженное лучше, чем плотное.
Читаемость имеет значение.
Особые случаи не настолько особые, чтобы нарушать правила.
При этом практичность важнее безупречности.
Ошибки никогда не должны замалчиваться.
Если не замалчиваются явно.
Встретив двусмысленность, отбрось искушение угадать.
Должен существовать один – и, желательно, только один – очевидный способ сделать это.
Хотя он поначалу может быть и не очевиден.
Сейчас лучше, чем никогда.
Хотя никогда зачастую лучше, чем прямо сейчас.
Если реализацию сложно объяснить – идея плоха.
Если реализацию легко объяснить – идея, возможно, хороша.
Пространства имен – отличная штука! Сделаем побольше!

  • 4 spaces instead of tabs. PIL suggests using two spaces, but a programmer looks at code from 4 to 8 hours a day, so it’s simpler to distinguish indentation with 4 spaces. Why spaces? Similar representation everywhere.

    Можно использовать строки режима (modelines) vim:

    -- vim:ts=4 ss=4 sw=4 expandtab
    
  • Файл должен заканчиваться на один символ переноса строки, но не должен заканчиваться на пустой строке (два символа переноса строки).

  • Отступы всех do/while/for/if/function должны составлять 4 пробела.

  • Related or/and in if must be enclosed in the round brackets (). Example:

    if (a == true and b == false) or (a == false and b == true) then
        <...>
    end -- хорошо
    
    if a == true and b == false or a == false and b == true then
        <...>
    end -- плохо
    
    if a ^ b == true then
    end -- хорошо, но не явно
    
  • Преобразование типов

    Не используйте конкатенацию для конвертации в строку или в число (вместо этого воспользуйтесь tostring/tonumber):

    local a = 123
    a = a .. ''
    -- плохо
    
    local a = 123
    a = tostring(a)
    -- хорошо
    
    local a = '123'
    a = a + 5 -- 128
    -- плохо
    
    local a = '123'
    a = tonumber(a) + 5 -- 128
    -- хорошо
    
  • Постарайтесь избегать несколько вложенных if с общим телом оператора:

    if (a == true and b == false) or (a == false and b == true) then
        do_something()
    end
    -- хорошо
    
    if a == true then
        if b == false then
            do_something()
        end
    if b == true then
        if a == false then
            do_something()
        end
    end
    -- плохо
    
  • Избегайте множества конкатенаций в одном операторе, лучше использовать string.format:

    function say_greeting(period, name)
         local a = "good  " .. period .. ", " .. name
    end
    -- плохо
    
    function say_greeting(period, name)
        local a = string.format("good %s, %s", period, name)
    end
    -- хорошо
    
    local say_greeting_fmt = "good %s, %s"
    function say_greeting(period, name)
        local a = say_greeting_fmt:format(period, name)
    end
    -- лучше всего
    
  • Используйте and/or для указания значений переменных, используемых по умолчанию,

    function(input)
        input = input or 'default_value'
    end -- хорошо
    
    function(input)
        if input == nil then
            input = 'default_value'
        end
    end -- нормально, но избыточно
    
  • операторов if и возврата:

    if a == true then
        return do_something()
    end
    do_other_thing() -- хорошо
    
    if a == true then
        return do_something()
    else
        do_other_thing()
    end -- плохо
    
  • Использование пробелов:

    • Don’t use spaces between function name and opening round bracket. Split arguments with one whitespace character:

      function name (arg1,arg2,...)
      end -- плохо
      
      function name(arg1, arg2, ...)
      end -- хорошо
      
    • Add a space after comment markers:

      while true do -- inline comment
      -- comment
      do_something()
      end
      --[[
      multiline
      comment
      ]]--
      
    • Surrounding operators:

      local thing=1
      thing = thing-1
      thing = thing*1
      thing = 'string'..'s'
      -- плохо
      
      local thing = 1
      thing = thing - 1
      thing = thing * 1
      thing = 'string' .. 's'
      -- хорошо
      
    • Add a space after commas in tables:

      local thing = {1,2,3}
      thing = {1 , 2 , 3}
      thing = {1 ,2 ,3}
      -- плохо
      
      local thing = {1, 2, 3}
      -- хорошо
      
    • Add a space in map definitions after equals signs and commas:

      return {1,2,3,4}
      return {
          key1 = val1,key2=val2
      }
      -- плохо
      
      return {1, 2, 3, 4}
      return {
          key1 = val1, key2 = val2,
          key3 = vallll
      }
      -- хорошо
      

      You can also use alignment:

      return {
          long_key  = 'vaaaaalue',
          key       = 'val',
          something = 'even better'
      }
      
    • Extra blank lines may be used (sparingly) to separate groups of related functions. Blank lines may be omitted between several related one-liners (for example, a set of dummy implementations).

      Use blank lines in functions (sparingly) to indicate logical sections:

      if thing ~= nil then
          -- ...stuff...
      end
      function derp()
          -- ...stuff...
      end
      local wat = 7
      -- bad
      
      if thing ~= nil then
          -- ...stuff...
      end
      
      function derp()
          -- ...stuff...
      end
      
      local wat = 7
      -- good
      
    • Delete whitespace at EOL (strongly forbidden. Use :s/\s\+$//gc in vim to delete them).

Avoid using global variables. In exceptional cases, start the name of such a variable with _G, add a prefix, or add a table instead of a prefix:

function bad_global_example()
end -- глобальная, очень-очень плохо

function good_local_example()
end
_G.modulename_good_local_example = good_local_example -- локальная, хорошо
_G.modulename = {}
_G.modulename.good_local_example = good_local_example -- локальная, лучше

Always use a prefix to avoid name conflicts.

  • Names of variables/»objects» and «methods»/functions: snake_case.
  • Names of «classes»: CamelCase.
  • Private variables/methods (future properties) of objects start with underscores <object>._<name>. Avoid syntax like local function private_methods(self) end.
  • Boolean: naming is_<...>, isnt_<...>, has_, hasnt_ is good style.
  • For «very local» variables:
    • t is for tables
    • i, j are for indexing
    • n is for counting
    • k, v is what you get out of pairs() (are acceptable, _ if unused)
    • i, v is what you get out of ipairs() (are acceptable, _ if unused)
    • k/key is for table keys
    • v/val/value is for values that are passed around
    • x/y/z is for generic math quantities
    • s/str/string is for strings
    • c is for 1-char strings
    • f/func/cb are for functions
    • status, <rv>.. or ok, <rv>.. is what you get out of pcall/xpcall
    • buf, sz is a (buffer, size) pair
    • <name>_p is for pointers
    • t0.. is for timestamps
    • err is for errors
  • Abbreviations are acceptable if they’re very common or if they’re unambiguous and you’ve documented them.
  • Global variables are spelled in ALL_CAPS. If it’s a system variable, it starts with an underscore (_G/_VERSION/..).
  • Modules are named in snake_case (avoid underscores and dashes): for example, „luasql“, not „Lua-SQL“.
  • *_mt and *_methods defines metatable and methods table.

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

  • функции *.cfg{ } (box.cfg/memcached.cfg/..)
  • функция ffi.cdef[[ ]]

Avoid the following constructions:

  • <func>“<name>“. Strongly avoid require“..“.
  • function object:method() end. Use function object.method(self) end instead.
  • Semicolons as table separators. Only use commas.
  • Semicolons at the end of line. Use semicolons only to split multiple statements on one line.
  • Unnecessary function creation (closures/..).

Avoid implicit casting to boolean in if conditions like if x then or if not x then. Such expressions will likely result in troubles with box.NULL. Instead of those conditions, use if x ~= nil then and if x == nil then.

Don’t start modules with license/authors/descriptions, you can write it in LICENSE/AUTHORS/README files. To write modules, use one of the two patterns (don’t use modules()):

local M = {}

function M.foo()
...
end

function M.bar()
...
end

return M

или

local function foo()
...
end

local function bar()
...
end

return {
foo = foo,
bar = bar,
}

Don’t forget to comment your Lua code. You shouldn’t comment Lua syntax (assume that the reader already knows the Lua language). Instead, tell about functions/variable names/etc.

Многострочные комментарии: используйте соответствующие скобки (--[[ ]]--) вместо простых (--[[ ]]).

Public function comments:

--- Копирование любой таблицы (поверхностное и глубокое)
-- * deepcopy: копирует все уровни
-- * shallowcopy: копирует только первый уровень
-- Поддержка метаметода __copy для копирования специальных таблиц с метатаблицами
-- @function gsplit
-- @table         inp  оригинальная таблица
-- @shallow[opt]  sep  флаг для поверхностной копии
-- @returns            таблица (копия)

Use the tap module for writing efficient tests. Example of a test file:

#!/usr/bin/env tarantool

local test = require('tap').test('table')
test:plan(31)

do -- check basic table.copy (deepcopy)
    local example_table = {
        {1, 2, 3},
        {"help, I'm very nested", {{{ }}} }
    }

    local copy_table = table.copy(example_table)

    test:is_deeply(
        example_table,
        copy_table,
        "checking, that deepcopy behaves ok"
    )
    test:isnt(
        example_table,
        copy_table,
        "checking, that tables are different"
    )
    test:isnt(
        example_table[1],
        copy_table[1],
        "checking, that tables are different"
    )
    test:isnt(
        example_table[2],
        copy_table[2],
        "checking, that tables are different"
    )
    test:isnt(
        example_table[2][2],
        copy_table[2][2],
        "checking, that tables are different"
    )
    test:isnt(
        example_table[2][2][1],
        copy_table[2][2][1],
        "checking, that tables are different"
    )
end

<...>

os.exit(test:check() and 0 or 1)

When you test your code, the output will be something like this:

TAP version 13
1..31
ok - checking, that deepcopy behaves ok
ok - checking, that tables are different
ok - checking, that tables are different
ok - checking, that tables are different
ok - checking, that tables are different
ok - checking, that tables are different
...

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

With error handling, this means that you must provide an error object as the second multi-return value in case of error. The error object can be a string, a Lua table, cdata, or userdata. In the latter three cases, it must have a __tostring metamethod defined.

В случае ошибки нулевое значение nil должно быть первым возвращаемым значением. В таком случае ошибку трудно игнорировать.

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

local data, err = foo()
if data == nil then
    return nil, err
end
return bar(data)

Unless the performance of your code is paramount, try to avoid using more than two return values.

In rare cases, you may want to return nil as a legal return value. In this case, it’s OK to check for error first and then for return:

local data, err = foo()
if err == nil then
    return data
end
return nil, err

Для проверки стиля кода Tarantool использует luacheck. Он анализирует различные аспекты кода, например неиспользуемые переменные, и иногда проверяет больше аспектов, чем нужно. Поэтому есть соглашение игнорировать некоторые предупреждения, которые выдает luacheck:

"212/self",   -- Неиспользуемый аргумент <self>.
"411",        -- Переопределение локальной переменной.
"421",        -- Переопределение локальной переменной на вложенном уровне.
"431",        -- Переопределение значения, заданного ранее, в функции.
"432",        -- Перегрузка аргумента на вложенном уровне.