第 20 章 Lua 开发最佳实践
第 20 章 Lua 开发最佳实践
Lua 的自由度很高,团队更需要明确约定。本章汇总命名、模块、依赖、安全、可观测性和持续学习方面的实践。
代码风格与命名
基本约定
-
使用两个空格缩进,不使用制表符混排。
-
局部变量使用
camelCase或团队统一的snake_case,不要在项目内混用。 -
模块表命名为
M,类可以使用 PascalCase。 -
常量使用全大写,例如
MAX_RETRIES。 -
文件名使用小写短横线或下划线,并保持项目一致。
-
函数在参数不合法时返回明确错误,或使用
assert标注调用方违反了前置条件。
函数长度
长函数通常混合了输入校验、业务逻辑、持久化和输出。可以按阶段拆分:
local function parseInput(arguments)
-- 只做解析和校验
end
local function execute(command, state, options)
-- 只做业务操作
end
local function render(result)
-- 只做输出
end
避免隐式全局变量
启用静态检查工具可以帮助发现拼写导致的全局变量。运行时也可以使用简单检查:
local function assertNoUnexpectedGlobals()
local unexpected = {}
for key in pairs(_G) do
if not key:match("^_") then
unexpected[#unexpected + 1] = key
end
end
return unexpected
end
这只是诊断工具,不能替代静态分析。
项目目录与版本管理
推荐的中型项目结构:
lua-service/
├── src/
│ ├── main.lua
│ ├── config.lua
│ ├── adapters/
│ └── domain/
├── tests/
├── data/
├── scripts/
├── README.md
└── rockspec
-
domain保存业务规则,不直接依赖文件或网络。 -
adapters实现外部系统访问。 -
main.lua负责组装依赖和处理进程生命周期。 -
tests与源码结构对应,但测试名描述行为。 -
生成文件、日志和本地配置不提交版本库。
提交与发布
提交前检查:
-
测试和格式化是否通过。
-
是否意外包含凭据或大文件。
-
是否更新了变更日志和兼容性说明。
-
数据格式变化是否有迁移脚本。
-
发布包是否包含许可证和 README。
兼容性与依赖策略
明确最低版本
在 README 中写明支持的 Lua 版本:
Tested with Lua 5.4.7 on Linux, macOS and Windows.
跨版本项目应持续在目标版本上测试,而不是只在最新版本上验证。
避免依赖加载顺序
模块不应假设另一个模块已经通过全局变量初始化。依赖应通过 require、参数注入或显式初始化传递。
依赖审计
-
固定版本或提交哈希。
-
检查维护状态、许可证和传递依赖。
-
避免引入只为节省十几个函数而增加的庞大运行时。
-
对 C 扩展额外检查编译器、平台和二进制兼容性。
安全与沙箱
不可信输入
始终把网络、文件、命令行参数和环境变量视为不可信输入。校验类型、长度、范围和字符集,不要只做字符串替换。
不可信脚本
如果应用需要执行用户脚本:
-
在独立进程中运行,而不是只依赖同进程沙箱。
-
限制 CPU、内存、文件、网络和进程权限。
-
移除
io、os、package、debug等危险库。 -
为宿主 API 做授权检查。
-
记录脚本来源和请求 ID。
数据加载
不要使用 load 或 loadfile 解析来自用户、网络或第三方的数据。Lua 代码文件就是可执行代码,不是安全的配置格式。
错误信息
错误信息应帮助修正输入,但不能暴露服务器路径、SQL、内部令牌或不必要的堆栈。日志与用户响应应使用不同字段。
可观测性与运维
结构化日志
日志至少包括时间、级别、事件名和关键上下文:
local function log(level, event, fields)
local parts = {
string.format("time=%s", os.date("!%Y-%m-%dT%H:%M:%SZ")),
"level=" .. level,
"event=" .. event
}
for key, value in pairs(fields or {}) do
parts[#parts + 1] = string.format(
"%s=%q",
key,
tostring(value)
)
end
io.stderr:write(table.concat(parts, " "), "\n")
end
若使用 JSON 日志,应使用经过测试的编码器并处理循环引用、特殊字符和编码问题。
健康检查
-
进程是否存活。
-
关键依赖是否可用。
-
队列是否积压。
-
数据文件是否可读可写。
-
最近业务流程是否持续失败。
健康检查本身必须有超时,不能因检查逻辑阻塞而拖垮服务。
持续学习路线
完成本教程后,建议继续:
-
阅读 Lua 5.4 参考手册。
-
研究一个成熟 Lua 项目的模块布局和测试方式。
-
学习 LuaJIT 与标准 Lua 的差异,但不要假设两者完全兼容。
-
阅读所使用宿主引擎的 Lua API 文档。
-
为开源 Lua 库补充测试或文档。
-
在实际项目中建立性能基线和错误监控。
推荐练习
-
把任务管理 CLI 改为 JSON 存储。
-
为配置模块增加环境变量覆盖。
-
使用协程实现流式日志读取。
-
为一个 C 库编写最小 Lua 绑定。
-
建立 Lua 5.1、5.3 和 5.4 的兼容测试矩阵。
-
对一个真实 Lua 模块做安全审计。
结语
Lua 的核心概念很少,但组合方式很多。可维护的 Lua 项目依赖清晰的模块边界、显式错误处理、严格的输入校验和可重复测试。
当你能解释“为什么这样组织表、何时使用元表、协程由谁调度、错误由谁处理”时,就已经掌握了 Lua 工程实践的核心。