开头:两天里撞上的三类坑
做一款宝可梦猜谜小程序(微信原生小程序 + 云开发 NoSQL),两天里集中遇到了三类问题:数据导入的格式约束、一个会直接让云函数崩溃的参数坑,还有一个 UI 体验坑。前两类是「修」,第三类是「加新玩法时顺手栽的」。把它们串起来,其实是一份挺完整的工程化样本——云开发、调试、资源管线、UCD 迭代全占了。
我在这行做了快十年,越往后越觉得,面试里能聊出细节的,往往不是「做了什么功能」,而是「踩了什么坑、怎么定位、怎么收口」。下面这三段,都是从真实日志里扒出来的。
数据层:云数据库导入的 JSON Lines 约束
云开发控制台导入题库时,要求的 .json 文件必须是 JSON Lines:每一行一个对象,不能带外层方括号。
{"_id":"q1","type":"flavor","stem":"...","options":["A","B","C","D"],"answer":0}
{"_id":"q2","type":"superlative","stem":"...","options":["A","B","C","D"],"answer":2}
我第一次导的是标准 JSON 数组,直接报「导入数据格式不正确,请检查是否为 JSON Lines 格式」。更绕的是控制台只识别 .json 扩展名,.jsonl 根本选不进去。解决办法很朴素:把 JSONL 内容存成 .json 后缀。题库 1209 条,_id 从 q1 到 q1209。这种约定型坑不致命,但每次重导都要想一下,固化成脚本生成 + 固定扩展名,比依赖手动导出稳得多。
运行时:getDailyQuiz 的漏参崩溃
getDailyQuiz 一跑就炸,报错 -504002,堆栈指向 pickByType 第 63 行:Cannot read properties of undefined (reading 'has')。
根因很清楚:main 里调用 pickByType(type, n, rng) 只传了三个参数,但函数内部依赖第 4 个参数 usedStems 做跨类型题干去重——usedStems 是 undefined,.has() 直接挂。这个去重设计本身是合理的:题库里有「攻击种族值最高」「防御种族值最高」这类同主题题,跨类型不能出重复题干。
// 修复前
const picks = pickByType(type, n, rng); // usedStems 为 undefined -> .has() 崩溃
// 修复后
const usedStems = new Set();
const picks = pickByType(type, n, rng, usedStems);
修复后与本地 data/test_daily.js 的跨类型去重设计对齐,本地测试(确定性 + 隔日不同)均通过。教训也直白:可选参数要么函数入口给默认值兜底,要么调用处显式传,别留 undefined 让它去访问属性。
资源管线:剪影玩法的素材生成
在原有每日 5 题之外,我新增了「猜猜我是谁」和「剪影猜」两个玩法。剪影猜的逻辑是:每天一只初代 151,全国同题,只给黑色剪影,5 次机会猜名字,猜错给冷热线索(属性对不对、编号偏大偏小、身高体重方向),猜中或次数用尽再淡入彩图揭晓。防作弊上,剪影素材文件名是随机 6 位 key,客户端包里没有 key 到名字的映射。
剪影素材本身是一条 Python 小管线:gen_silhouette_img.py 读取官方 artwork 的 alpha 通道,二值化出透明剪影 PNG(高 200),再同 bbox 生成白底彩图 JPG(高 128),页面叠层对齐。
sil = Image.new("RGBA", img.size, (0, 0, 0, 0))
sil.putalpha(img.split()[3]) # 用 alpha 当剪影形状
bbox = sil.getbbox(); sil = sil.crop(bbox)
ratio = SIL_H / sil.size[1]
sil = sil.resize((int(sil.size[0]*ratio), SIL_H), Image.LANCZOS)
flat_grey = Image.new("L", sil.size, 42) # #2A2A2A 深灰剪影
sil_aa = Image.merge("LA", (flat_grey, sil.split()[3]))
151 只素材 PNG+JPG 共 896KB,整包压在 0.93MB,主包 2MB 内宽松。
体验迭代:zoom 从整体缩放到渐进揭示
最有意思的是 zoom 的两次迭代。第一版我让剪影初始 0.6x 整体缩小,想用「放大聚焦」体现难度递进。用户截图回来:一开始就满屏,猜错根本没「放大」效果。
改成初始 2.0x 局部放大(中心 25% 可见),每猜错一次拉远 0.25x,4 次错后满屏:
function computeZoom(wrongs, over) {
if (over) return 1.0;
return Math.max(1.0, 2.0 - wrongs * 0.25); // 2.0 -> 1.0 渐进
}
配合 .stage { overflow: hidden; transform-origin: center },zoom 大于 1 时超出部分被裁掉,真正实现了「先显示一部分,后慢慢变多」。文案也从「放大聚焦」改成「看剪影一角 / 猜错会露出更多」。
方法论沉淀
回头看这两天,值钱的不是某个功能做没做出来,而是三件事背后的工程习惯:
- 数据正确性靠格式约定兜底,用脚本固化扩展名,而非依赖手动导出。
- 崩溃修复靠参数契约与本地测试对齐,缺省参数在函数入口兜底。
- 体验正确性靠把用户截图当第一手需求,zoom 的「局部放大渐进」比「整体缩放」更符合「先露一角」的心理预期。
一个小程序里同时练到云开发、调试、资源管线和 UCD 迭代,对面试聊细节够用了。完整代码与题库生成脚本已在项目仓库,欢迎交流。