我们在团队里搞过一次月度评选。一开始的方式很原始:发通知、逐人催、收表格、手工汇总、算平均、排榜、再通知。
问题不在于麻烦,而在于它会悄悄毁掉数据本身:
- 打分的人会想:"我给了低分,会不会被看出来?"——只要有人这么想,分数就不是真话
- 汇总靠手工,多维度加权 + 平均值 + 并列名次,算一次得复核一次
- 谁交了、谁没交,只能靠人盯
后来我干脆自己写了一套系统。它不大,但有几个设计决定,我觉得比"写代码"本身更值钱。
一、匿名不是承诺,是数据模型
绝大多数系统做匿名,靠的是"我们不查""权限控制住"——这是承诺,承诺是可以被推翻的。
我换了做法:让系统结构上就查不到。
数据分两张表,物理上无法关联:
周期 + 投票人 ID,唯一约束保证一人一票——不含任何分数。
被评人 + 维度 + 分数——不含投票人,也不含精确时间。
结果是:即使有人拿到数据库,也无法把某一分对应到某一票。匿名从"制度上的承诺"变成了"结构上的事实"。
二、把规则写进代码,窗口自己走
评选必须有一个"窗口期",否则永远有人补分、有人反悔。我们定的规则是:每月前 3 个工作日。
系统按法定节假日表和调休表,自动算出当月窗口的起止日期——周期在第一次被访问时自动创建,不需要任何人去开、去关。窗口内才能打分;窗口结束后才公布排名:先打分、后揭榜,避免有人看到结果再改分。
三、榜单上不该出现的人,就不让它出现
这一条是踩坑换来的。早期版本会把"本期没有参与评选的人"也列进榜单,于是他们显示 0 分。问题是:0 分会被人读成"差评",而且并列名次会被搅乱。
改法很简单——只列本期实际被评到的人。宁可少一行,也不要一行错的。展示层的规则必须跟着业务语义走,不能让默认值污染结果。
四、自动化之后,人只做一件事
- 月初,机器人按工作日表自动发启动卡片
- 窗口内打分(匿名),参与情况实时可见——谁交了、还差几人,一目了然
- 窗口到期自动关闭,系统算出排名
- 结果按需推送,榜单一键可查
人只做一件事:在月初确认一下周期。其余全部交给机器。同一套模型,后来又挂了一个"半年一次的全员互评"(互评 + 自评,匿名机制完全一致)——因为底层设计是通的,边际成本几乎为零。
五、踩过的两个坑
坑一:一张节假日表,存在了两个地方。应用里有一张表算"评分窗口",发卡脚本里另有一张表定"月初哪天发卡"。两张表不同步,症状非常隐蔽——卡片发出的时候,窗口其实还没开(或者已经关了)。有一次就出在长假:旧表少录了几天假期,窗口被算到了法定假日里,结果谁都投不了。
教训:跨系统共享的"事实",必须有唯一来源,或者至少要有一次对账机制。两边都靠人维护,早晚会错开。
坑二:改了代码,为什么没生效?因为部署方式是"源码拷进镜像后构建",改动被打进了构建产物。重启不会重新构建——必须真的 build 一次。现在的固定动作是四步:留回滚点 → build → 重启 → 复核镜像里的源码。少了最后一步,你永远只是"以为生效了"。
六、可以复用的四个件
- 匿名打分的数据模型——记录与分数分表,物理断开关联。适用于互评、满意度、360 考评
- 工作日窗口计算——所有"每月前 N 个工作日"类的流程都能用
- 排名算法口径——维度均分 → 汇总 → 并列处理;关键是对外公布和系统展示用同一套算法
- 部署与验证模式——容器化 + 统一入口 + 一键重建脚本 + 行为验证(不看文档,看接口返回什么)
结语
这套系统不大,服务的也就是身边这些同事。但我越来越觉得:真正难的不是写代码,而是把"管理的规则"翻译成"机器能无损执行的约束"——匿名写成数据模型、窗口写成日期计算、榜单规则写成展示过滤。
翻译对了,剩下的事机器自己会做完。