返回首页

教程导航

返回首页

🏷️ 标签(1)

🗂️ 分类(1)

第 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 日志,应使用经过测试的编码器并处理循环引用、特殊字符和编码问题。

健康检查

  • 进程是否存活。

  • 关键依赖是否可用。

  • 队列是否积压。

  • 数据文件是否可读可写。

  • 最近业务流程是否持续失败。

健康检查本身必须有超时,不能因检查逻辑阻塞而拖垮服务。

持续学习路线

完成本教程后,建议继续:

  1. 阅读 Lua 5.4 参考手册。

  2. 研究一个成熟 Lua 项目的模块布局和测试方式。

  3. 学习 LuaJIT 与标准 Lua 的差异,但不要假设两者完全兼容。

  4. 阅读所使用宿主引擎的 Lua API 文档。

  5. 为开源 Lua 库补充测试或文档。

  6. 在实际项目中建立性能基线和错误监控。

推荐练习

  • 把任务管理 CLI 改为 JSON 存储。

  • 为配置模块增加环境变量覆盖。

  • 使用协程实现流式日志读取。

  • 为一个 C 库编写最小 Lua 绑定。

  • 建立 Lua 5.1、5.3 和 5.4 的兼容测试矩阵。

  • 对一个真实 Lua 模块做安全审计。

结语

Lua 的核心概念很少,但组合方式很多。可维护的 Lua 项目依赖清晰的模块边界、显式错误处理、严格的输入校验和可重复测试。

当你能解释“为什么这样组织表、何时使用元表、协程由谁调度、错误由谁处理”时,就已经掌握了 Lua 工程实践的核心。

上一章:测试、调试与性能 | 返回完整目录