This style guides lists the coding conventions used in the
LuaRocks project. It does not claim to
be the best Lua coding style in the planet, but it is used successfully in a
long-running project, and we do provide rationale for many of the design
decisions listed below.
The list of recommendations in this document was based on the ones mentioned
in the following style guides: (sometimes copying material verbatim, sometimes
giving the opposite advice! :) )
Let's address the elephant in the room first. LuaRocks is indented with 3 spaces.
for i, pkg inipairs(packages) dofor name, version inpairs(pkg) doif name == searched thenprint(version)
endendend
Rationale: There is no agreement in the Lua community as for indentation, so
3 spaces lies nicely as a middle ground between the 2-space camp and the
4-space camp. Also, for a language that nests with do/end blocks, it
produces pleasant-looking block-closing staircases, as in the example above.
One should not use tabs for indentation, or mix it with spaces.
Use LF (Unix) line endings.
Documentation
Document function signatures using
LDoc. Specifying typing information
after each parameter or return value is a nice plus.
--- Load a local or remote manifest describing a repository.-- All functions that use manifest tables assume they were obtained-- through either this function or load_local_manifest.-- @param repo_url string: URL or pathname for the repository.-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.-- @return table or (nil, string, [string]): A table representing the manifest,-- or nil followed by an error message and an optional error code.functionmanif.load_manifest(repo_url, lua_version)
-- codeend
Use TODO and FIXME tags in comments. TODO indicates a missing feature
to be implemented later. FIXME indicates a problem in the existing code
(inefficient implementation, bug, unnecessary code, etc).
Prefer LDoc comments over the function that explain what the function does
than inline comments inside the function that explain how it does it. Ideally,
the implementation should be simple enough so that comments aren't needed. If
the function grows complex, split it into multiple functions so that their names
explain what each part does.
Variable names
Variable names with larger scope should be more descriptive than those with
smaller scope. One-letter variable names should be avoided except for very
small scopes (less than ten lines) or for iterators.
i should be used only as a counter variable in for loops (either numeric for
or ipairs).
Prefer more descriptive names than k and v when iterating with pairs,
unless you are writing a function that operates on generic tables.
Use _ for ignored variables (e.g. in for loops:)
for _, item inipairs(items) dodo_something_with_item(item)
end
Variables and function names should use snake_case.
-- badlocal OBJEcttsssss = {}
local thisIsMyObject = {}
local c =function()
-- ...stuff...end-- goodlocal this_is_my_object = {}
localfunctiondo_that_thing()
-- ...stuff...end
Rationale: The standard library uses lowercase APIs, with joinedlowercase
names, but this does not scale too well for more complex APIs. snake_case
tends to look good enough and not too out-of-place along side the standard
APIs.
When doing OOP, classes should use CamelCase. Acronyms (e.g. XML) should
only uppercase the first letter (XmlDocument). Methods use snake_case too.
In LuaRocks, this is used in the Api object in the luarocks.upload.api
module.
UPPER_CASE is to be used sparingly, with "constants" only.
Rationale: "Sparingly", since Lua does not have real constants. This
notation is most useful in libraries that bind C libraries, when bringing over
constants from C.
Do not use uppercase names starting with _, they are reserved by Lua.
Tables
When creating a table, prefer populating its fields all at once, if possible:
local player = {
name ="Jack",
class ="Rogue",
}
You can add a trailing comma to all fields, including the last one.
Rationale: This makes the structure of your tables more evident at a glance.
Trailing commas make it quicker to add new fields and produces shorter diffs.
Use plain key syntax whenever possible, use ["key"] syntax when using names
that can't be represented as identifiers and avoid mixing representations in
a declaration:
Use "double quotes" for strings; use 'single quotes' when writing strings
that contain double quotes.
local name ="LuaRocks"local sentence ='The name of the program is "LuaRocks"'
Rationale: Double quotes are used as string delimiters in a larger number of
programming languages. Single quotes are useful for avoiding escaping when
using double quotes in literals.
Line lengths
There are no hard or soft limits on line lengths. Line lengths are naturally
limited by using one statement per line. If that still produces lines that are
too long (e.g. an expression that produces a line over 256-characters long,
for example), this means the expression is too complex and would do better
split into subexpressions with reasonable names.
Rationale: No one works on VT100 terminals anymore. If line lengths are a proxy
for code complexity, we should address code complexity instead of using line
breaks to fit mind-bending statements over multiple lines.
Function declaration syntax
Prefer function syntax over variable syntax. This helps differentiate between
named and anonymous functions.
Even though Lua allows it, do not omit parenthesis for functions that take a
unique string literal argument.
-- badlocal data =get_data"KRP"..tostring(area_number)
-- goodlocal data =get_data("KRP"..tostring(area_number))
local data =get_data("KRP")..tostring(area_number)
Rationale: It is not obvious at a glace what the precedence rules are
when omitting the parentheses in a function call. Can you quickly tell which
of the two "good" examples in equivalent to the "bad" one? (It's the second
one).
You should not omit parenthesis for functions that take a unique table
argument on a single line. You may do so for table arguments that span several
lines.
local an_instance = a_module.new {
a_parameter =42,
another_parameter ="yay",
}
Rationale: The use as in a_module.new above occurs alone in a statement,
so there are no precedence issues.
Table attributes
Use dot notation when accessing known properties.
local luke = {
jedi =true,
age =28,
}
-- badlocal is_jedi = luke["jedi"]
-- goodlocal is_jedi = luke.jedi
Use subscript notation [] when accessing properties with a variable or if using a table as a list.
local vehicles =load_vehicles_from_disk("vehicles.dat")
if vehicles["Porsche"] thenporsche_handler(vehicles["Porsche"])
vehicles["Porsche"] =nilendfor name, cars inpairs(vehicles) doregular_handler(cars)
end
Rationale: Using dot notation makes it clearer that the given key is meant
to be used as a record/object field.
Functions in tables
When declaring modules and classes, declare functions external to the table definition:
local my_module = {}
functionmy_module.a_function(x)
-- codeend
When declaring metatables, declare function internal to the table definition.
local version_mt = {
__eq =function(a, b)
-- codeend,
__lt =function(a, b)
-- codeend,
}
Rationale: Metatables contain special behavior that affect the tables
they're assigned (and are used implicitly at the call site), so it's good to
be able to get a view of the complete behavior of the metatable at a glance.
This is not as important for objects and modules, which usually have way more
code, and which don't fit in a single screen anyway, so nesting them inside
the table does not gain much: when scrolling a longer file, it is more evident
that check_version is a method of Api if it says function Api:check_version()
than if it says check_version = function() under some indentation level.
Variable declaration
Always use local to declare variables.
-- bad
superpower =get_superpower()
-- goodlocal superpower =get_superpower()
Rationale: Not doing so will result in global variables to avoid polluting
the global namespace.
Variable scope
Assign variables with the smallest possible scope.
-- badlocalfunctiongood()
local name =get_name()
test()
print("doing stuff..")
--...other stuff...if name =="test"thenreturnfalseendreturn name
end-- goodlocal bad =function()
test()
print("doing stuff..")
--...other stuff...local name =get_name()
if name =="test"thenreturnfalseendreturn name
end
Rationale: Lua has proper lexical scoping. Declaring the function later means that its
scope is smaller, so this makes it easier to check for the effects of a variable.
Conditional expressions
False and nil are falsy in conditional expressions. Use shortcuts when you
can, unless you need to know the difference between false and nil.
-- badif name ~=nilthen-- ...stuff...end-- goodif name then-- ...stuff...end
Avoid designing APIs which depend on the difference between nil and false.
Use the and/or idiom for the pseudo-ternary operator when it results in
more straightforward code. When nesting expressions, use parentheses to make it
easier to scan visually:
localfunctiondefault_name(name)
-- return the default "Waldo" if name is nilreturn name or"Waldo"endlocalfunctionbrew_coffee(machine)
return (machine and machine.is_loaded) and"coffee brewing"or"fill your water"end
Note that the x and y or z as a substitute for x ? y : z does not work if
y may be nil or false so avoid it altogether for returning booleans or
values which may be nil.
Blocks
Use single-line blocks only for then return, then break and function return (a.k.a "lambda") constructs:
-- goodif test thenbreakend-- goodifnot ok thenreturnnil, "this failed for this reason: ".. reason end-- gooduse_callback(x, function(k) return k.lastend)
-- goodif test thenreturnfalseend-- badif test <1anddo_complicated_function(test) ==falseor seven ==8and nine ==10thendo_other_complicated_function() end-- goodif test <1anddo_complicated_function(test) ==falseor seven ==8and nine ==10thendo_other_complicated_function()
returnfalseend
Separate statements onto multiple lines. Do not use semicolons as statement terminators.
-- badlocal whatever ="sure";
a =1; b =2-- goodlocal whatever ="sure"
a =1
b =2
Spacing
Use a space after --.
--bad-- good
Always put a space after commas and between operators and assignment signs:
Rationale: This keep indentation levels aligned at predictable places. You don't
need to realign the entire block if something in the first line changes (such as
replacing x with xy in the using_a_callback example above).
The concatenation operator gets a pass for avoiding spaces:
-- okaylocal message ="Hello, "..user.."! This is your day # "..day.." in our platform!"
Rationale: Being at the baseline, the dots already provide some visual spacing.
No spaces after the name of a function in a declaration or in its arguments:
Functions that can fail for reasons that are expected (e.g. I/O) should
return nil and a (string) error message on error, possibly followed by other
return values such as an error code.
On errors such as API misuse, an error should be thrown, either with error()
or assert().
Always require a module into a local variable named after the last component of the module’s full name.
local bar =require("foo.bar") -- requiring the module
bar.say("hello") -- using the module
Don’t rename modules arbitrarily:
-- badlocal skt =require("socket")
Rationale: Code is much harder to read if we have to keep going back to the top
to check how you chose to call a module.
Start a module by declaring its table using the same all-lowercase local
name that will be used to require it. You may use an LDoc comment to identify
the whole module path.
--- @module foo.barlocal bar = {}
Try to use names that won't clash with your local variables. For instance, don't
name your module something like “size”.
Use local function to declare local functions only: that is, functions
that won’t be accessible from outside the module.
That is, local function helper_foo() means that helper_foo is really local.
Public functions are declared in the module table, with dot syntax:
functionbar.say(greeting)
print(greeting)
end
Rationale: Visibility rules are made explicit through syntax.
Do not set any globals in your module and always return a table in the end.
If you would like your module to be used as a function, you may set the
__call metamethod on the module table instead.
Rationale: Modules should return tables in order to be amenable to have their
contents inspected via the Lua interactive interpreter or other tools.
请发表评论