Skip to content

Lev Coding Guidelines

kristate edited this page Sep 17, 2012 · 1 revision

lev coding guidelines are a work in process (WIP), although we have decided on the naming rules below:

C-to-C API

  • Functions must be snaked_case names.

C-to-Lua and Lua-to-Lua APIs

  • Variables and functions must be camelCase names.
  • Classes must be TitleCase names.

This is the style in "Programming in Lua". Examples are: http://www.lua.org/pil/14.2.html http://www.lua.org/pil/13.4.5.html http://www.lua.org/pil/16.2.html


All rules below this line are just a working drafts for discussion.


The original page is https://github.com/logiceditor-com/coding-guidelines/blob/master/lua-guidelines.md

Priorities

  • Code readability and uniformity.
  • Reduction of probability of introducing errors.
  • Minimization of the length of patches (less changes to review).
  • Code peformance.

Old Code

The old code that does not meet guideline should be corrected along the way. Such corrections should be done with separate commits.

Variables Naming Convention

No "Hungarian notation".

Constants are named CAPITAL_LETTERS.

THE_ULTIMATE_ANSWER = 42

The variable name should generally be a noun.

Name of a variable that contains the object should contain the name of that object (See XIV.3. Factories). (Typically, such a variable is a table.)

Variable name for a table containing a collection of objects typically should be plural noun (foos - a collection of foo objects).

Boolean variable name typically should be one of forms is_*, are_*, has_​​*, need_*, should_*, must_*, in_*, not_* etc.

Amount of objects: num_*.

Whitespaces

The code must not contain embedded tabs. If a string literal needs a tab, use escape sequence

"\t"

Strings should be qouted with kind of quotes which do not appear in the text:

"Can't open such file"
'Fatal: "%s"'
[[Can't open such file "%s"]]

The code should be indented with two spaces.

The code should not contain trailing whitespaces.

The length of a code line should not exceed 80 characters.

The code should not contain more than one space in a row except for the indents.

The code should not contain more than one consecutive blank lines.

After each comma and semicolon there must be a space.

Before comma and semicolon there must be no spaces.

a, b, c

All binary operators (=, ~=, <, >, +, -, *, /, and, or, ..) should be separated with spaces both on the left and on the right.

a = b
a > b
a .. b

Length operator # and unary minus should not be separated with a space from the operand, but should be separated with a space on the left.

a > #b
a = -b

not operator should be separated with spaces both on the left and on the right.

a = not b

Parentheses at function calls should not be separated from function name.

a() + b()
a(b)

After the opening curly brace and before the closing curly brace there should be spaces.

a({ b })
{ A, b }
foo = { }

Each source file must end with a newline character.

The body and the header of a non-anonymous function which is written in more than one line must be separated by a blank line before and after.

Indentation Rules

By default, to avoid differences in the amount of space on the adjacent non-empty rows more than one margin (unless otherwise stated).

During a "break" statement and transfer part of it to the following line in it is one additional margin (unless otherwise stated). If expression is split into multiple lines, all lines except the first begin with one additional indentation.

    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

In the table, column, and other similar data may placement indentation on the need to preserve the columns if this improves readability.

    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"

When a multiple-entry code inside, function-end, do-end, then-end, repeat-until and similar structures is shifted right by one indentation.

    while
      some_long_name_operand ==
        other_long_name_operand another_long_name_operand
    do
      some_stuff ()
    end

When a multiple-entry table designer, opening and closing braces are written on new lines with a space equal to the indentation of the first non-empty line before the opening parenthesis.

    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";
    }

Allowed to shift to the right by one indent code that is between begin ()-end ()-like paired structures.

    TODO: example

When you split a call to a few lines, the arguments of the functions are shifted for TWO indent closing parenthesis - one regarding line opening. Desirable to put the opening brace on the same line as the name function. It is advisable in this case to write each argument on a new line. Indentation in the function declaration are made the same way.

    long_function_name (
        with,
        many,
        many,
        parameters
      )

If the function is called it is passed a single argument, and this argument - anonymous function created by the construction of function-end directly to the time of the call, the function body shifted one indentation (instead of two). Keyword function the word is written directly after the opening bracket challenge without space between them. Appropriate keyword function keyword end is written with the same indentation as the line that says function. The closing bracket is placed directly after the call end-but without space between them. You can record on one line.

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

    foo(function(args) body() end)

Some examples of complex and mixed (added as questions):

    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. Allowed sveshivanie .. When you transfer
    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 directive

To split the rows import directive applies special formatting, the historically.

Prisvaemaya each variable is written on a separate line. List of variables aligned to the name of the first variable.

Import directive itself is written in a single line with the name of the imported library.

The opening and closing braces for the import list are on separate lines indented perdyduschey line.

The import list is divided by rows with additional space from curly brackets. List items are separated by a comma, not a semicolon (similar formatting when you assign a list of variables).

Example:

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

If the list of imports consists of one element, the import directive can or formatted in one line, with all the general rules or break in the lines, as described above.

Example:

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

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

# # Multiple constants

For strings, enclosed in long brackets, rules do not apply padding **. ** Indents are placed so that the derivation of the string looked like it should.

Example:

      io.stdout:write([[

    Config format:

    ]])

      io.stdout:flush()

FIXME: should describe: return and or, if a non-standard shift left concatenation, multiline condition if-then while-do for-do, (what else?) -Alexander Gladysh 24.11.09 22:56

Excess syntax

You can not put parentheses around the condition in if-then, while-do, for-do, repeat-until.

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

Syntactic sugar

If a function is recursive, use a form definition a = function() end.

    local a = function(args)
      ...
    end

If a recursive function - function a() end.

    local function a(args)
      ...
    end

When you call a method, you must use the operator ":".

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

You can not omit the parentheses when assigning a string literal and designers tables as arguments to functions, if the code is written in the imperative style.

    TODO: example

The code, written in a declarative style, it is desirable to lower brackets when Transfer string literals and constructors table as arguments functions. (Including "import".)

Parentheses when calling a function with a table literal recommended lower in the following cases

  1. When declarative record.
  2. When the function transforms the table into another table (example: tset())
  3. When the function serializes the table (example: luabins.save())

Do not immerse the function just does something to the table (Eg some count_number_of_keys()).

Correct:

    api: input { };

    api: input
    {
    };

Wrong:

    api: input
    {};

Cycles

  • Do not use ipairs(). Instead - numeric for.

  • If possible, avoid concatenation in a loop using   variable power. Instead, use concatter.

  Desirable:

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

  Desirable:

    local cat, concat = make_concatter()
    for ... do
      cat "something" (foo())
    end
    return concat()

Functions

Check arguments

Established argument check

    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

Default arguments

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

Table Designer

If a table is created in the same line, it is desirable to separate its elements commas. If the list is separated by commas, in the bottom of the list "hanging" comma was not raised.

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

If a table is created on a few lines, it is desirable to separate its elements semicolons, and to have one entry per line. If the list is separated by a semicolon at the end of the list as a dot semicolon.

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

You can not mix commas and semicolons in a single list. Exception - long tables of homogeneous elements, which probably rarely need to read carefully, because their contents are obvious:

    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";
    }

    - Specifically, it would have to be written with the 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;
    }

