Web Analytics

Best Practices in Lua

Intermediate ~25 min read

Writing clean, efficient, and maintainable code is essential for professional development. In this lesson, you'll learn Lua best practices covering code style, naming conventions, performance optimization, security, and professional patterns. Let's explore how to write excellent Lua code!

Code Style and Formatting

Indentation and Spacing

-- Good: Consistent 4-space indentation
function calculateTotal(items)
    local total = 0
    for i, item in ipairs(items) do
        if item.price > 0 then
            total = total + item.price
        end
    end
    return total
end

-- Bad: Inconsistent indentation
function calculateTotal(items)
local total = 0
  for i, item in ipairs(items) do
      if item.price > 0 then
    total = total + item.price
      end
  end
return total
end

Line Length

-- Good: Keep lines under 80-100 characters
local function createUser(name, email, age, address, phone)
    return {
        name = name,
        email = email,
        age = age,
        address = address,
        phone = phone
    }
end

-- Bad: Very long lines
local function createUser(name, email, age, address, phone) return {name = name, email = email, age = age, address = address, phone = phone} end
Output
Click Run to execute your code

Naming Conventions

Variables and Functions

-- Good: Descriptive, lowercase with underscores
local user_count = 0
local is_active = true
local max_retry_attempts = 3

function calculate_total_price(items)
    -- Implementation
end

-- Bad: Unclear, inconsistent
local uc = 0
local IsActive = true
local MAXRETRY = 3

function calcTP(i)
    -- Implementation
end

Constants

-- Good: UPPERCASE with underscores
local MAX_CONNECTIONS = 100
local DEFAULT_TIMEOUT = 30
local API_VERSION = "1.0"

-- Bad: Lowercase like variables
local maxConnections = 100
local defaulttimeout = 30

Classes and Modules

-- Good: PascalCase for classes
local UserManager = {}
local DatabaseConnection = {}
local HttpClient = {}

-- Good: lowercase for modules
local string_utils = require("string_utils")
local math_helpers = require("math_helpers")

Always Use Local Variables

-- Good: Local variables
local function processData(data)
    local result = {}
    local count = 0
    
    for i, item in ipairs(data) do
        count = count + 1
        table.insert(result, item)
    end
    
    return result, count
end

-- Bad: Global variables (pollutes global namespace)
function processData(data)
    result = {}  -- Global!
    count = 0    -- Global!
    
    for i, item in ipairs(data) do
        count = count + 1
        table.insert(result, item)
    end
    
    return result, count
end
Warning: Global variables can cause hard-to-find bugs and naming conflicts. Always use local unless you specifically need a global.

Performance Best Practices

Cache Table Lookups

-- Good: Cache frequently accessed values
local function processItems(items)
    local insert = table.insert  -- Cache table.insert
    local result = {}
    
    for i = 1, #items do
        insert(result, items[i] * 2)
    end
    
    return result
end

-- Less efficient: Repeated table lookups
local function processItems(items)
    local result = {}
    
    for i = 1, #items do
        table.insert(result, items[i] * 2)  -- Looks up table.insert every time
    end
    
    return result
end

Use table.concat() for String Building

-- Good: Use table.concat()
local function buildString(parts)
    return table.concat(parts, ", ")
end

-- Bad: String concatenation in loop
local function buildString(parts)
    local result = ""
    for i, part in ipairs(parts) do
        result = result .. part .. ", "  -- Creates new string each iteration
    end
    return result
end

Reuse Tables

-- Good: Reuse table
local temp = {}
for i = 1, 1000 do
    -- Clear and reuse
    for k in pairs(temp) do
        temp[k] = nil
    end
    -- Use temp
end

-- Less efficient: Create new table each iteration
for i = 1, 1000 do
    local temp = {}  -- New allocation each time
    -- Use temp
end
Output
Click Run to execute your code

Error Handling Best Practices

Validate Input Early

-- Good: Validate at function entry
local function divide(a, b)
    assert(type(a) == "number", "First argument must be a number")
    assert(type(b) == "number", "Second argument must be a number")
    assert(b ~= 0, "Cannot divide by zero")
    
    return a / b
