我遇到这个问题时,表面上只是一个按钮动作:媒体生成流程在页面上看见“继续生成”,就顺手替用户点了下去。页面上还有“剩余 3 张待生成”之类的提示,流程本来想做的也很简单——既然上一批还没有结束,就把后面的批次接着跑完。
但我重新看那段流程时,发现它只确认了“继续”这件事,却没有确认继续要付出什么。按钮文案看起来像一个普通的流程按钮,旁边却可能写着“将额外消耗 24 积分”。如果这一步被自动忽略,系统就会把一次可能产生用量的决定,伪装成一次没有成本的界面操作。
这件事让我重新意识到,自动化最危险的地方,往往不是它不会点击,而是它太容易把人的判断一起点击掉。凡是可能增加用量的自动动作,都必须先经过明确的人为花费门。
这次真正发生了什么
原流程处理的是生成后的确认对话。第一类对话是“今日内默认生成全部”,它有自己的既定行为;第二类对话是在还有剩余批次时出现的“剩余若干张待生成”,页面提供“继续生成”和“取消”两个选择。问题就出在第二类对话:流程看到继续按钮后直接点击,没有读取额外积分。
这不是一个偶发的点击位置错误。它的危险性来自判断链缺了一环。流程知道页面还有剩余内容,也知道用户曾经发起过生成,却没有知道下一次点击是否会消耗新的额度。前两个事实不能推出第三个事实。剩余数量说明“还有工作”,不说明“继续免费”。
如果不处理,后果也不只是多跑一次任务。一次付费继续可能消耗本不该自动使用的积分;更糟的是,用户事后只能看到额度减少,却很难还原是哪一次自动点击做出的决定。自动化本来是为了减少重复劳动,最后却把成本控制变成了事后追责。
按钮文案不是价格证明
最容易误判的地方,是把“继续生成”理解成了动作本身,把旁边的价格信息当成装饰。人在页面上操作时,可能会同时看到按钮、剩余张数和价格提示;程序如果只抓按钮,就等于主动丢掉了最关键的上下文。
同类的图片生成流程已经给出了更稳妥的做法:它会同时解析剩余张数和额外积分,再决定是否继续。这个对照让我确认,问题不在于页面无法提供价格,而在于媒体流程没有把价格纳入决策。
我一开始也可能会想到一个错误捷径:只要页面出现“继续生成”,就把它当成用户已经同意继续,默认点下去,把“默认继续”当成效率优化。这个办法在免费场景里看起来顺滑,但一旦页面把免费和付费都复用了同一个按钮,它就会把不同风险压成同一个动作。
“继续”是界面动词,不是费用结论;真正的费用结论必须来自可解析的价格信息。
所以我选择把判断对象从按钮改成对话状态。我需要先确认剩余数量,再确认额外积分;只有两项都能被读取,而且额外积分明确等于 0,才允许自动执行。这样做不是为了让流程变慢,而是为了让“免费继续”和“付费继续”在程序里成为两个不同的分支。
把一次点击拆成4步
这次修复最后沉淀成了4步:探测、解析、决策、回执。探测负责确认当前出现的确实是“剩余批次”的继续对话,而不是其他确认框。解析负责从提示中取出剩余张数和额外积分。决策负责执行唯一允许自动化的分支。回执负责把这次判断留下来。
解析时,剩余数量按“剩余 N 张待生成”的结构读取,额外积分按“将额外消耗 X 积分”的结构读取。这里的关键不是某个正则表达式长什么样,而是价格字段必须是决策输入,不能只做展示。字段缺失、格式变化或解析失败,都不能被当成 0。
决策分成3种状态。额外积分为 0,自动点击“继续生成”;额外积分大于 0,立即停止自动点击,报出真实价格,并留下人工决定的路标;额外积分无法解析,同样停止,不猜测、不兜底为免费。取消按钮则永远不自动点击,因为它代表的是人的停止意愿,不是系统可以代替的效率动作。
我判断未知状态也必须关闭,是因为未知本身已经说明信任边界没有满足。若把“没有读到价格”解释成“价格大概为 0”,那价格提示一旦改版、加载延迟或结构异常,流程就会悄悄退化成原来的 fail-open。在费用边界上,未知不是中性状态,而是需要人工接管的风险状态。
回执不是日志装饰
光有抛错还不够。自动化停止以后,接手的人需要知道它为什么停、停在什么页面状态、是否曾经点击过。于是回执里增加了继续对话事件,记录 remaining、extra_credits 和 clicked_at,也就是剩余数量、额外积分和点击时间。
这几个字段各自回答一个问题。remaining 说明当时还有多少生成任务,extra_credits 说明自动决策依据是什么,clicked_at 说明是否真的发生了点击以及发生在什么时候。若额外积分为正,事件会带着真实价格和人工决策路标进入错误信息;若价格无法解析,则明确提示价格不可读,而不是生成一个看似完整的假答案。
我更愿意把这看成给未来的自己留下证词。程序执行完以后,最容易消失的不是结果,而是当时为什么选择这个分支。没有回执,下一次排查只能重新猜页面、猜状态、猜用户是否被扣了额度;有了回执,问题就从“到底发生过什么”变成“已经知道发生过什么,接下来要怎么处理”。
这也解释了为什么回执字段要和确认对话结果放在同一条链上,而不是另写一份散落的调试信息。它不是为了让日志更丰富,而是为了让花费判断可以被复核。一个流程是否可靠,不看它在顺利时多快,而看它在需要停下时能不能把理由留下。
三态测试比一个成功例子更重要
这次没有只测试“额外积分为 0 时会点击”。那只能证明快乐路径仍然可用,不能证明钱的边界被守住。测试补了3种状态:0 积分时自动点击并记录回执;额外积分为正时抛出 fail-closed 错误且不点击;价格字段缺失或无法解析时同样抛错且不点击。
第2种状态还要检查错误里包含真实价格和剩余数量。因为“停止了”并不等于“帮助了人”,如果错误只说操作失败,人工仍然要重新打开页面确认到底要花多少。第3种状态则专门防止一种隐蔽回归:未来有人把价格解析写成可选字段,探测不到价格时继续沿用默认点击。
测试结果从改动前的 487 个通过增加到改动后的 490 个通过,新增的正是这3种花费状态。这个数字的意义不在于测试更多,而在于把原来藏在按钮动作里的判断分支显式化了。原有的4个媒体对话测试也保持通过,说明“今日内默认生成全部”的行为没有被这次门禁误伤。
如果只测成功例子,系统会给人一种虚假的安全感:免费时能点,似乎就代表整个流程正确。真正需要验证的是边界——有价格时能不能停,没价格时能不能停,停下以后能不能让人继续做决定。边界测试不是对正常流程的不信任,而是对自动化权限的精确定义。
方法可以迁移到所有自动动作
这件事表面上是媒体生成的积分门,实际上处理的是一个更普遍的问题:页面上的继续、重试、升级、批量执行,是否可能带来新的成本。以后遇到类似流程,我会先问4个问题:这个动作会不会增加用量?真实价格或资源变化在哪里?价格能否被明确解析?如果不能解析,谁来做下一步决定?
如果答案涉及费用、额度、库存或不可逆结果,就把自动化拆成探测、解析、决策和回执。探测确认对象,解析确认代价,决策只放行无成本分支,回执记录证据。任何字段缺失都进入人工路径,而不是进入默认路径。
这套方法也适用于非金钱场景。比如批量删除前要确认数量,重试请求前要确认是否会重复扣费,升级资源前要确认新增额度,自动发送前要确认收件范围。它们共同的结构是:一个看似普通的按钮,背后可能承载需要人承担的后果。
真正难的不是写出一个“只有 0 才点击”的条件,而是承认自动化并不拥有所有判断权。它可以替人识别明确安全的状态,却不应该替人把未知解释成安全。一个成熟流程的标志,不是它永远不停下来,而是它知道什么情况下必须把方向盘交还给人。
下次我会怎么做
下次再遇到“继续生成”或类似按钮,我会先找价格和资源变化,而不是先找点击坐标。确认页面形态后,同时读取剩余数量和额外成本;成本明确为 0,才允许自动继续;成本为正、缺失或无法解析,全部 fail-closed,并把人工决策路标和现场数据写入回执。
我还会在动手前先设计三态测试:免费、付费、未知。免费状态验证动作真的发生,付费和未知状态验证动作绝不发生。随后检查回执里是否有剩余数量、额外成本和时间信息,最后跑完整测试,确认既有免费流程没有被扩大门禁误伤。
这件事让我重新意识到,效率不是把每一个按钮都交给机器,而是把机器能够安全承担的部分说清楚。自动化真正应该替人省下的,是重复点击,不是替人承担未经确认的花费。