A variable

The scope of a variable should be as limited as allows common sense in every situation.

Global variables are not allowed (except in special cases). Any case introduction of a new global variable must be approved separately dokumentirovatya in code.

A variable in the global scope file should be narrowly blocks do-end.

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

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

Should not re-use the same variable for different purposes (except documented cases where this is necessary because of the requirements for superstiff performance - in practice, such cases have not yet been).

Variable names must not overlap the names of variables in external scope'ah (Allows reasonable exceptions, such as in cases where it improves readability).

Standard global variables (for example, print, error) is desirable "Cache" in the local top of the file: local print, error = print, error. It allows you to create code that is not "out of the box" to work within sandbox'a replaced with the global environment.

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

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

Description of objects

Should adhere to the following format.

FIXME: Show, describe options with metatable and upvalue as valid, and cases in which they preferred.)

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

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

Table field with an underscore at the end - private data.

Modules

Separation of code files

General culture programming

In the code, you should avoid using unnamed "magic numbers": http://en.wikipedia.org/wiki/Magic_number_ (programming) # Unnamed_numerical_constants

Naming functions

The function name should comprise a verb or a noun ends at ending-er (usually meant when the "functor"), except for special cases and declarative code.

Predicates: is_ Factories: make_ <object_name>

Naming Test

Test name should describe what the tests test. The name should be written without spaces; individual words in the name are separated by a hyphen.

Examples:

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

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

Identifiers in the name of the test is written as it is.

Example:

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

Outdated expression language

Current standard language is Lua 5.1. Can not be used in the code and the methods that have been declared obsolete in it, even if they still work. These include in particular:

  • arg (table of variables passed to the function open list) -   instead you should use select (n, ...)
  • string.len - instead, you should use the length operator #
  • table.getn - instead, you should use the length operator #

Connecting loggers

Loggers must be present in each file of each library below lua-aplicado.

Loggers are connected at the top of the file like this:

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

Loggers in the test files are named similarly to the example below:

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

LIBRARY instead substitute with the correct name of the library option make_loggers.

If the right of the file in which they are connected to the old scheme (With separate import make_loggers), it is desirable to separate commits to bring it into a modern look.

# # # Using the data loggers

A logger no need to use a conversion function to the string. This happens automatically

False:

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

True:

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

In the data logger are prohibited from using multi-line comments, this is due to the peculiarities of parsing strings logging services in combat mode.

False:

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

True:

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

Use the appropriate situation loggers for errors - log_error, for rutinnnoy information - log, debug - dbg.

False:

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

True:

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

Avoid using concatenation inside loggers, upload the information as a set of variables.

False:

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

True:

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

Required logging

  1. math.randomseed when installed in a value

Error Handling

When you call the error is to use the error(...), not assert(nil, ...). This recommendation was introduced in order to improve the readability of the code and has no other practical sense.

We recommend a comment o essence of the error.

False:

    assert(a)

True:

    assert(a, "some bad things happened")

If you come with a bug-report stektreysom, which is assert without error message, it is mandatory to add to the code error message.

If a comment in assert(...) requires concatenation, you should use If ​​not + log_error + error.

False:

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

True:

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