About a year and a half ago, I shared a post discussing how I structure my scripts that I write. Anyone who has explored the world of programming outside of just Roblox has probably been exposed to the numerous programming languages that exist for varying use-cases, also accompanied by the equally numerous rules and practices to follow for each. However, there exists some programming rules/practices that are generally accepted to be language-agnostic. A common example is the avoidance of “magic numbers”, unnamed numerical values in code whose purpose isn’t immediately clear.
Unfortunately, while there may be some generally accepted rules, there exists far more that programmers will endless debate about; tabs vs. spaces, variable casing styles, and more. Over time, every programmer forms their own set of rules and practices to follow (for better or worse). After my few years of experience programming, I’ve developed a collection of personal rules that guide how I write and organize my code.
A quick disclaimer: these rules are not intended to be perfect or universally “correct”. In fact, several of these rules are highly contradictory to practices known to be “idiomatic” in Lua/u. Some common programming concepts and practices are made heavily complicated to implement while following these rules, some just simply disallowed. I maintain that these rules have reasoning behind them based on my experience and personal opinions about varying programming concepts. However, due to the fact that however many reasons I could have, there exists far more reasons to argue against my rules. Therefore, I have tried to keep my justification and reasoning for these rules to a minimum, but am more than happy to engage in discussion about it. I recognize that these rules are not meant for everyone, and some might say that these rules could even be damaging to newer programmers coming to the platform because it might incentivize bad habits. I maintain that these rules have helped me improve my consistency across my work and ultimately produce more maintainable and better structured code.
I am very curious to see how others might view these rules and welcome any discussion in agreement or disagreement.
For some background, I’m currently in college studying computer science and computer engineering and consider myself an intermediate programmer in the context of Roblox game development. Although I technically have been on the platform programming/scripting for about 4 years, it would be dishonest to say that I have the equivalent of 4 years of experience within Roblox programming.
Luau Coding Standards & Practices
1. Script Templates
All scripts must adhere to the following templates to ensure a consistent and organized structure.
1.1 Module Script Template
Template
--!strict
--[[
@module Module Name
@description Module Description.
]]--
-- ========
-- SERVICES
-- ========
-- ==============
-- MODULE IMPORTS
-- ==============
-- =================
-- MODULE DEFINITION
-- =================
local Module = {}
-- ==============
-- VARIABLE TYPES
-- ==============
-- =================
-- PRIVATE VARIABLES
-- =================
-- ================
-- PUBLIC VARIABLES
-- ================
-- ================================
-- PRIVATE FUNCTION INITIALIZATIONS
-- ================================
-- ================
-- PUBLIC FUNCTIONS
-- ================
-- ============================
-- PRIVATE FUNCTION DEFINITIONS
-- ============================
-- =============
-- MODULE EXPORT
-- =============
return Module
1.2 LocalScript / Script Template
Template
--!strict
--[[
@script Script
@description Description of the Script.
]]--
-- ========
-- SERVICES
-- ========
-- ==============
-- MODULE IMPORTS
-- ==============
-- ==============
-- VARIABLE TYPES
-- ==============
-- =========
-- VARIABLES
-- =========
-- ========================
-- FUNCTION INITIALIZATIONS
-- ========================
-- ====================
-- FUNCTION DEFINITIONS
-- ====================
--[[
@function main
@description Description of main function.
]]
function main() : ()
end
-- ================
-- SCRIPT EXECUTION
-- ================
main()
2. Naming Conventions
| Identifier Type | Case | Example |
|---|---|---|
| Module Names / File Names | PascalCase | DataManager |
| Roblox Instances | PascalCase | MainGui, ReplicatedStorage |
| Custom Type Names | PascalCase | type PlayerData = {} |
| Constant Variables | MACRO_CASE | MAX_HEALTH |
| Non-Constant Variables | snake_case | current_health |
| Function Parameters | snake_case | function on_player_added(player) |
| Module Public Variables | PascalCase | Module.IsReady |
| Module Private Functions | snake_case | calculate_damage() |
| Module Public Functions | PascalCase | Module.GetCharacter() |
3. Type Checking & Variables
3.1 Strict Type Declaration
All variables must have their types explicitly specified. The only exception is for variables serving as a reference to an Instance present in the Explorer for the sake of indexing children without type-errors.
3.2 Advanced Type Syntax
- All tables must be strictly typed.
- The any type is disallowed except for scenarios where functionality demands it.
- Union types (|) and intersections (&) are permitted but must be used sparingly and with immediately apparent justification for their necessity present in their use case/implementation.
3.3 Naming Conventions
Variables that reference constant instances in the Explorer will default to the PascalCase instead of MACRO_CASE regardless of if the reference or value categorizes the variable as constant.
3.4 Constant Variables
- Constants must be declared in either the
PUBLIC VARIABLESorPRIVATE VARIABLESsections. - Within a variable declaration section, all constants must be defined before any non-constant variables.
- Constants cannot be declared locally within a function.
3.5 Variable Declaration & Grouping
- Variables should be organized into logical groups based on their use case.
- Each logical group must be separated by a single blank line.
- Due to the subjective nature of variable categorization for use case, the general guideline to follow is that relation of their use cases should be immediately and intuitively relevant.
4. Function Definitions
4.1 General Rules
- Syntax Enforcement: The only permitted syntax for defining a function is assigning it to a variable. All other forms, such as
function Module.MyFunction() end, are disallowed. The only exception to this is themainfunction to be defined in Local/Server Scripts. - No Anonymous Functions: Anonymous functions are disallowed. All functions, including callbacks for event connections, must be declared as named local functions following the intialize-then-define pattern.
- Mandatory Return Types: All functions must explicitly define their retype type. For functions that do not return a value, the empty tuple
()must be used to indicate intention behind the lack of an explicit return value. - Method Definition: Functions that operate on
selfmust be defined using dot-syntax and include self as the first parameter with its type explicitly defined. The exception to this rule is the use of:to call functions for Roblox defined methods, generally methods belonging to Roblox defined classes/objects.
4.2 Public Module Definitions
Public functions are defined as key-value pairs directly on the Module table within the PUBLIC FUNCTION DEFINITIONS section.
4.3 Private & Local Functions
The initialize-then-define pattern is mandatory for all local functions.
5. Error Handling
- All errors must be directly handled and processed in the form of strict guard clauses.
6. Code Style & Readability
(This is arguably the most controversial section after functions)
6.1 Line-by-Line Simplicity
Logic must be broken into the simplest possible steps. A single line of code must not perform more than one operation, which primarily disallows chaining accessors. An exception is to be made for navigation to a location in the Explorer Hierarchy.
6.2 Constructors
Calling a constructor (e.g., Vector3.new()) is an exception to the “one operation” rule. However, a line containing a constructor call must only be a variable assignment.
6.3 Variable Naming Philosophy
- No Abbreviations: Variable names must be fully descriptive and self-documenting.
- Semantic Duplication: If a variable’s role changes, a new variable must be created for that context to make the code’s intent intuitively logical. The need to search for context to understand a variable indicates failure in naming.
6.4 Table Formatting
- Tables must be defined in a multi-line format, with each element on a new, indented line and a trailing comma on the final element.
- Exception: Single-line table definitions are permitted only for inline, single-use tables, such as in a
setmetatablecall.
6.5 Handling Edge Cases
Edge cases that can be solved with a single line guard clause should be. If the option to return early is possible, then it should be taken to avoid unnecessary code being ran.
6.6 Disallowing Magic Numbers
“Magic numbers” are disallowed and must be defined as named constants or variables. The only exception is for values intuitively understandable within their literal value such as checking for a 0, nil, or boolean value. Additionally, the use of select number values to use in narrow tolerance checks for floating-point comparisons is allowed.
7. Script Dependencies
7.1 Services
All necessary Roblox services must be declared in the SERVICES section, retrieved using game:GetService(), and alphabetized.
7.2 Modules
- All module dependencies must be declared in the
MODULE IMPORTSsection usingrequire(). Their organization should be grouped together by use-case and relevance similar to variables, including a separation from other use-case module groups by a blank line. Unlike variables, modules within their group of related use-case modules should be alphabetized. - Module paths must be absolute from a root service (e.g., ReplicatedStorage). Relative paths should not be used for module imports.
8. Metatable Standards
- Metatables are permitted but must be used sparingly to avoid creating overly complex data structures. (There is beauty and elegance in simplicity)
- The
__indexfiled must be assigned inline within the setmetatable call to prevent it from being an accessible key on the base table.
(Rules concerning metatables are subject to discretion as I personally try to avoid using metatables as much as I can. I also acknowledge that the use of metatables can quickly become highly advanced and rules regarding their use-case and implementation requires nuance.)