cfc_glua_style_guidelines
cfc_glua_style_guidelines copied to clipboard
GLua Style guidelines to keep your code clean and cool 😎
CFC Glua Style Guidelines
A set of guidelines to help keep your GLua code Clean and Cool 😎
:construction_worker: For CFC Members and Contributors
Please adhere to these rules in our repositories. Your PR will be automatically blocked if it does not meet the style guidelines.
:cowboy_hat_face: For Non-CFC Members
Feel free to use these guidelines as you see fit! We believe they help maintain and share code effectively.
If you disagree with some guidelines, that's okay! The most important principle is consistency. If you choose to follow or ignore certain rules, ensure you do so consistently throughout your code. 👍
Glossary
-
Tooling
- GLuaLint
-
Spacing
- Around operators
- Inside parenthesis
- After commas
- Indentation
- Inside square brackets
- Comments
-
Newlines
- Limits
- Top-level blocks
- Nested blocks
- Returns
- Chunking
-
GLua
- GMod Operators
- GMod Comments
- Continue
-
Naming
- Local variables and functions
- Constants
- Globals
- Table keys
- Throwaway variable
- Hooks
-
General
- Quotations
- Parentheses
- Multiline Tables
- Multiline Function calls
- Magic numbers
- Complex expressions
- Semicolons
- Constants
- Long Conditions
- Line length
-
Numbers
-
Comments
- Useless Comments
-
General Practices
- Unnecessary
IsValid
- Unnecessary
Tooling
GLuaLint
Use GLuaLint (GLuaFixer) as your linter. There are plugins available for most major editors.
GLuaFixer: https://github.com/FPtje/GLuaFixer
Use CFC's GLuaFixer config, found here: https://cfc.gg/configs/gluafixer/glualint.json
Spacing
Spaces around operators
Good
local x = a * b + c
Bad
local x = a* b+c
Spaces inside parentheses and curly braces if they contain content
Good
local x = ( 3 * myFunc() ) + 5
local data = { 5, {} }
Bad
local x = (3 * myFunc( )) + 5
local data = {5, { }}
Spaces after commas
Good
myFunc( 10, { 3, 5 } )
Bad
myFunc( 10,{ 3,5 } )
Indentation should be done with 4 spaces
Good
if cond then
myFunc()
end
Bad
if cond then
myFunc()
end
No spaces inside square brackets
Good
local val = tab[5] + tab[3]
local val2 = tab[5 * 3]
Bad
local val = tab[ 5 ] + tab[ 3 ]
local val2 = tab[ 5 * 3 ]
Single space after comment operators and before if not at start of line
Good
-- This is a good comment
local a = 3 -- This is also good
Bad
--This comment doesn't have a space before it
local a = 3-- This comment starts too close to the 3
Newlines
Never have more than 2 newlines
Good
local config = GM.Config
function GM:Think()
-- do thing
end
Bad
local config = GM.Config
function GM:Think()
-- do thing
end
Top level blocks should have either 1 or 2 newlines between them
Good
local config = GM.Config
function GM:Think()
-- do thing
end
Bad
local config = GM.Config
function GM:Think()
-- do thing
end
Non top level blocks/lines should never have more than 1 newline between them
Good
function test()
local a = 3
print( a )
end
Bad
function test()
local a = 3
print( a )
end
Returns should have one newline before them unless the codeblock is only one line
Good
function test()
local a = 3
return a
end
function test2()
return 3
end
Bad
function test()
local a = 3
return a
end
Code should be split into managable chunks using a single new line
Good
function CFCNotifications.resolveFilter( filter )
if type( filter ) == "Player" then
filter = { filter }
end
if type( filter ) == "table" then
filter = fWrap( filter )
end
filter = filter or player.GetAll
local players = filter()
if type( players ) == "Player" then
players = { players }
end
if not players then players = player.GetAll() end
return players
end
Bad, far too dense and difficult to read at a glance
function CFCNotifications.resolveFilter( filter )
if type( filter ) == "Player" then
filter = { filter }
end
if type( filter ) == "table" then
filter = fWrap( filter )
end
filter = filter or player.GetAll
local players = filter()
if type( players ) == "Player" then
players = { players }
end
if not players then players = player.GetAll() end
return players
end
Gmod lua additions
Do not use GMod operators ( && || ! != )
Good
if a ~= b and not b then end
Bad, Garry's operators aren't recognized by standard Lua highlighting
if a != b && !b then end
Do not use GMod style comments ( /* */ and // )
Good
--[[
This line does stuff
]]
do( stuff ) -- Stuff being done
Bad, gmod comments aren't recognized by standard Lua highlighting
/*
This line does stuff
*/
do( stuff ) // Stuff being done
The use of continue should be avoided if possible
Good
for k, v in pairs( tab ) do
if IsValid( v ) then
v:Remove()
end
end
Bad, garry's continue is a flawed implementation that is prone to errors when used in repeat-until loops
for k, v in pairs( tab ) do
if not IsValid( v ) then continue end
v:Remove()
end
If your condition is more complicated, consider deferring the logic to another function
local function processItem( item )
if not IsValid( item ) then return end
if not item:IsReady() then return end
item:Process()
end
for _, item in ipairs( tab ) do
processItem( item )
end
Naming conventions
Local variables and functions should always be written in camelCase
Good
local myVariable = 10
Bad
local MyVariable = 10
local my_variable = 20
Constants should be written in SCREAMING_SNAKE
Good
CATEGORY_NAME = "nothing"
local MAXIMUM_VALUE = 25
Bad
CategoryName = "nothing"
categoryName = "nothing"
category_name = "nothing"
local maximumValue = 25
Global variables should be written in PascalCase
Good
GlobalVariable = 10
Bad
globalVariable = 20
global_variable = 20
Methods for objects should be written in PascalCase
Good
function classTable:SetHealth( amount )
end
Bad
function classTable:setHealth( amount )
end
Table keys
Table keys should only contain a-z A-Z 0-9 and \_. they should not start with 0-9
Good
myTable.myValue = 4
Bad, accessing the table value requires brackets and quotes because of the hyphen
myTable["my-value"] = 4
Use _ as a variable to "throwaway" values that will not be used
Good
for _, ply in pairs( player.GetAll() ) do
local _, shouldKill = ply:GetKillData()
if shouldKill then
ply:Kill()
end
end
Bad, k isn't used
for k, ply in pairs( player.GetAll() ) do
local canKill, shouldKill = ply:GetKillData()
if shouldKill then
ply:Kill()
end
end
Hook naming:
- Hook identifiers should be named as such
Organization_AddonName_HookPurpose - The hook event name should not be included in the identifier.
For example, you should not do
ORG_MyAddon_OnDisconnector evenORG_MyAddon_CleanupPropsOnDisconnect. ButORG_MyAddon_CleanupPropswould be appropriate. The "HookPurpose" should state what a function does without restating information provided by the event name. - Hook event names should be named as such
Organization_EventName
General
Quotations
You may use single or double quotes, but be consistent
Good, quote usage is consistent
myFunc( "hello ", "world!" )
Bad, different quotes are used interchangeably
myFunc( "hello ", 'world!' )
Do not use redundant parenthesis
Good
if x == y then
Bad, these parenthesis are not necessary
if ( x == y ) then
Redundant parenthesis may be used if it makes the order of operations clear. The author should use their best judgement
It's important for the author to remember who their target audience is. Not every reader will be good with maths, and it can be helpful to make equations easy to follow
Acceptable
local amount = ( quantity * modifier ) + baseAmount
Unsavory, but fine
local amount = quantity * modifier + baseAmount
Multiline tables
Elements should begin on the next line and the last line should contain only a closing bracket. Elements inside should be indented once
Good
tbl = {
v1 = c,
v2 = d,
v3 = k,
v4 = j,
v5 = y
}
Fine, if both the keys and values are short, it's acceptable to keep them in one line
tbl = { v1 = c, v2 = d, v3 = k, v4 = j, v5 = y }
Bad, the indentation is inconsistent
tbl = { v1 = v, v2 = d,
v3 = k, v4 = j }
Multiline function calls
Multiline function calls should follow the same guidelines as Multiline tables
Good
myFunc(
"First arg",
secondArg,
{ third, arg }
)
Bad, this indentation is inconsistent (and objectively wrong)
myFunc( "First arg",
secondArg, { third, arg } )
Return early from functions
Good
function test()
if not myThing then return end
-- Do a bunch of real complicated things
end
Bad, adds an extra level of indentation for no reason
function test()
if myThing then
-- Do a bunch of real complicated things
end
end
"Magic numbers" should be pulled out into meaningful variables
Good
local maxX = 25
function checkX( x )
return x > maxX
end
Bad, the significance of 25 is unknown without a meaningful variable name
function checkX( x )
return x > 25
end
Complex expressions should be written on multiple lines with meaningful variable names
Good, each step of the equation is named and done individually. The math is easy to follow
local widthModifier = amount * damageMult
local age = 1 - lifetime / duration
local width = widthModifier * age
Bad, this math is difficult to figure out from a glance
local width = ( amount * 5 ) * ( 1 - lifetime / duration )
Never use semicolons
Semicolons provide no functional value in Lua. While they can be used to delimit table items, a comma is preferred
Good
local a = 3
print( a )
return a
Bad, this exclusively makes the code harder to read
local a = 3; print( a );
return a;
Make use of existing constants where possible
Good
x = y * math.pi
radians = math.rad( deg )
Bad, the meaning of 3.142 may not be immediately clear. It also suffers a loss in precision compared to math.pi
x = y * 3.142
radians = deg * ( 3.142 / 180 )
Unnecessarily long conditions should be avoided
Conditions can be pulled out into meaningful variable names to avoid this
Good, each check is clear and it's easy to follow the reasoning
local entValid = IsValid( ent )
local entOwner = ent:CPPIGetOwner()
local ownerVehicleIsVisible = entOwner:GetVehicle():GetColor().a > 0
local ownerCapable = entOwner:IsAdmin() or entOwner.canDoThing
if entValid and ownerVehicleIsVisible and ownerCapable then
-- doThing
end
Bad, the conditions are difficult to follow
if IsValid( ent ) and ent:GetCPPIOwner():GetVehicle():GetColor().a > 0
and ( ent:CPPIGetOwner():IsAdmin() or ent:GetCPPIOwner().canDoThing ) then
-- do thing
end
Lines should be around 110 characters long at the most
This isn't a hard limit, there are plenty of valid exceptions
Good
if IsValid( ent ) and ent:IsPlayer() and ent:IsAdmin() then
local hue = ( CurTime() * 10 ) % 360
local color = HSVToColor( hue, 1, 1 ) )
ent:SetColor( color )
end
Bad, this line is too long and could easily be split into multiple, easy-to-follow lines
if IsValid( ent ) and ent:IsPlayer() and ent:IsAdmin() then ent:SetColor( HSVToColor( ( CurTime() * 10 ) % 360, 1, 1 ) ) ) end
Numbers
Prefer decimals with leading 0s
While you can omit a zero in decimals in the range of 0-1, it's an antipattern and a one-off exception when compared to decimals outside of said range
Good
local num = 0.69
Bad, while the 0 is implied, it can make it harder to parse at a glance
local num = .69
Prefer whole numbers without leading 0s
Lua numbers can technically be prefixed with as many 0s as you'd like, but in most cases it's completely unnecessary
Good
local factor = 420
Bad, just why?
local factor = 00420
Prefer decimals with no trailing 0s
Except in cases where the trailing 0s make it easier to understand the values (e.g. when you have a column of configurable decimals with varying precision), there is no reason to include them and could confuse the reader
Good
local decimal = 0.2
Bad
local decimal = 0.200
Comments
Do not add useless comments
Good variable and function names can make comments unecessary. Strive for self commenting code. Save comments for complicated code that may not be clear on its own
Good
for _, ply in pairs( players ) do
print( ply )
if ply:Alive() then ply:Kill() end
end
Bad, the code explains itself without comments
for _, v in pairs( stuff ) do -- loop through players
print( v ) -- print the player
if v:Alive() then v:Kill() end -- kill player if player is alive
end
--
General Practices
Avoid unnecessary IsValid/:IsValid() checks
While these functions are relatively low-cost on their own, they still add overhead in places where it may not be necessary
Developers have been conditioned to pepper these checks anywhere-and-everywhere to avoid Attempt to index NULL errors.
If you know an Entity can become NULL, first try to remedy the underlying issue that causes it. Only rely on IsValid if the situation is outside of your control (i.e. delayed timer actions).
Good, IsValid is only used in pieces of code that we can't avoid, and isn't used in places that add no value
net.Receive( "my_net_message", function( _, ply )
-- No IsValid check is needed here, Players cannot become invalid at this point
ply:ChatPrint( "Starting the process now..." )
local steamID64 = ply:SteamID64()
timer.Create( "delayed_action_" .. steamID64, 5, 1, function()
-- IsValid check is required here because the Player may have disconnected before this timer triggers
if not IsValid( ply ) then return end
ply:DoAction()
timer.Create( "delayed_action_step2_" .. steamID64, 5, 1, function()
-- No IsValid check is needed here because we already saved the data we need from their Player object
Database:UpdatePlayer( steamID64, true )
end )
end )
end )
Bad, IsValid is used in a hook where it's impossible for the Player to have become invalid
hook.Add( "PlayerCanSuicide", "check_suicide", function( ply )
if not IsValid( ply ) then return end
if ply:IsAdminFrozen() then return false end
end )