Руководство по написанию кода на Lua
Для вдохновения:
- https://github.com/Olivine-Labs/lua-style-guide
- http://dev.minetest.net/Lua_code_style_guidelines
- http://sputnik.freewisdom.org/en/Coding_Standard
Придерживаться стиля в программировании – это искусство. Даже учитывая некоторую произвольность правил, для них есть надежное обоснование. Полезно не только давать значимые советы по стилю, но также понимать основополагающие причины и человеческий аспект того, почему формируются рекомендации по стилю:
- http://mindprod.com/jgloss/unmain.html
- http://www.oreilly.com/catalog/perlbp/
- http://books.google.com/books?id=QnghAQAAIAAJ
Дзен языка программирования Python подходит и здесь; используйте его с умом:
Красивое лучше, чем уродливое.Явное лучше, чем неявное.Простое лучше, чем сложное.Сложное лучше, чем запутанное.Плоское лучше, чем вложенное.Разреженное лучше, чем плотное.Читаемость имеет значение.Особые случаи не настолько особые, чтобы нарушать правила.При этом практичность важнее безупречности.Ошибки никогда не должны замалчиваться.Если не замалчиваются явно.Встретив двусмысленность, отбрось искушение угадать.Должен существовать один – и, желательно, только один – очевидный способ сделать это.Хотя он поначалу может быть и не очевиден.Сейчас лучше, чем никогда.Хотя никогда зачастую лучше, чем прямо сейчас.Если реализацию сложно объяснить – идея плоха.Если реализацию легко объяснить – идея, возможно, хороша.Пространства имен – отличная штука! Сделаем побольше!
4 пробела, а не табуляция. Библиотека PIL предлагает использовать два пробела, но разработчик читает код от 4 до 8 часов в день, а различать отступы с 4 пробелами легче. Почему именно пробелы? Соблюдение однородности.
Можно использовать строки режима (modelines) vim:
-- vim:ts=4 ss=4 sw=4 expandtab
Файл должен заканчиваться на один символ переноса строки, но не должен заканчиваться на пустой строке (два символа переноса строки).
Отступы всех do/while/for/if/function должны составлять 4 пробела.
or
/and
вif
должны быть обрамлены круглыми скобками (). Пример: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 -- плохо
Использование пробелов:
не следует вставлять пробелы между именем функции и открывающей круглой скобкой, но аргумент необходимо разделять одним символом пробела
function name (arg1,arg2,...) end -- плохо function name(arg1, arg2, ...) end -- хорошо
добавляйте пробел после маркера комментария
while true do -- встроенный комментарий -- комментарий do_something() end --[[ многострочный комментарий ]]--
примыкающие конструкции
local thing=1 thing = thing-1 thing = thing*1 thing = 'string'..'s' -- плохо local thing = 1 thing = thing - 1 thing = thing * 1 thing = 'string' .. 's' -- хорошо
добавляйте пробел после запятых в таблицах
local thing = {1,2,3} thing = {1 , 2 , 3} thing = {1 ,2 ,3} -- плохо local thing = {1, 2, 3} -- хорошо
используйте пробелы в определениях ассоциативного массива по сторонам от знаков равенства и запятых
return {1,2,3,4} -- плохо return { key1 = val1,key2=val2 } -- плохо return { 1, 2, 3, 4 key1 = val1, key2 = val2, key3 = vallll } -- хорошо
также можно применить выравнивание:
return { long_key = 'vaaaaalue', key = 'val', something = 'even better' }
также можно добавлять пустые строки (не слишком часто) для выделения групп связанных функций. Пустые строки не стоит добавлять между несколькими связанными программами в одну строку (например, в формальной реализации)
не слишком часто можно добавлять пустые строки в коде функций, чтобы отделить друг от друга логические части
if thing then -- ...что-то... end function derp() -- ...что-то... end local wat = 7 -- плохо if thing then -- ...что-то... end function derp() -- ...что-то... end local wat = 7 -- хорошо
Удаляйте символы пробела в конце файла (они категорически запрещаются). Для их удаления в vim используйте
:s/\s\+$//gc
.
Следует избегать глобальных переменных. В исключительных случаях используйте переменную _G
для объявления, добавьте префикс или таблицу вместо префикса:
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 -- локальная, лучше
Всегда добавляйте префиксы во избежание конфликта имен
- имена переменных/»объектов» и «методов»/функций: snake_case
- имена «классов»: CamelCase
- частные переменные/методы (в будущем параметры) объекта начинаются с символа подчеркивания
<object>._<name>
. Избегайтеlocal function private_methods(self) end
- логическое именование приветствуется
is_<...>
,isnt_<...>
,has_
,hasnt_
. - для «самых локальных» переменных: -
t
для таблиц -i
,j
для индексации -n
для подсчета -k
,v
для получения изpairs()
(допускаются,_
если не используются) -i
,v
is what you get out ofipairs()
(допускаются,_
если не используются) -k
/key
для ключей таблицы -v
/val
/value
для передаваемых значений -x
/y
/z
для общих математических величин -s
/str
/string
для строк -c
для односимвольных строк -f
/func
/cb
для функций -status, <rv>..
илиok, <rv>..
для получения из pcall/xpcall -buf, sz
– это пара (буфер, размер) -<name>_p
для указателей -t0
.. для временных отметок -err
для ошибок - допускается использование сокращений, если они недвусмысленны, и если вы документируете их.
- глобальные переменные пишутся ЗАГЛАВНЫМИ_БУКВАМИ. Если это системная переменная, для определения используется символ подчеркивания (
_G
/_VERSION
/..) - именование модулей – с помощью snake_case (избегайте подчеркивания и дефисов) - „luasql“, а не „Lua-SQL“
*_mt
и*_methods
определяют метатаблицу и таблицу методов
Всегда пользуйтесь круглыми скобками при вызове функций, за исключением множественных случаев (распространенные идиомы в Lua):
- функции
*.cfg{ }
(box.cfg
/memcached.cfg
/..) - функция
ffi.cdef[[ ]]
Избегайте конструкций такого типа:
- <func>“<name>“ (особенно избегайте require“..“)
function object:method() end
(используйтеfuncton object.method(self) end
)- не вставляйте точку с запятой в качестве символа-разделителя в таблице (только запятые)
- точки с запятой в конце строки (только для разделения нескольких операторов в одной строке)
- старайтесь избегать создания ненужных функций (closures/..)
Не начинайте создание модуля с указания лицензии/авторов/описания, это можно сделать в файлах LICENSE/AUTHORS/README соответственно. Для написания модулей используйте один из двух шаблонов (не используйте 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,
}
Пишите код так, чтобы его не нужно было описывать, но не забывайте о комментировании. Не следует комментировать Lua-синтаксис (примите, что читатель знаком с языком Lua). Постарайтесь рассказать о функциях, именах переменных и так далее.
Многострочные комментарии: используйте соответствующие скобки (--[[ ]]--
) вместо простых (--[[ ]]
).
Комментарии к доступным функциям (??):
--- Копирование любой таблицы (поверхностное и глубокое)
-- * deepcopy: копирует все уровни
-- * shallowcopy: копирует только первый уровень
-- Поддержка метаметода __copy для копирования специальных таблиц с метатаблицами
-- @function gsplit
-- @table inp оригинальная таблица
-- @shallow[opt] sep флаг для поверхностной копии
-- @returns таблица (копия)
Используйте модуль tap
, чтобы написать эффективные тесты. Пример файла с тестом:
#!/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)
После тестирования кода вывод будет примерно таким:
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
...
Принимайте разнообразные значения и выдавайте строго определенные.
В рамках обработки ошибок это означает, что в случае ошибки вы должны предоставить объект ошибки как второе возвращаемое значение. Объектом ошибки может быть строка, Lua-таблица или cdata, в последнем случае должен быть определен метаметод __tostring
.
В случае ошибки нулевое значение nil
должно быть первым возвращаемым значением. В таком случае ошибку трудно игнорировать.
При проверке возвращаемых значений функции проверяйте сначала первый аргумент. Если это nil
, ищите ошибку во втором аргументе:
local data, err = foo()
if not data then
return nil, err
end
return bar(data)
Если производительность вашего кода не имеет первоочередное значение, постарайтесь избегать использования более двух возвращаемых значений.
В редких случаях nil
можно сделать возвращаемым значением. В таком случае можно сначала проверить ошибку, а потом вернуть значение:
local data, err = foo()
if not err then
return data
end
return nil, err