
一、一个“看起来很简单”的功能简记往来最受欢迎的功能之一是“批量记礼”。用户把纸上的礼单敲成文本一次性粘贴进去张叔叔 800 李阿姨 500 表哥 1000系统自动解析出姓名和金额一次性生成所有记录。看起来很简单对吧但实际上这个功能的正则表达式写了5版才稳定。二、第一版只支持“姓名 金额”第一版我写了一个最简单的正则constreg/^([^\d\s])\s(\d)$/逻辑匹配“非数字非空格的字符姓名 至少一个空格 数字金额”。上线后用户反馈来了“我输入‘张叔叔800’为什么解析不了”“我输入‘表哥1000.00’为什么报错”“我输入‘李阿姨500婚礼’怎么办”第一版只支持最完美的“姓名 空格 金额”格式但用户的真实输入五花八门。三、第二版支持无空格既然用户可能不敲空格那就让正则支持“姓名金额”的格式constreg/^([^\d])\s*(\d(?:\.\d)?)$/改动点\s*让空格变成“可有可无”(\d(?:\.\d)?)支持了带小数的金额支持了“张叔叔800”但“王二小800”又成了新问题——正则把“二”也当成了数字的一部分。四、第三版明确匹配中英文第三版用更精确的字符集替换了“排除数字”的偷懒写法constmatchline.match(/^([\u4e00-\u9fa5a-zA-Z·])\s*([\d.])/)改动点[\u4e00-\u9fa5a-zA-Z·]明确匹配中文、英文和中间点少数民族姓名不再用“排除数字”这种偷懒写法这次稳定了很多但“李阿姨500婚礼”这种带备注的还是不行。五、第四版逐行独立解析 多格式尝试第四版换了思路逐个尝试不同的格式哪个能匹配就用哪个。functiontryParse(line){lineline.trim()// 尝试1标准“姓名 金额”letmatchline.match(/^([\u4e00-\u9fa5a-zA-Z·])\s([\d.])/)if(match)return{name:match[1].trim(),amount:parseFloat(match[2])}// 尝试2无空格“姓名金额”matchline.match(/^([\u4e00-\u9fa5a-zA-Z·])([\d.])/)if(match)return{name:match[1].trim(),amount:parseFloat(match[2])}// 尝试3金额在末尾“任意内容 金额”matchline.match(/([\d.])$/)if(match){constnamePartline.replace(/([\d.])$/,).trim()if(namePart.length0){return{name:namePart,amount:parseFloat(match[1])}}}returnnull}这次覆盖了大部分场景但个别边缘情况仍然解析失败。六、第五版容错 预览编辑最终版的核心逻辑functionparseBatch(text){constlinestext.split(\n).filter(ll.trim())constresults[]for(constlineoflines){constparsedtryParse(line)if(parsed){results.push(parsed)}else{// 解析失败标记为“待修正”results.push({error:true,raw:line,name:,amount:null})}}returnresults}同时在前端做了防抖处理——用户停止输入300ms后才触发解析避免频繁计算。核心教训不要追求一次性完美解析。给用户预览和修正的机会比追求100%准确更重要。第一版追求完美结果连50%的输入都覆盖不了第五版接受“不完美”反而覆盖了95%以上的真实输入场景。七、5次迭代的总结版本策略结果第一版只支持“姓名 金额”覆盖50%用户抱怨第二版支持无空格覆盖约70%中文数字问题第三版明确匹配字符集覆盖约80%备注问题第四版多格式尝试覆盖约90%边缘问题第五版容错预览编辑覆盖95%用户可修正八、经验教训不要试图用一个正则覆盖所有场景。先把80%的常见场景处理好剩下的通过产品设计让用户自己修正。用户需要的是“帮我省90%的力剩下10%我自己来”而不是“100%完美但只能处理50%的场景”。下一篇我们来聊聊批量记礼的防抖与预览编辑设计。评论区聊聊你处理用户输入不规范时用了什么方案