-
Notifications
You must be signed in to change notification settings - Fork 72
Style guide for core Lua modules
#tl;dr Keep code simple. Work within platform constraints to minimize user-facing errors. Use the power of lua while not abusing it. Keep in mind the Liquipedia contributor.
Modules should strive to follow the structure in this section, to allow for easier understanding. This is needed because LUA is a very freeform language, which means there is a lot of freedom. In addition, by default, LUA does not have a real structure of its own. Common programming paradigms such as classes don't exist, instead everything is a table. On top of that, since our entry point is often from wikicode, there is additional complexity for our structure.
We divide desired structure for LUA into two types of modules: libraries and components:
-
Components are the building blocks of our wikis. These modules produce layouts, content, and more. These are almost always called from wikicode. They tend to need access to the
frame
object, which allows us to see them as "view" code. -
Libraries are used by (various) Components, and provide general functionality that is to be re-used. These should generally not need access to the
frame
object, which means we can sort of see these modules as business logic or the data layer as they would commonly be referred to in more standardised software applications.
While proper separation of view code and business logic is not particularly feasible, we can use this separation as guiding principle. For example, we would generally not want a Math module to have access to the frame
object.
Components build the layouts we see on the wikis, which means they often utilize Templates. This means we rely on the frame
object. Components should utilize libraries as much as possible to minimize business logic within the module, thus simplifying the module. This should allow less advanced users to make some (small) changes too.
Libraries provide re-usable functionality to components (and in a rare case wikicode directly). This means their focus should be their usability from LUA, while still being operable from wikicode. The Module:Class module allows us to accomplish this. Example:
local SomeModule = {} <!--- This is what we call our "class"
local Class = require('Module:Class')
function SomeModule.doCoolStuff(a, b) <!--- Callable from both LUA and wikicode!
return a + b
end
return Class.export(SomeModule)
Please see the Class module documentation for more details. With this module, we can accomplish an object-oriented approach to these library modules.
The user should be protected from errors. If one does happen, it is preferably handled in the code. If one must happen, for example because of wrong input by contributors, minimize the impact and provide a clear error message so contributors can fix it.
Many people on Liquipedia are not programmers or are still learning. Because of that, we want to keep our code relatively simple where possible. Avoid terms that are only recognizable to advanced programmers where possible. When building modules that are intended, or likely, to be changed by less proficient programmers, keep this in mind. For example, split a module into two modules, one of which acts like a config file and can thus be easily adjusted by anyone, and the other acting as the actual core logic.
Every module that is in active use should have such documentation on the module page. The following structure for Module docs is convention:
- A brief paragraph up top summarising the module
- A '''Usage''' section highlighting any prerequisites needing for the module to work properly. This section can also show examples.
- An '''API''' section, using Template:ApiDoc.
- If wanted, a '''See Also''' section linking to related templates and modules.
Numbers in code should be clearly understandable. Either extract the number to a constant, thus providing explanation via the variable name, or add a comment.
Use the uppercase format for global variables and constants, e.g. COOL_VARIABLE
. Note that lua does not actually have constants, but we nonetheless use global variables as such.
To indicate a variable or function is not to be used outside a module, prefix it with _
.
When faced with a naming dilemma between a more generally used term in English and what is perhaps a more accurate term from software engineering, prefer the English term to facilitate understanding.
Functions should be followed by a newline.
In a larger boolean statement, inner statements should be surrounded by brackets.
if isSomething or x ~= nil and x.isTrue() then -- BAD
if isSomething or (x ~= nil and x.isTrue()) then -- GOOD
Child components should indicate that in the file path. For example, if we have a module that creates the infobox for a company, the file path should be Module:Infobox/Company
.