luau-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLuau Best Practices
Luau 最佳实践
Production-quality patterns for Roblox game development.
面向Roblox游戏开发的生产级代码模式。
Core Principles
核心原则
- Server Authority - Server owns game state; client is for presentation
- Fail Fast - Validate early, error loudly in development
- Explicit > Implicit - Clear intent beats clever code
- Minimal Surface Area - Expose only what's needed
- Server Authority(服务器权威) - 服务器掌控游戏状态,客户端仅负责展示
- 快速失败 - 尽早验证,开发阶段明确抛出错误
- 显式优于隐式 - 清晰的代码意图胜过“聪明”的代码
- 最小暴露面 - 仅暴露必要的内容
Code Style
代码风格
Naming Conventions
命名规范
lua
-- PascalCase: Types, Classes, Services, Modules
type PlayerData = { ... }
local ShopService = {}
local PlayerController = require(...)
-- camelCase: Variables, functions, methods
local playerCount = 0
local function getPlayerData() end
function ShopService:purchaseItem() end
-- SCREAMING_SNAKE_CASE: Constants
local MAX_PLAYERS = 50
local DEFAULT_HEALTH = 100
-- Private with underscore prefix
local function _validateInput() end
local _cache = {}lua
-- PascalCase:类型、类、服务、模块
type PlayerData = { ... }
local ShopService = {}
local PlayerController = require(...)
-- camelCase:变量、函数、方法
local playerCount = 0
local function getPlayerData() end
function ShopService:purchaseItem() end
-- SCREAMING_SNAKE_CASE:常量
local MAX_PLAYERS = 50
local DEFAULT_HEALTH = 100
-- 以下划线开头表示私有
local function _validateInput() end
local _cache = {}File Organization
文件组织
lua
--!strict
-- 1. Services/imports at top
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Signal = require(ReplicatedStorage.Packages.Signal)
local Types = require(script.Parent.Types)
-- 2. Constants
local MAX_RETRIES = 3
local TIMEOUT = 5
-- 3. Types
type Config = {
enabled: boolean,
maxItems: number,
}
-- 4. Module table
local MyModule = {}
-- 5. Private state
local _initialized = false
local _cache: { [string]: any } = {}
-- 6. Private functions
local function _helperFunction()
end
-- 7. Public API
function MyModule.init()
end
function MyModule.doSomething()
end
-- 8. Return
return MyModulelua
--!strict
-- 1. 服务/导入放在顶部
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Signal = require(ReplicatedStorage.Packages.Signal)
local Types = require(script.Parent.Types)
-- 2. 常量定义
local MAX_RETRIES = 3
local TIMEOUT = 5
-- 3. 类型定义
type Config = {
enabled: boolean,
maxItems: number,
}
-- 4. 模块表
local MyModule = {}
-- 5. 私有状态
local _initialized = false
local _cache: { [string]: any } = {}
-- 6. 私有函数
local function _helperFunction()
end
-- 7. 公开API
function MyModule.init()
end
function MyModule.doSomething()
end
-- 8. 返回模块
return MyModuleModule Patterns
模块模式
Service Pattern (Server)
服务模式(服务器端)
lua
--!strict
local MyService = {}
local _started = false
function MyService:Start()
assert(not _started, "MyService already started")
_started = true
-- Initialize connections, load data
end
function MyService:Stop()
-- Cleanup for hot-reloading
end
return MyServicelua
--!strict
local MyService = {}
local _started = false
function MyService:Start()
assert(not _started, "MyService already started")
_started = true
-- 初始化连接、加载数据
end
function MyService:Stop()
-- 热重载时的清理逻辑
end
return MyServiceController Pattern (Client)
控制器模式(客户端)
lua
--!strict
local MyController = {}
local _player = game:GetService("Players").LocalPlayer
function MyController:Init()
-- Setup without yielding
end
function MyController:Start()
-- Connect events, start loops
end
return MyControllerlua
--!strict
local MyController = {}
local _player = game:GetService("Players").LocalPlayer
function MyController:Init()
-- 无阻塞初始化
end
function MyController:Start()
-- 连接事件、启动循环
end
return MyControllerLazy Initialization
延迟初始化
lua
local _data: PlayerData? = nil
local function getData(): PlayerData
if not _data then
_data = loadExpensiveData()
end
return _data
endlua
local _data: PlayerData? = nil
local function getData(): PlayerData
if not _data then
_data = loadExpensiveData()
end
return _data
endError Handling
错误处理
Use pcall for External Calls
对外部调用使用pcall
lua
-- DataStore, HTTP, any Roblox API that can fail
local success, result = pcall(function()
return dataStore:GetAsync(key)
end)
if not success then
warn("DataStore failed:", result)
return nil
end
return resultlua
-- DataStore、HTTP、任何可能失败的Roblox API
local success, result = pcall(function()
return dataStore:GetAsync(key)
end)
if not success then
warn("DataStore failed:", result)
return nil
end
return resultResult Pattern
结果模式
lua
type Result<T> =
{ ok: true, value: T } |
{ ok: false, error: string }
local function fetchData(id: string): Result<Data>
local success, data = pcall(function()
return dataStore:GetAsync(id)
end)
if not success then
return { ok = false, error = tostring(data) }
end
return { ok = true, value = data }
endlua
type Result<T> =
{ ok: true, value: T } |
{ ok: false, error: string }
local function fetchData(id: string): Result<Data>
local success, data = pcall(function()
return dataStore:GetAsync(id)
end)
if not success then
return { ok = false, error = tostring(data) }
end
return { ok = true, value = data }
endAssert for Programming Errors
使用assert捕获编程错误
lua
-- Use assert for things that should never happen
function processPlayer(player: Player)
assert(player, "player is required")
assert(player:IsA("Player"), "expected Player instance")
-- ...
endSee references/error-handling.md for comprehensive patterns.
lua
-- 对绝不应该发生的情况使用assert
function processPlayer(player: Player)
assert(player, "player is required")
assert(player:IsA("Player"), "expected Player instance")
-- ...
end查看 references/error-handling.md 获取完整的错误处理模式。
Memory Management
内存管理
Always Disconnect
务必断开连接
lua
local connection: RBXScriptConnection
connection = event:Connect(function()
-- handler
end)
-- Later, cleanup:
connection:Disconnect()lua
local connection: RBXScriptConnection
connection = event:Connect(function()
-- 事件处理逻辑
end)
-- 后续清理:
connection:Disconnect()Use Maids/Janitors
使用Maid/Janitor工具
lua
local Maid = require(Packages.Maid)
local maid = Maid.new()
maid:GiveTask(event:Connect(handler))
maid:GiveTask(instance)
maid:GiveTask(function()
-- Custom cleanup
end)
-- Cleanup everything at once
maid:Destroy()lua
local Maid = require(Packages.Maid)
local maid = Maid.new()
maid:GiveTask(event:Connect(handler))
maid:GiveTask(instance)
maid:GiveTask(function()
-- 自定义清理逻辑
end)
-- 一次性清理所有资源
maid:Destroy()Weak References for Caches
对缓存使用弱引用
lua
local cache = setmetatable({}, { __mode = "v" })
-- Values are garbage collected when no other references exist
cache[key] = expensiveObjectSee references/memory.md for leak prevention patterns.
lua
local cache = setmetatable({}, { __mode = "v" })
-- 当没有其他引用时,值会被垃圾回收
cache[key] = expensiveObject查看 references/memory.md 获取泄漏预防的完整模式。
Security Best Practices
安全最佳实践
Server Authority
服务器权威原则
lua
-- BAD: Client tells server what happened
RemoteEvent.OnServerEvent:Connect(function(player, damage)
target.Health -= damage -- Client controls damage!
end)
-- GOOD: Server calculates everything
RemoteEvent.OnServerEvent:Connect(function(player, targetId)
local target = getValidTarget(player, targetId)
if not target then return end
local damage = calculateDamage(player) -- Server calculates
target.Health -= damage
end)lua
-- 错误示例:客户端告知服务器发生了什么
RemoteEvent.OnServerEvent:Connect(function(player, damage)
target.Health -= damage -- 客户端可以随意控制伤害!
end)
-- 正确示例:服务器负责所有计算
RemoteEvent.OnServerEvent:Connect(function(player, targetId)
local target = getValidTarget(player, targetId)
if not target then return end
local damage = calculateDamage(player) -- 服务器计算伤害
target.Health -= damage
end)Validate All Input
验证所有输入
lua
RemoteFunction.OnServerInvoke = function(player, itemId, quantity)
-- Type validation
if typeof(itemId) ~= "string" then return end
if typeof(quantity) ~= "number" then return end
-- Range validation
if quantity < 1 or quantity > 99 then return end
if quantity ~= math.floor(quantity) then return end
-- Business logic validation
if not Items[itemId] then return end
if not canAfford(player, itemId, quantity) then return end
-- Now safe to process
return purchaseItem(player, itemId, quantity)
endlua
RemoteFunction.OnServerInvoke = function(player, itemId, quantity)
-- 类型验证
if typeof(itemId) ~= "string" then return end
if typeof(quantity) ~= "number" then return end
-- 范围验证
if quantity < 1 or quantity > 99 then return end
if quantity ~= math.floor(quantity) then return end
-- 业务逻辑验证
if not Items[itemId] then return end
if not canAfford(player, itemId, quantity) then return end
-- 此时处理请求是安全的
return purchaseItem(player, itemId, quantity)
end)Rate Limiting
速率限制
lua
local lastAction: { [Player]: number } = {}
local COOLDOWN = 0.5
local function isRateLimited(player: Player): boolean
local now = os.clock()
local last = lastAction[player] or 0
if now - last < COOLDOWN then
return true
end
lastAction[player] = now
return false
endSee references/security.md for comprehensive security patterns.
lua
local lastAction: { [Player]: number } = {}
local COOLDOWN = 0.5
local function isRateLimited(player: Player): boolean
local now = os.clock()
local last = lastAction[player] or 0
if now - last < COOLDOWN then
return true
end
lastAction[player] = now
return false
end查看 references/security.md 获取完整的安全模式。
Common Anti-Patterns
常见反模式
Avoid
应避免
lua
-- Using wait() - use task.wait()
wait(1) -- BAD
task.wait(1) -- GOOD
-- spawn() - use task.spawn()
spawn(fn) -- BAD
task.spawn(fn) -- GOOD
-- delay() - use task.delay()
delay(1, fn) -- BAD
task.delay(1, fn) -- GOOD
-- Polling when events exist
while true do
if something then break end
task.wait()
end
-- GOOD: Use events/signals instead
-- String concatenation in loops
local s = ""
for i = 1, 1000 do
s = s .. tostring(i) -- O(n²)
end
-- GOOD: Use table.concat
-- FindFirstChild chains
workspace.Folder.SubFolder.Part -- Errors if missing
-- GOOD: Safe navigation
local folder = workspace:FindFirstChild("Folder")
local part = folder and folder:FindFirstChild("SubFolder")
and folder.SubFolder:FindFirstChild("Part")lua
-- 使用wait() - 改用task.wait()
wait(1) -- 错误用法
task.wait(1) -- 正确用法
-- spawn() - 改用task.spawn()
spawn(fn) -- 错误用法
task.spawn(fn) -- 正确用法
-- delay() - 改用task.delay()
delay(1, fn) -- 错误用法
task.delay(1, fn) -- 正确用法
-- 已有事件时仍使用轮询
while true do
if something then break end
task.wait()
end
-- 正确用法:使用事件/信号替代
-- 循环中拼接字符串
local s = ""
for i = 1, 1000 do
s = s .. tostring(i) -- 时间复杂度O(n²)
end
-- 正确用法:使用table.concat
-- 链式调用FindFirstChild
workspace.Folder.SubFolder.Part -- 若中间节点不存在会报错
-- 正确用法:安全导航
local folder = workspace:FindFirstChild("Folder")
local part = folder and folder:FindFirstChild("SubFolder")
and folder.SubFolder:FindFirstChild("Part")Prefer
推荐用法
lua
-- Generalized iteration
for _, v in ipairs(array) do end -- OLD
for _, v in array do end -- MODERN (Luau)
-- If expressions
local x = if condition then a else b -- Clean ternary
-- Continue in loops
for _, item in items do
if not item.valid then continue end
process(item)
end
-- Optional chaining with and
local name = player and player.Character and player.Character.Namelua
-- 通用迭代
for _, v in ipairs(array) do end -- 旧写法
for _, v in array do end -- 现代写法(Luau)
-- 条件表达式
local x = if condition then a else b -- 简洁的三元表达式
-- 循环中使用continue
for _, item in items do
if not item.valid then continue end
process(item)
end
-- 使用and进行可选链式调用
local name = player and player.Character and player.Character.NameProject Structure
项目结构
src/
├── Server/
│ ├── init.server.luau # Bootstrap
│ ├── Services/ # Game services
│ │ ├── DataService.luau
│ │ └── CombatService.luau
│ └── Components/ # Server components
├── Client/
│ ├── init.client.luau # Bootstrap
│ ├── Controllers/ # Client controllers
│ └── UI/ # UI components
├── Shared/
│ ├── Types.luau # Shared type definitions
│ ├── Constants.luau # Shared constants
│ └── Util/ # Shared utilities
└── Packages/ # Wally packagessrc/
├── Server/
│ ├── init.server.luau # 启动文件
│ ├── Services/ # 游戏服务
│ │ ├── DataService.luau
│ │ └── CombatService.luau
│ └── Components/ # 服务器组件
├── Client/
│ ├── init.client.luau # 启动文件
│ ├── Controllers/ # 客户端控制器
│ └── UI/ # UI组件
├── Shared/
│ ├── Types.luau # 共享类型定义
│ ├── Constants.luau # 共享常量
│ └── Util/ # 共享工具类
└── Packages/ # Wally包Quick Reference
快速参考
| Do | Don't |
|---|---|
| |
| |
| |
| |
| Validate on server | Trust client data |
| Use types | Use |
| Disconnect events | Leave connections dangling |
| Use constants | Magic numbers/strings |
| Early return | Deep nesting |
| Small functions | 200+ line functions |
| 推荐做法 | 避免做法 |
|---|---|
| |
| |
| |
| |
| 在服务器端验证 | 信任客户端数据 |
| 使用类型定义 | 到处使用 |
| 断开事件连接 | 让连接悬空 |
| 使用常量 | 魔法数字/字符串 |
| 提前返回 | 深层嵌套 |
| 小函数 | 超过200行的函数 |
References
参考文档
- Code Style Guide - Naming, formatting, organization
- Common Patterns - Services, signals, state management
- Error Handling - pcall, Result types, retries
- Memory Management - Cleanup, leaks, weak tables
- Security - Server authority, validation, anti-exploit
- Code Style Guide - 命名、格式化、组织方式
- Common Patterns - 服务、信号、状态管理
- Error Handling - pcall、Result类型、重试
- Memory Management - 清理、泄漏、弱表
- Security - 服务器权威、验证、反作弊