A set of guidelines to help keep your GLua code Clean and Cool π
π· 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.
π€ 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. π
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
Good
local x = a * b + c
Bad
local x = a* b+c
Good
local x = ( 3 * myFunc() ) + 5
local data = { 5, {} }
Bad
local x = (3 * myFunc( )) + 5
local data = {5, { }}
Good
myFunc( 10, { 3, 5 } )
Bad
myFunc( 10,{ 3,5 } )
Good
if cond then
myFunc()
end
Bad
if cond then
myFunc()
end
Good
local val = tab[5] + tab[3]
local val2 = tab[5 * 3]
Bad
local val = tab[ 5 ] + tab[ 3 ]
local val2 = tab[ 5 * 3 ]
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
Good
local config = GM.Config
function GM:Think()
-- do thing
end
Bad
local config = GM.Config
function GM:Think()
-- do thing
end
Good
local config = GM.Config
function GM:Think()
-- do thing
end
Bad
local config = GM.Config
function GM:Think()
-- do thing
end
Good
function test()
local a = 3
print( a )
end
Bad
function test()
local a = 3
print( a )
end
Good
function test()
local a = 3
return a
end
function test2()
return 3
end
Bad
function test()
local a = 3
return a
end
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
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
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
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
Good
local myVariable = 10
Bad
local MyVariable = 10
local my_variable = 20
Good
CATEGORY_NAME = "nothing"
local MAXIMUM_VALUE = 25
Bad
CategoryName = "nothing"
categoryName = "nothing"
category_name = "nothing"
local maximumValue = 25
Good
GlobalVariable = 10
Bad
globalVariable = 20
global_variable = 20
Good
function classTable:SetHealth( amount )
end
Bad
function classTable:setHealth( amount )
end
Good
myTable.myValue = 4
Bad, accessing the table value requires brackets and quotes because of the hyphen
myTable["my-value"] = 4
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 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_OnDisconnect
or evenORG_MyAddon_CleanupPropsOnDisconnect
. ButORG_MyAddon_CleanupProps
would 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
Good, quote usage is consistent
myFunc( "hello ", "world!" )
Bad, different quotes are used interchangeably
myFunc( "hello ", 'world!' )
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
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 }
Good
myFunc(
"First arg",
secondArg,
{ third, arg }
)
Bad, this indentation is inconsistent (and objectively wrong)
myFunc( "First arg",
secondArg, { third, arg } )
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
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
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 )
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;
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 )
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
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
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
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
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
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
--
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 )