Every docs URL answers with Markdown when you add .md. Site map: llms.txt
Conditionals and early returns
Guard clauses that keep functions flat, returning the expression instead of true or false, the or default trap, and ordering checks so the cheap one runs first.
Per ora questa documentazione è in inglese.
Most unreadable Lua is not complicated logic. It is simple logic wrapped in four levels of if. These are the four changes that fix nearly all of it.
Guard clauses
Handle the reasons to stop first, one per line, then write the real work at the left margin:
RegisterNetEvent('shop:server:buy', function(item, amount)
local src = source
local player = GetPlayer(src)
if player then
if Items[item] then
if type(amount) == 'number' and amount > 0 then
if player.money >= Items[item].price * amount then
giveItem(src, item, amount)
takeMoney(src, Items[item].price * amount)
else
notify(src, 'Not enough money')
end
end
end
end
end)RegisterNetEvent('shop:server:buy', function(item, amount)
local src = source
local player = GetPlayer(src)
if not player then return end
local def = Items[item]
if not def then return end
if type(amount) ~= 'number' or amount < 1 or amount > 100 then return end
local cost = def.price * amount
if player.money < cost then
return notify(src, 'Not enough money')
end
takeMoney(src, cost)
giveItem(src, item, amount)
end)Same logic, no nesting, and every precondition is visible in the first few lines. When you add a fifth check you add a line instead of another level.
Two practical notes. Do the cheap checks first so the expensive ones never run on bad input: a type() check before a database call, not after. And do the checks in dependency order, so nothing reads a value that an earlier check has not confirmed yet.
Return the expression
If both branches return a boolean, the condition already is the answer:
-- Noise
if role == 'police' or role == 'ems' then
return true
else
return false
end
-- The same thing
return role == 'police' or role == 'ems'The same goes for if x then y = true else y = false end. Assign the comparison.
Default values with or
Lua’s or returns the first value that is not nil or false, which makes defaults short:
local function greet(name)
return 'Hello, ' .. (name or 'stranger')
endWarning
This trap catches everyone once. or treats false exactly like nil, so a default for a boolean option silently ignores the caller.
-- Broken: passing false gives you true
local function setup(opts)
local enabled = opts.enabled or true -- always true
end
-- Correct
local function setup(opts)
local enabled = opts.enabled
if enabled == nil then enabled = true end
endThe same applies to numbers when 0 is meaningful in your code. 0 is truthy in Lua, so count or 10 is safe, but value ~= nil is what you want whenever false is a legitimate value.
The related idiom cond and a or b is not a ternary operator. It breaks when a is false or nil, because the and fails and you get b:
local label = isDead and false or 'alive' -- always 'alive'Use it for values you know are never false or nil, and write a real if otherwise.
Prefer the positive form
not costs the reader a mental flip, and two of them cost a re-read:
-- Backwards
if not isOnDuty then
hideBlip()
else
showBlip()
end
-- Forwards
if isOnDuty then
showBlip()
else
hideBlip()
endName variables positively for the same reason: isReady reads better than isNotReady, and if not isNotReady is how you get bugs.
The exception is a guard clause, which is nearly always negative on purpose (if not player then return end). That is fine: there is no else to flip, and the negative form is the whole point.
A lookup instead of a chain
Once an if / elseif chain is comparing the same variable against five constants, a table says it better and does not get longer with each new case:
-- Grows a branch per item
if item == 'water' then
restoreThirst(20)
elseif item == 'cola' then
restoreThirst(35)
elseif item == 'coffee' then
restoreThirst(15)
end
-- Grows a line per item, and the data can move to a config file
local DRINKS <const> = { water = 20, cola = 35, coffee = 15 }
local thirst = DRINKS[item]
if thirst then restoreThirst(thirst) endThe table version is also a single hash lookup instead of up to five comparisons, which matters if this runs per frame or per item in a loop.
Next: Tables and loops.
