This document outlines the standards and best practices for Lua development at Bayat.
- Maximum line length should be 80 characters
- Use 2 spaces for indentation, not tabs
- Files should be encoded in UTF-8
- Files should end with a newline
- Use spaces around operators
- No trailing whitespace
- Use empty lines to separate logical sections of code
-
Variables and Functions:
- Use snake_case for variable and function names (e.g.,
player_score
,calculate_total
) - Use descriptive names that reflect purpose or meaning
- Local variables and private functions should start with lowercase
- Boolean variables should use prefixes like
is_
,has_
, orshould_
(e.g.,is_valid
)
- Use snake_case for variable and function names (e.g.,
-
Constants:
- Use SCREAMING_SNAKE_CASE for constants (e.g.,
MAX_PLAYERS
,DEFAULT_TIMEOUT
) - Define constants at the top of the file or module
- Use SCREAMING_SNAKE_CASE for constants (e.g.,
-
Modules and Classes:
- Use PascalCase for "classes" (tables that are meant to be used with a metatable)
- Use snake_case for modules (e.g.,
player_manager.lua
) - Module names should match their filenames
-
Private Members:
- Prefix private functions and variables with an underscore (e.g.,
_private_helper
) - Keep truly private functions local to the module
- Prefix private functions and variables with an underscore (e.g.,
-
File Structure:
- Organize files by functionality
- One primary "class" or module per file
- Keep files reasonably sized (under 500 lines when possible)
- Standard order:
- Module declaration/imports/requires
- Constants
- Local functions
- Module table definition
- Public functions
- Return statement for the module
-
Module Structure:
- Use the module pattern:
local MyModule = {} -- Functions and variables defined here return MyModule
- Import other modules with local variables:
local other_module = require("other_module")
- Use the module pattern:
-
Table Formatting:
- Format tables consistently:
-- Single line for short tables local point = { x = 10, y = 20 } -- Multiple lines for longer tables local config = { name = "Application", version = "1.0.0", settings = { fullscreen = true, resolution = "1920x1080" } }
- Use trailing commas in multiline tables for easier additions and version control
- Align keys and values in table definitions when it improves readability
- Format tables consistently:
-
Table Usage:
- Use tables as the primary data structure
- Prefer table.insert() and table.remove() for array operations
- Use ipairs() for array-like tables and pairs() for associative tables
- Avoid mixing array and hash table styles unless necessary
-
Class Pattern:
- Use a consistent class pattern with metatables:
local MyClass = {} MyClass.__index = MyClass function MyClass.new(args) local self = setmetatable({}, MyClass) -- Initialize object return self end function MyClass:method() -- Method implementation end return MyClass
- Use a consistent class pattern with metatables:
-
Inheritance:
- Implement inheritance through metatables:
local Parent = require("parent") local Child = {} Child.__index = Child setmetatable(Child, Parent) function Child.new(args) local self = Parent.new(args) setmetatable(self, Child) -- Initialize child-specific properties return self end
- Implement inheritance through metatables:
-
Error Reporting:
- Use Lua's error mechanism appropriately:
if not condition then error("Meaningful error message", 2) -- 2 means report error at caller's level end
- Include contextual information in error messages
- Use assert() for validating arguments and preconditions:
function my_function(required_arg) assert(required_arg, "required_arg is mandatory") -- Function implementation end
- Use Lua's error mechanism appropriately:
-
Error Handling:
- Use pcall() or xpcall() to catch errors:
local success, result = pcall(function() -- Function that might throw an error end) if not success then -- Handle error print("Error: " .. result) end
- Return error values for non-exceptional conditions:
function find_user(id) if not valid_id(id) then return nil, "Invalid user ID" end -- Find user return user end
- Use pcall() or xpcall() to catch errors:
-
Memory Management:
- Avoid creating tables in tight loops
- Reuse tables when possible using table.clear() (Lua 5.4+) or custom clear function
- Be aware of garbage collection events and their impact on performance
- Use weak tables for caches to allow garbage collection of unused objects
-
Algorithm Efficiency:
- Use local variables for performance-critical code
- Avoid global lookups in tight loops
- Precompute values when possible
- Be mindful of table access patterns and string concatenation
- Use string.format() for complex string formatting
- Consider performance trade-offs when using closures
-
Code Comments:
- Use
--
for single-line comments - Use
--[[
and]]
for multi-line comments - Document the purpose of all functions, parameters, and return values
- Explain non-obvious code sections
- Use consistent comment style:
--- -- Brief description of function -- @param name (type) Description of parameter -- @return (type) Description of return value -- function my_function(name) -- Implementation end
- Use
-
Module Documentation:
- Include a header comment at the top of each file describing the module's purpose
- Document dependencies and usage examples
- Consider using LuaDoc or similar documentation tools for API documentation
-
Unit Testing:
- Use a testing framework like Busted, LuaUnit, or Telescope
- Write tests for all public functions
- Organize tests to match the module structure
- Test edge cases and error conditions
-
Test Organization:
- Keep tests in a separate directory (e.g.,
tests/
) - Name test files to match the modules they test (e.g.,
test_player_manager.lua
) - Use descriptive test names that indicate what is being tested
- Keep tests in a separate directory (e.g.,
-
Module Structure:
- Use proper module pattern:
local MyModule = {} -- Module functions defined here return MyModule
- Avoid global variables
- Keep module interfaces clean and focused
- Document dependencies in the module header
- Use proper module pattern:
-
Dependency Management:
- Use require() with relative paths for local modules
- Specify version dependencies in a project manifest
- Consider using LuaRocks for dependency management
- Minimize external dependencies
-
Input Validation:
- Validate all inputs, especially from external sources
- Sanitize data used in sensitive operations
- Be cautious with functions like loadstring() and setfenv()
- Avoid using insecure serialization methods
-
Environment Security:
- Use sandboxing techniques for untrusted code
- Limit access to sensitive system functions
- Carefully review any use of os.execute(), io operations, and network functions
-
Version-Specific Features:
- Clearly document which Lua version your code targets
- Be aware of language differences between Lua 5.1, 5.2, 5.3, and 5.4
- Use conditional code or compatibility libraries when supporting multiple Lua versions
- Document any version-specific features or dependencies
-
Compatibility Libraries:
- Consider using compatibility libraries like compat-5.3
- Test code on all supported Lua versions
-
Game Engine Integration:
- Follow engine-specific best practices (Unity, LÖVE, etc.)
- Understand the lifecycle and performance characteristics of the engine
- Use appropriate design patterns for your engine
-
Game-Specific Patterns:
- Use component-based design when appropriate
- Consider using an entity-component system for complex games
- Separate game logic from rendering code
- Implement proper game state management
- Commit Guidelines:
- Follow the project's commit message format
- Make logical, focused commits
- Ensure code runs without errors before committing
- Document breaking changes clearly