luau-best-practices について
LuauでRobloxゲームを開発する人向けのリファレンススキルです。コード構成、サーバー側の検証、エラー処理、メモリのクリーンアップ、一般的なパターンを解説します。
主な機能
- 01Luauの命名規則とファイル構成
- 02サーバーとクライアントのモジュールパターン
- 03外部呼び出しのエラー処理
- 04接続の解除とメモリ管理
- 05入力検証と不正対策
- 06現代的な構文とアンチパターンの指針
活用シーン
- 01Robloxゲームのモジュールやサービスの構成
- 02サーバーとクライアントの境界や入力検証の確認
- 03API障害への対応とリソースのクリーンアップ
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dig1t/ skills --skill luau-best-practicesインストール後、Claude Code で/luau-best-practicesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/code-style.mderror-handling.mdmemory.mdpatterns.mdsecurity.md詳細
Luau のベストプラクティス
Roblox のゲーム開発における、本番環境向けの品質の高いパターンです。
基本原則
- サーバー権限 - ゲームの状態はサーバーが管理し、クライアントは表示を担当します
- 早期に失敗させる - 早い段階で検証し、開発中は明確にエラーを通知します
- 暗黙より明示 - 巧妙なコードより、意図が明確なコードを優先します
- 公開範囲を最小限にする - 必要なものだけを公開します
コードスタイル
命名規則
-- 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 = {}
ファイル構成
ファイル内の順序:--!strict → サービス/インポート → 定数 → 型 → モジュールテーブル → プライベート状態 → プライベート関数 → 公開 API → return。references/code-style.md に完全なテンプレートがあります。
モジュールパターン
サービスパターン(サーバー)
--!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 MyService
コントローラーパターン(クライアント)
--!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 MyController
その他のパターン(シグナル、状態管理、遅延初期化)については、references/patterns.md を参照してください。
エラー処理
外部呼び出しには pcall を使用する
-- 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 result
プログラミングエラー(決して発生すべきでない事象)には assert を使用し、正当に失敗する可能性のある外部呼び出しには pcall と warn を使用します。Result 型と再試行パターンについては、references/error-handling.md を参照してください。
メモリ管理
必ず切断する
local connection: RBXScriptConnection
connection = event:Connect(function()
-- handler
end)
-- Later, cleanup:
connection:Disconnect()
Maids/Janitors を使用する
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()
キャッシュ用の弱参照テーブルや、その他のリーク防止パターンについては、references/memory.md を参照してください。
セキュリティのベストプラクティス
サーバー権限
-- 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)
すべての入力を検証する
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)
end
レート制限やエクスプロイト対策のパターンについては、references/security.md を参照してください。
よくあるアンチパターン
避ける
-- 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
-- Dot-index chains on instances
workspace.Folder.SubFolder.Part -- Errors if missing
-- GOOD: Safe navigation
local folder = workspace:FindFirstChild("Folder")
local subFolder = folder and folder:FindFirstChild("SubFolder")
local part = subFolder and subFolder:FindFirstChild("Part")
推奨
-- 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.Name
プロジェクト構成
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 packages
クイックリファレンス
| 推奨 | 非推奨 |
|---|---|
task.wait() | wait() |
task.spawn() | spawn() |
task.delay() | delay() |
for _, v in t | for _, v in pairs(t) |
| サーバーで検証する | クライアントのデータを信頼する |
| 型を使用する | あらゆる箇所で any を使用する |
| イベントを切断する | 接続を放置する |
| 定数を使用する | マジックナンバー/文字列 |
| 早期に return する | 深くネストする |
| 小さな関数 | 200 行以上の関数 |
参考資料
- コードスタイルガイド - 命名、書式、構成
- 一般的なパターン - サービス、シグナル、状態管理
- エラー処理 - pcall、Result 型、再試行
- メモリ管理 - クリーンアップ、リーク、弱参照テーブル
- セキュリティ - サーバー権限、検証、エクスプロイト対策
コメント
Markdown 対応