end

-- Bad: No validation
local function divide(a, b)
    return a / b  -- May cause runtime errors
end

Return nil, error for Expected Failures

-- Good: Return nil, error
local function readConfig(filename)
    local file, err = io.open(filename, "r")
    if not file then
        return nil, "Failed to open config: " .. err
    end
    
    local content = file:read("*all")
    file:close()
    
    return content
end

-- Usage
local config, err = readConfig("config.txt")
if not config then
    print("Error:", err)
    return
end

Code Organization

Keep Functions Small

-- Good: Small, focused functions
local function validateEmail(email)
    return email:match("^[%w.]+@[%w.]+%.%w+$") ~= nil
end

local function validateAge(age)
    return type(age) == "number" and age >= 0 and age <= 150
end

local function createUser(name, email, age)
    if not validateEmail(email) then
        return nil, "Invalid email"
    end
    if not validateAge(age) then
        return nil, "Invalid age"
    end
    
    return {name = name, email = email, age = age}
end

-- Bad: One large function
local function createUser(name, email, age)
    if not email:match("^[%w.]+@[%w.]+%.%w+$") then
        return nil, "Invalid email"
    end
    if type(age) ~= "number" or age < 0 or age > 150 then
        return nil, "Invalid age"
    end
    -- More validation...
    -- More logic...
    -- Gets hard to read and maintain
end

Single Responsibility Principle

-- Good: Each function has one responsibility
local function loadData(filename)
    -- Only loads data
    local file = io.open(filename, "r")
    local content = file:read("*all")
    file:close()
    return content
end

local function parseData(content)
    -- Only parses data
    local data = {}
    for line in content:gmatch("[^\n]+") do
        table.insert(data, line)
    end
    return data
end

local function processData(data)
    -- Only processes data
    local result = {}
    for i, item in ipairs(data) do
        table.insert(result, item:upper())
    end
    return result
end

-- Bad: One function does everything
local function loadAndProcessData(filename)
    -- Loads, parses, and processes - too many responsibilities
end

Documentation

--[[
    Calculates the total price of items in a shopping cart
    
    @param items table Array of item objects with 'price' and 'quantity' fields
    @param discount number Optional discount percentage (0-100)
    @return number Total price after discount
    @return number Amount saved from discount
]]
local function calculateTotal(items, discount)
    discount = discount or 0
    
    local subtotal = 0
    for i, item in ipairs(items) do
        subtotal = subtotal + (item.price * item.quantity)
    end
    
    local discountAmount = subtotal * (discount / 100)
    local total = subtotal - discountAmount
    
    return total, discountAmount
end
Output
Click Run to execute your code

Security Best Practices

Security Tips:
  • Validate all input: Never trust user input
  • Avoid load() and loadstring(): Can execute arbitrary code
  • Sanitize file paths: Prevent directory traversal
  • Use pcall() for untrusted code: Contain errors
  • Don't expose debug library: In production

Best Practices Checklist

✓ Code Style:
  • Consistent indentation (4 spaces)
  • Lines under 80-100 characters
  • Meaningful variable names
  • Proper spacing around operators
✓ Variables:
  • Always use local
  • Descriptive names
  • UPPERCASE for constants
✓ Functions:
  • Keep functions small and focused
  • Validate input early
  • Return nil, error for failures
  • Document complex functions
✓ Performance:
  • Cache table lookups
  • Use table.concat() for strings
  • Reuse tables when possible
  • Profile before optimizing
✓ Organization:
  • One module, one responsibility
  • Group related functions
  • Use meaningful file names
  • Keep modules focused

Practice Exercise

Review and improve this code:

Output
Click Run to execute your code

Summary

In this lesson, you learned:

  • Code style and formatting guidelines
  • Naming conventions for variables, functions, and modules
  • Always using local variables
  • Performance optimization techniques
  • Error handling best practices
  • Code organization principles
  • Documentation standards
  • Security considerations

What's Next?

Congratulations on completing Module 6! You now know how to write professional, maintainable Lua code. In the final module, we'll explore advanced topics including coroutines, file I/O, and performance optimization. Let's finish strong! 🚀