02.在你写下第一行红包代码之前,你脑子里一定会冒出来的那套流程
我带班的时候经常碰到一个场景:上一节课刚把需求讲完,学员回去一打开 IDE,手已经开始痒了。你让他别急,他也会点头,但下一秒还是会下意识去建 Controller,写个 /grab 接口出来。因为他脑子里已经有一套“我觉得就该这么写”的流程了。这篇我就专门讲这套流程。不是为了笑话谁,而是为了把大家拉到同一个起跑线。你只要承认自己确实会这么想,后面你看到翻车现场的时候,才不会觉得是我在编故事。我们先把场景压得很低,不搞微信那套大而全。就按最小可用版本来:有人发红包,有人抢红包,能查到结果。你只要盯住“抢红包”这一步,直觉流程通常是这样的:我先把红包查出来,看它还有没有剩;再看看这个人抢过没有;能抢就算一个金额;算完就扣掉剩余金额、剩余个数;最后把抢到的钱记一条记录。你看,这套描述听起来特别合理,甚至可以说很“工整”。问题就在于它太工整了,工整到你很容易忽略其中几个你以为不重要、实际上会要命的细节。先说清楚你脑子里默认了什么数据。你嘴上不说,但你写代码的时候一定会假设自己手里至少有两类东西。这里我建议你养成一个习惯:先别急着写方法,先把“系统里有什么东西是事实”列出来。你以后做任何业务,都会发现这一步才是基本功。第一类,是红包本身。哪怕你不叫它 RedPacket,你也会想办法把这些信息放进去:这是谁发的,总金额是多少,总共几份,现在还剩多少份,还剩多少钱,过期了没有。你可能还会顺手加个祝福语,再加个状态,方便前端展示。你看,你还没写代码,模型已经在你脑子里成型了。第二类,是抢红包记录。因为你想要“能查结果”。那就必然要记住谁抢了哪个红包、抢了多少钱、什么时候抢的。至于“手气最佳”这种东西,很多人第一版不会算,这很正常,先放着。到这里,很多学生会有一种踏实感:对象有了,字段也差不多齐了,我就差一个接口把它串起来了。发红包的时候你会想得很顺:生成一个红包 ID,把总金额和份数塞进去,然后把金额拆开。拆法先别纠结,平均也行,随机也行,总之先拆出来。拆出来之后放到一个“池子”里,等别人来抢。抢红包的时候,你的直觉流程更像一段伪代码。这里我希望你把它当成“你以后要守住的三条底线”,而不是当成某个接口的步骤。先拿到红包对象;如果不存在就失败;如果剩余份数是 0 就失败;如果这个 userId 在记录里出现过就失败;否则从金额池里拿出一份金额;把剩余份数减 1,把剩余金额减掉;保存抢红包记录;返回成功。这段流程,单线程跑,几乎永远不会出错。你自己点接口点十次、二十次,也看不出任何问题。你甚至会觉得:这不就是一个很普通的业务么,为什么别人老说红包难?真正的坑埋在一个你几乎不会主动写出来的前提里:你默认同一时间,只会有一个人进来。换句话说,你默认“读到的状态”在你动手写回之前不会变。注意,我说的不是“高并发”,不是春晚那种千万请求。哪怕就两个人,同时点了一下,也足够把这套直觉流程撕开一道口子。因为你的流程里有很多“先读,再判断,再写”的动作,而你默认在这三步之间,世界是静止的。你可以把它记成一句更硬的工程话:这套直觉流程里藏着一个时间窗口,窗口里发生的事情,你第一版代码是看不见的。后面我们所有的设计,几乎都是围绕这个窗口展开:要么把窗口变小,要么把窗口里的变化变成“可控失败”,要么干脆让窗口不存在。这就是为什么我一直强调:第一个版本不要急着追求写得漂亮,你要先把自己脑子里的这套直觉流程看清楚。看清楚之后,你下一篇再去写那段 Service 代码,会非常自然,而且你看一眼就会说一句:对,这就是我会写出来的那版。下一篇我就按你现在脑子里这套流程写一版“正常人第一版”的抢红包方法,不做任何优化,不加任何高级东西。你先看它能跑得多顺,再看它会在哪个瞬间炸掉。到那一刻,你就不会再纠结“要不要加锁”这种问题了,你会先开始问更关键的一句:我到底要保证哪几步不能被打断。