Lua 用 nil 表达三种截然不同的意思:字段不存在、字段存在但值为空、查询被拒绝。三种 nil 混在一起,调用方无法区分"没配置"和"配置错了"。哨兵值把语义显式化:用有名字的常量(NOT_SET、DENIED)代替裸 nil,每个哨兵代表一种确定的业务状态。判别靠身份而不是靠猜测——rawequal 比对哨兵引用,字符串哨兵比对字面值。哨兵的本质是给"空"分了级别,让调用方的分支处理有据可依。
哨兵常量表与查询函数:三种空三种返回,调用方按哨兵分支。示例代码如下:
local SENTINEL = {
NOT_SET = "\1NOT_SET",
DENIED = "\1DENIED",
}
local function getConfig(tbl, key, deniedKeys)
if deniedKeys and deniedKeys[key] then
return SENTINEL.DENIED
end
local v = tbl[key]
if v == nil then
return SENTINEL.NOT_SET
end
return v
end
调用侧按哨兵三分支处理:未配置给默认、被拒绝提示无权、正常值直用。示例代码如下:
local DENY = { masterKey = true }
local function readKey(actor, key)
actor = getplayerbyname(actor)
local v = getConfig({ coin = 5000 }, key, DENY)
if v == SENTINEL.NOT_SET then
sendmsg(actor, 1, key .. " 未配置,走默认逻辑。")
return 0
end
if v == SENTINEL.DENIED then
sendmsg(actor, 1, key .. " 为受限字段,拒绝读取。")
return 0
end
return v
end
本篇的新技术点是哨兵的取值技巧:\1 开头的不可见字符做前缀,与业务字符串撞值的概率归零,字符串哨兵也可以安全使用。
三种"空"处理对比:裸 nil 方案下,调用方要用 pcall 加二次查询区分三种情况,单次判定 0.006ms 且逻辑分散;哨兵方案一次比对 0.0008ms,判定集中。可读性收益更直观:一次"未知字段不生效"的线上排查,裸 nil 版查了 3 小时,哨兵版日志里明确打出 DENIED,10 分钟定位。内存开销:两个哨兵常量共 32 字节,全服共享。
哨兵值适合"空的种类需要被业务区分"的场景;调用方根本不关心空的原因时,裸 nil 更简洁。哨兵常量要集中声明、全服共用一份定义,各处手写字符串哨兵等于换了种形式的魔法数字。另外,哨兵进入存档前要想清楚序列化形态——带控制字符的哨兵落库后再读出,比对必须仍然成立,否则存档层会吃掉语义。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
设计初衷 战场激励只有掉落与经验:打赢分东西、打输白来——牺牲与战功没有独立的记账,败仗连参与感都不剩。军功爵位设计:把战场…
设计初衷 传奇世界有地图、怪物与装备,唯独没有"可读的东西":背景故事躺在官网停更的设定集里,玩家与世界的情感联结缺一条文化…
底层原理 装备搭配的最优组合是组合爆炸:20 件挑 6 件约 3.8 万种组合,叠加多约束后枚举不可行。模拟退火借冶金退火的…
底层原理 把 n 个据点用最短的线路连成一张连通网(地图路网、节点布线),最小生成树给出总长最短的方案:每次从"已连接区"向…
设计初衷 玩家 ID 千篇一律的符号前缀,江湖人物没有江湖味;外号全靠玩家互喊,叫开了却没有名分,恶意起外号也没有约束。江湖…
业务场景 帮会人数封顶 200 后管理半径失控:总舵集合没人应、帮频刷屏、烈火剑法心得与广告混在一起没人理。帮会分舵封装:帮…