你的后端一清二楚用户错在哪里,页面上却只剩下四个字:输入有误

你的后端一清二楚用户错在哪里,页面上却只剩下四个字:输入有误
张文保 76 分钟阅读 3,106 阅读
本文目录
  1. 系统说不出用户错在哪,这句话为什么在技术上站不住?
  2. 判定一个输入不合格,前提是已经知道它错在哪
  3. 问题于是换了一个问法:它在哪一行被丢掉的
  4. 三类销毁点里,只有第一类是你能修的
  5. 这篇讲的不是控件怎么选,是控件拒绝你之后说什么
  6. 也不是弃单成因清单,只挑其中一个死角
  7. 跟老站那批必填校验的文章又是两码事
  8. 有三件事本文不打算展开
  9. 一句话总纲
  10. 那98% 的站点,是在哪一步把已经算好的话咽了回去?
  11. 九成八这个数字,说的不是能力而是习惯
  12. 同一个邮箱字段,三种完全不同的错法得到同一句话
  13. 五分钟解决一个简单错误,这个观察比百分比更吓人
  14. 它不普遍,但每一例都是满分伤害
  15. 销毁点通常就在那三行代码里
  16. 三处销毁点各有各的症状
  17. 先数一遍字段,别从最难的那个开始
  18. 为什么这件事不能靠一次性文案评审解决
  19. 预算挂错了科目,所以这件事永远排不上队
  20. 原创指标之一:报错分辨率
  21. 浏览器把九个理由摆在你面前,页面上为什么只剩一句?
  22. 规范给了九个理由位,实现里只读了那一个汇总位
  23. 九个位分别对应哪种真实错法
  24. 那个只能装一句话的槽位,才是真正的瓶颈
  25. 最容易被漏掉的那一位,专坑中文输入法
  26. 原生提示的语言跟浏览器走,不跟你的站走
  27. 三层校验必须共用一份规则来源
  28. 规则来源应该长成一张表,不是散落的判断语句
  29. 缺项要在构建期报错,不是运行期兜底
  30. 别把校验错误和业务拒绝混成一类
  31. 邮箱这个字段,凭什么由你那条正则说了算?
  32. 浏览器规范自己承认,它对邮箱的定义是故意违反标准的
  33. 标准里真正确定的,只有几个长度数字
  34. 你拒绝的,多半是你的正则不认识的合法地址
  35. 正确的说法不是无效,是我们这边不接受哪一部分
  36. 该规范化的东西不要拿去报错
  37. 规范化还是报错,有一条清晰的判据
  38. 拼写建议可以给,但不要替用户改
  39. 登录场景要单独例外
  40. 确认邮箱那个字段,能删就删
  41. 一套号码规则打天下,会挡掉哪几个市场的真实买家?
  42. 八成九的人不照着你给的格式示例填
  43. 掩码和报错各管一段,别互相替代
  44. 一套正则打天下,等于系统性拒绝几个国家
  45. 地址校验器是在做建议,不是在做审判
  46. 号码字段的正解是先问国家,再定规则
  47. 按市场配规则,不是按语言配规则
  48. 限制本身也该定期复查一次
  49. 字符集这件事,宁可放宽不要收紧
  50. 这一节能立刻做的三件事
  51. 支付被拒的那一句,有多少信息压根不在你手上?
  52. 这一类销毁点不在你的代码里
  53. 翻开拒付码表,一半以上写着未知原因
  54. 还有四条,是明文写着不许告诉用户的
  55. 于是拒付码天然分成三档
  56. 三档划进代码时有个容易忽略的细节
  57. 最后一行不是错误,别用错误的样式渲染它
  58. 不要把联系发卡行当成默认兜底
  59. 钱扣没扣,是用户此刻最关心的那件事
  60. 重试要有节制,否则你在替别人做测试
  61. 支付这段的度量该看什么
  62. 什么时候把话说清楚,反而是在帮倒忙?
  63. 无障碍标准自己写好了这条例外
  64. 那句除非,正好圈住了登录框
  65. 说清楚等于替撞库的人验证了一遍邮箱库
  66. 判据只有一句话
  67. 三个真实样本,含糊与清楚的差距
  68. 限速做到位,才有资格谈精确报错
  69. 创建密码和输入密码,是两个相反的场景
  70. 把体验损失挪到别处消化
  71. 这一节的一句话结论
  72. 从2025年6月28日起,同一件事在欧盟换了性质
  73. 那份研究发表一年半之后,规则变了
  74. 指令里对电商的要求,正好指向支付和身份那一段
  75. 可理解这个词不是形容词,它有技术定义
  76. 三秒钟消失的提示,不满足以文本说明
  77. 报错要跟字段有程序上的关联,不只是视觉上挨着
  78. 多个错误时,顶部要有一份汇总
  79. 移动端还有两个额外的检查项
  80. 微型企业有豁免,但多数独立站够不着
  81. 这一节的另一面:报错文案是搜索引擎看不到的内容
  82. 顺手把机器能引用的部分也补上
  83. 一次把拒付码全都翻译出来的改版,第七个月收到一封信
  84. 那个站和那个决定
  85. 头四个月的数据好得没话说
  86. 第七个月,问题不是以问题的形式出现的
  87. 你的精确报错,成了别人的验卡工具
  88. 代价落在了完全无辜的人身上
  89. 修的时候才发现,那张表压根没有这一列
  90. 第三层最值钱:那四十七条里有二十三条从来没触发过
  91. 改了三处
  92. 结果与那笔算不清的账
  93. 四周之内先动哪几处,才不至于变成又一个文案项目?
  94. 原创指标之二:报错重犯率
  95. 这个数怎么判读
  96. 日志要记子规则标识,不要记文案
  97. 报错时机:什么时候说,比说什么更影响感受
  98. 十八项上线自查
  99. 四周落地路径
  100. 三档投入怎么选
  101. 它跟整站体验治理是什么关系
  102. 三条反信号:出现这些说明方向偏了
  103. 回到那行代码
  104. 常见问题解答
  105. 后端到底知不知道用户具体错在哪,怎么自己验证一下?
  106. 报错文案改细之后,会不会给恶意用户提供便利?
  107. 没有开发资源,只能改文案的话,先改哪几句最划算?
  108. 做了输入掩码和格式示例,是不是就不用管报错了?
  109. 把报错做细,跟无障碍合规是同一件事吗?
  110. 多语言站怎么防止某个语言长期停在兜底文案上?
  111. 怎么向老板证明这件事值得排期?
  112. 权威参考资料

摘要:页面上那句“输入有误”,从来不是因为系统不知道用户错在哪。恰恰相反,能判定这个值不合格,前提就是已经算出了它违反哪一条;那条信息在某一行代码里被压成了一个真假值,然后被扔掉了。这篇文章不谈多写几句文案,谈的是去找那行代码:哪些信息是你自己丢的、哪些是上游压根没给你的、哪些是你必须故意说糊的。三类销毁点的修法完全不同,混在一起处理,就会出现一边给攻击者送情报、一边让真实买家卡在收银台前面五分钟的局面。

系统说不出用户错在哪,这句话为什么在技术上站不住?

能判定不合格,前提就是已经算出了哪里不合格。这份知情不是不存在,是在某一行代码里被压成了真假两个字。

判定一个输入不合格,前提是已经知道它错在哪

先把一件被说滥了的事翻过来看。

大部分关于表单文案的讨论,起手都是“用户看不懂报错”,然后落到“我们得多写几句话”。这个链条听上去没毛病,可它默认了一个前提:那条更具体的信息,眼下还不存在,需要有人去把它造出来。

这个前提是错的。

校验逻辑要判定某个值不合格,在逻辑上必须先确定它触碰了哪一条规则。少了一个字符和多了一个字符,是两条不同的判断;域名部分缺后缀和整个字符串没有分隔符,走的也不是同一个分支。系统若真的分不清这些,它连“不合格”这个结论都给不出来——它只会照单全收。

换句话说,能报错本身就是知情的证据。

所以“我们不知道用户错在哪”这句话,在技术上从来不成立。真正发生的事情是:那份知情在某个环节被主动降维了,从一棵分叉清楚的判断树,压成了一个只有真和假两种取值的返回值,剩下的枝干原地蒸发。

问题于是换了一个问法:它在哪一行被丢掉的

把前提纠正过来,整件事的性质跟着变了。

原来的问法是“要不要投入资源去生成更细的报错信息”,这是一个内容预算问题,排期时永远排在功能后面。新的问法是“这份已经生成好的信息,是在哪一行代码里被丢弃的”,这是一个接口契约问题,属于工程债,不属于文案任务。

两个问法通向完全不同的排期表。前者要立项、要写文案、要评审、要翻译成六种语言,谁看都像个季度级项目;后者往往只是把某个函数的返回类型从布尔值换成一个带原因的结构体,再让上层别在半路上把它吞掉。

保哥这些年看过的表单项目里,卡住的地方十有八九不在写字那一端。文案早就写好了,躺在表格里等着上线,卡住的是渲染那一层根本收不到区分它们所需要的那个字段。

三类销毁点里,只有第一类是你能修的

接下来这篇文章都在做同一件事:找销毁点,然后按类型分开处理。

第一类是你自己丢的。判断树在服务端跑完了,结果被压成一个通用异常抛出去,前端接住之后只能渲染一句放之四海而皆准的话。这类占绝大多数,也是唯一能靠改自己的代码彻底解决的。

第二类是上游根本没给你。最典型的是卡组织那条链路:一笔授权被拒,你拿到的可能只是一个含义为“因未知原因被拒绝”的代码,发卡行出于自己的风控考虑不愿意讲得更细。这一类修不了,只能改说法——把“我们不知道”翻译成一句对用户仍然有用的话。

第三类最反直觉:有些信息你手上有,但必须故意说糊。登录失败就是标准场景,把话说清楚等于替想撞库的人免费验证了一遍邮箱库。这一类不是技术问题,是取舍问题,而且业内早就有成文的判据。

三类混在一起处理,结果一定是拧巴的:该说清楚的地方含糊其辞,该含糊的地方倒是交代得清清楚楚。

这篇讲的不是控件怎么选,是控件拒绝你之后说什么

站内讲表单的文章已经有几篇了,边界得先划清楚,免得读到一半觉得眼熟。

讲下拉框在独立站表单里为什么悄悄吃掉订单的那篇,处理的是选型:这个字段该用下拉、单选还是直接让人打字。那是提交之前的事,目标是让错误尽量别发生。

本文处理的是它的下一秒:预防手段全部用尽之后,错误照样发生了,页面此刻该说什么。这两件事的读者甚至常常不是同一批人——前者归设计,后者的决定权其实在写接口的那个人手上。

再往前一层,那些看着无关紧要却真能提转化的界面细节那篇谈的是杠杆的分布,本文只钻其中一格,钻到底。

也不是弃单成因清单,只挑其中一个死角

另一条容易撞车的线是结账放弃。

结账页放弃率为什么超过七成那篇是横着铺的,九类成因各占一段,谁都不深挖,用途是诊断时按图索骥。校验报错在那张图里只是其中一格。

本文把那一格单独拎出来竖着挖。为什么值得单独挖:因为这一格的严重度分布跟其他格子不一样。运费太贵、要求注册、支付方式不全,这些都是让人权衡之后决定不买;而看不懂报错是另一回事——用户想买,钱也准备好了,他只是被一句话挡在门外,出不去也进不来。

跟老站那批必填校验的文章又是两码事

站内还有一批年头更久的内容,讲的是自定义表单怎么做必填校验的三层防护,那批文章的主题是怎么拦住不合格的提交,属于安全和数据完整性。

拦住是前置条件,本文默认它已经做好了。真正的战场在拦住之后:同一个被拦下来的提交,可以让用户三秒钟改对,也可以让他在原地绕五分钟然后关掉页面。两种结果之间的差别,全在那一行返回值里带没带上原因。

有三件事本文不打算展开

为了不把篇幅摊薄,先声明三条边界。

一是服务端异常页。数据库连不上、网关超时、五百错误,那属于故障处理,跟“用户输入不符合规则”是两个体系,站内讲自定义错误页与状态码的那篇更对口。

二是机器人对抗。验证码、蜜罐、频率限制会影响报错的说法,本文只在涉及披露判据时点到为止。三是具体框架的写法,各家技术栈差得太远,这里只讲不依赖框架的那部分:规则来源、返回结构、映射表和度量。

一句话总纲

如果全文只记一句,那就记这句:一个系统能说出“这不合格”,就必然已经算出了“哪里不合格”;页面上没有出现的那部分,不是没生成,是被谁在半路上删掉了,而你的工作是找到那个人——很多时候他就在提交历史里,还留着你自己的名字。

那98% 的站点,是在哪一步把已经算好的话咽了回去?

三个高频销毁位置,各有各的症状。认准症状能省掉大半排查时间,其中最好修的那处离用户只隔着一层渲染代码。

九成八这个数字,说的不是能力而是习惯

Baymard Institute在结账体验的大规模基准里量过这件事:受测样本中约98% 的站点使用通用报错文案,只有2% 会针对触发校验的那条具体子规则给出定制说明。这个比例高得有点离谱,离谱到不可能是能力问题——能做出国际化结账、能接三种本地支付、能算跨境税的团队,写不出第二句报错文案是说不通的。

所以它是习惯问题,而且是一个在架构层面被固化下来的习惯。

Baymard那篇文章里有一段描述值得原样搬过来:后端的校验逻辑本来就掌握着用户输入哪里出了问题,否则它无从判定这个输入无效。这句话跟本文开头那个论证是同一件事,只是他们说得更克制。

同一个邮箱字段,三种完全不同的错法得到同一句话

把这件事落到一个具体字段上,荒诞感立刻就出来了。

假设用户在邮箱框里分别打了三个东西:一个缺了域名后缀,一个把分隔符打成了别的符号,一个域名部分少了那个点。这是三条不同的规则被触碰,判断分支各走各的。

而页面上出现的是同一句:这个邮箱地址无效。

更要命的是,这三种情况用户要做的动作完全不同。第一种需要在末尾补三个字符,第二种需要把中间那个符号换掉,第三种需要在域名中间插一个点。一句“无效”把这三条互不相干的指令合并成了一句“你自己找找看”。

找得到的人当然多数,可总有一批人找不到。他们盯着那串字符看了半天,觉得完全正确,因为在他们的认知里那就是自己的邮箱。

五分钟解决一个简单错误,这个观察比百分比更吓人

Baymard的测试记录里有个数字比98% 更值得贴在墙上:在登录这类简单任务里,他们观察到有受试者仅仅因为报错措辞含糊,花了长达五分钟才把问题解决

五分钟是什么概念。整个结账流程做得好的话也就两三分钟,一句话让人在原地转了比全流程还长的时间。

而这还是解决了的那批人。Baymard记录的更严重的一类是完全卡死:受试者判断不出问题出在哪,尝试了几次之后放弃,转去别的站。有位在英国站测试的受试者对着手机号字段的报错自言自语,猜测是不是要加国际区号,最后得出结论说这站显然坏了,我换一家——而真实原因只是那个字段不接受空格。

它不普遍,但每一例都是满分伤害

这里有个容易被数据掩盖的结构。

被含糊报错卡住的用户占比不高,多数人磕磕绊绊还是过去了。所以在整体转化率的曲线上,这件事几乎看不出来,任何一次改版的噪音都比它大。

可是落到个体身上,它的严重度是满格的。一个看不懂报错、又改不对的用户,在技术意义上被锁在了订单之外——他不是权衡之后决定不买,他是想买而系统不让。这两种流失在报表里长得一模一样,在业务性质上差着十万八千里。

做优化的人对这类分布要格外小心:发生率低而单例严重度满分的问题,永远不会在总量指标上主动举手,它只会在客服工单和退款申请里露出一点边角。

销毁点通常就在那三行代码里

说了半天抽象的,来点具体的。信息到底在哪儿丢的,实际排查时有三个高频位置。

第一处是服务端的校验函数。判断树跑完,最后一行写着返回假,或者抛出一个只带字段名不带原因的异常。原因在函数内部是活的,出了函数就没了。

第二处是接口的响应结构。校验函数其实返回了原因码,可接口层为了统一响应格式,把它塞进一个笼统的消息字段,或者干脆映射成一个宽泛的错误类型。这一处最隐蔽,因为两端的工程师都觉得自己没丢东西。

第三处是前端的渲染分支。响应里带着原因码,可组件只判断了有没有错,没有按码分流,于是全都走进同一个兜底文案。这一处最好修,改的是几十行渲染代码,不动后端。

排查顺序建议倒着来:先看前端拿到了什么,再看接口传出了什么,最后才去看校验函数算出了什么。多数团队第一天就会发现,原因其实一路传到了浏览器,只是没人把它显示出来。

三处销毁点各有各的症状

三个位置的表现不一样,认准症状能省掉大半排查时间。

销毁位置典型症状怎么确认修复成本
服务端校验函数同一字段所有错法都返回同一个错误类型读校验代码,数它内部有几个分支高,要改函数签名与调用方
接口响应结构日志里有细分原因,接口文档里只有一个消息字段对照服务端日志与实际响应体中,加字段并向后兼容
前端渲染分支响应体里有原因码,页面上只有一句兜底话浏览器开发者工具看那次请求的返回低,只动组件
语言包缺项只有一种语言分得清,其他语言全走兜底切到德语法语再触发同一个错误低,但容易长期没人发现

最后一行是跨境站特有的。国际化流程里,新增文案键往往先上英文,其他语言等下一轮翻译批次;而兜底逻辑通常写成“找不到就用通用消息”,于是德语用户看到的永远是最含糊的那句。这个坑不出现在任何一次中文或英文的回归测试里。

先数一遍字段,别从最难的那个开始

知道该改什么之后,还有个顺序问题。

直觉会让人先去攻最复杂的那个字段——通常是地址或者支付信息,因为那儿的规则最多、报错最含糊、看起来收益最大。可复杂字段的规则往往缠着下游系统,动一处牵三处,第一个迭代就卡在那儿的团队非常多。

更稳的顺序是先按触发量排序,从前三名里挑规则最简单的那个开刀。它大概率是邮箱或者手机号,改造范围小,一周内能上线,触发量大又意味着效果两周就能在数据上看出来。拿到第一个可见结果之后,再去谈那些需要跨团队协调的字段,说服成本会低很多。

为什么这件事不能靠一次性文案评审解决

还有个更长期的麻烦。

就算你今天把所有报错文案都写细了,明天有人给某个字段加了一条新规则,那条规则大概率不会带着自己的文案一起上线。它会复用现有的错误类型,于是新规则从第一天起就没有对应的说法。

半年之后,你的报错分辨率又会退回到接近改版之前的水平,而且没有任何一次代码评审会拦住这个过程,因为每一次单独看都很合理。

要挡住它,得让规则和文案在结构上绑在一起,缺一个就构建不过去。这一点后面讲落地时会展开,它是整套方案里唯一带强制力的环节。

预算挂错了科目,所以这件事永远排不上队

还有一个非技术的原因,值得单独说。

“报错文案”这四个字里带着“文案”,于是它天然被归进内容或者设计的活儿。而内容和设计的排期里,永远有更显眼的东西排在前面:新落地页、大促视觉、品牌升级。一句报错文案在这个队列里是排不到的。

可实际要动的地方在后端接口。如果按“接口返回值丢失了业务信息”来立项,它进的是另一个队列,那个队列里跟它竞争的是别的技术债,量级和优先级都对得上。

保哥的经验是,同一件事换个科目提,通过率能差出一倍。这不是耍花招,是它本来就应该待在那个科目里。

原创指标之一:报错分辨率

要把这件事变成可以管理的东西,得先有个数。这里给第一个:报错分辨率

定义是一个比值:某个字段上,后端能够区分的失败子类数量,除以前端实际展示出来的不同文案数量

比如邮箱字段后端能分出六种失败情形,前端只有两句话(一句为空、一句无效),那这个字段的报错分辨率就是三。这个数字直接量化了信息在传递过程中被压缩了多少倍。

三个好处。一是分子分母都能在半天内数出来,分子去读校验代码,分母去读语言包,不需要新埋点;二是它天然按字段拆,能立刻排出优先级;三是它有明确的目标值,就是一。

怎么判读:大于三的字段说明信息压缩得太狠,用户拿到的提示基本等于没提示;等于一但用户仍然频繁改不对,说明问题不在分辨率而在措辞或者时机,该换另一套工具;分母比分子还大是个危险信号,意味着有人在前端凭空造了后端根本区分不了的说法,那些话大概率是错的。

最后这种情况保哥真见过:前端为了体验好,猜了几种常见错误,自己判断之后给出不同提示,而后端的实际规则跟它对不上。用户照着前端的提示改完,提交上去照样被拒。

浏览器把九个理由摆在你面前,页面上为什么只剩一句?

规范早就把不合格拆成了九种互相独立的情形,还留了一个自定义位。可它同时只给一个消息槽,默认路径就是丢信息那条。

规范给了九个理由位,实现里只读了那一个汇总位

这件事最漂亮的证据不在任何一份体验报告里,在浏览器的规范文本里。

HTML标准定义了一套约束校验机制,每个表单控件都挂着一个校验状态对象。翻开规范里那份状态清单会发现,它把“这个值为什么不合格”拆成了九种互相独立的情形:缺值、类型不匹配、模式不匹配、太长、太短、低于下限、高于上限、步长不匹配、输入本身不完整,另外还有一个留给开发者自定义的错误位。

也就是说,规范的作者早在二十年前就想明白了:无效不是一个状态,是九个。他们把这九个理由分成九个独立的布尔位,一个不落地交到你手上。

然后绝大多数实现只读了另外一个属性——那个把九位全部或起来的汇总位。

这就是销毁点的教科书级样本,而且它离用户只有一层。信息不是不够细,是细到规范层面都准备好了,被一行“如果无效就提示无效”给抹平了。

九个位分别对应哪种真实错法

把这九位翻译成日常语言,能直接当映射表的骨架用。

校验状态位用户实际干了什么措辞该往哪个方向走
缺值必填框留空了说清楚这一项为什么必须要,而不是只喊必填
类型不匹配邮箱或网址的整体形态不对指出缺的是哪一部分,别说无效
模式不匹配不符合你自定的格式约束给一个当地格式的正确样例
太长超过了长度上限报出上限数字与当前长度
太短不足长度下限报出还差几位,别只说太短
低于下限数量或金额小于最小值说明最小值,并解释它从哪来
高于上限超过最大值说明是库存限制还是单笔限购
步长不匹配只能按箱按套买却填了零头给出最接近的两个合法值
输入不完整字符没能被解析成这个类型说明只接受哪一类字符

这张表的用法不是照抄,是当核对清单:拿你自己的某个关键字段过一遍,看看九行里有几行在你的产品里是有独立说法的。多数字段的答案是一到两行。

那个只能装一句话的槽位,才是真正的瓶颈

规范里还有一处设计,把这个矛盾摆得更明显。

开发者可以给控件设置一条自定义校验消息。可这个接口只接受一个字符串,一个控件同一时刻只能挂一句话。

于是局面变成这样:规范一边给你九个理由位,一边只给一个消息槽。想让消息跟着理由变,你就必须自己在中间架一张映射表,读九个位里哪个为真,再决定往那个槽里塞哪句话。

这件事说明一个常被误解的点:做不到自适应报错,不是因为团队偷懒,是因为从平台层开始,默认路径就是丢信息的那一条。要走另一条路,得自己多写一层。多数人不会主动多写一层,除非有人告诉他们那层能捡回什么。

最容易被漏掉的那一位,专坑中文输入法

九个位里有一个特别值得单独说,就是“输入本身不完整”那一位。

它描述的情形是:用户敲进去的东西,浏览器根本没能把它解析成这个类型的合法值。数字框里出现了字母、日期框里是半截日期,都算。

它跟其他八位有个本质区别:其他八位说的是“值是合法的,但不满足你的约束”,这一位说的是“压根没得到一个值”。所以这时候不能提示“请填写正确的数量”,因为在系统看来那个框根本是空的——用户明明看见自己打了字。

跨境站还有一层麻烦。中文和日文输入法的候选状态、全角数字、以及一些输入法在未上屏时的中间态,都可能落到这一位上。用户看着自己框里的字,系统说没收到,双方都觉得对方在胡说。

这一位单独给一句话是划算的,措辞方向是“这里只接受半角数字,你输入的字符没能被识别”,而不是笼统的格式错误。

原生提示的语言跟浏览器走,不跟你的站走

还有个跨境站必踩的坑。

如果完全不管,直接让浏览器弹出内置提示,那句话的语言由浏览器界面语言决定,不由页面语言决定。一个在德国用英文系统的用户,打开你的德语站,会看到德语的商品、德语的按钮,和一句英文的报错。

更糟的是这句话的措辞你完全控制不了,而它出现的位置往往是整个结账流程最紧张的那一刻。

所以但凡是正经做多市场的站,原生提示都应该被接管,改成自己渲染。接管之后语言跟着页面走,措辞跟着你的映射表走,样式也能跟表单的其他部分统一。这属于开工第一天就该做掉的事,成本极低。

三层校验必须共用一份规则来源

校验通常有三层:浏览器原生属性、前端脚本、服务端。三层各写各的,是另一类信息事故的源头。

常见的错法是这样:原生属性里写了一个最大长度,前端脚本又写了一遍正则,服务端还有一套自己的规则。三处由三个人在三个季度写下,谁都没打算跟另外两处保持一致。

结果分两种,都难看。前端比后端松,用户在页面上一路绿灯,提交之后被打回来,而这时候他已经填完了整张表;前端比后端严,页面拦下了一个后端其实能接受的值,你损失了一个本来能成的订单,而且这个损失永远不会出现在任何报表上——它根本没产生记录。

正确的做法是让规则只有一个来源,另外两层是它的产物。可以是一份声明式的规则描述,构建时生成前端校验代码,运行时被服务端读取。改一处,三层同时变。

规则来源应该长成一张表,不是散落的判断语句

把规则集中之后,它的形态也有讲究。

如果集中之后仍然是一堆判断语句,只是搬到了同一个文件里,那映射表还是建不起来——你没法从一段代码里自动列出“这个字段一共有几种失败方式”。

更好的形态是一张结构化的表:每一行是一条子规则,带着自己的标识、适用字段、适用市场、以及对应的文案键。校验引擎读这张表执行,文档从这张表生成,报错分辨率的分子也从这张表直接数出来。

这张表最好能被非工程角色读懂。真到了要改措辞的时候,需要动的是文案键指向的内容,不是规则本身,两边的责任就此分开。

缺项要在构建期报错,不是运行期兜底

前一节留了个尾巴:怎么防止新规则上线时不带文案。

答案是把它变成构建失败。规则表里新增一行,如果它的文案键在语言包里找不到对应内容,构建就不通过。这是整套方案里唯一带牙齿的地方,也是唯一能防止半年后打回原形的地方。

多语言站可以放宽一档:主语言缺项直接失败,其他语言缺项发警告并记入待翻译队列,队列超过约定条数才升级为失败。这样既不卡住发布,也不会让某个语言长期停在兜底文案上。

保哥见过一个团队把这条规则加上之后,第一次构建就红了四十多处。那四十多处不是新增的,是历史上一直缺着,只不过运行期的兜底逻辑替它们遮了两年。

别把校验错误和业务拒绝混成一类

最后划一条容易混的界。

“这个邮编格式不对”和“这个地址我们不配送”,在用户眼里都是提交失败,在系统里完全是两回事。前者是格式约束,用户改一下就能过;后者是业务规则,用户改多少遍都不会过。

把两者塞进同一套错误通道,会造出一种最伤人的体验:用户以为自己填错了,反复检查、反复重填,其实你从一开始就不打算卖给他。

业务拒绝需要的是另一套说法——说清楚不是他的问题,并给出替代路径。这条边界在配送范围、支付方式可用性、以及库存分配上最常出问题。

邮箱这个字段,凭什么由你那条正则说了算?

制定网页标准的人公开承认,自己对邮箱的定义是故意违反邮件标准的。连他们都在打架,你那句无效底气从哪来。

浏览器规范自己承认,它对邮箱的定义是故意违反标准的

邮箱大概是全世界被写过最多次正则的字段,也是最不该由你自己写正则的字段。

HTML标准在定义邮箱输入类型时,给出了一段语法,然后紧接着加了一句极其罕见的自白:这条要求是对RFC 5322的一次故意违反。理由写得很直白——那份定义在分隔符之前太严格、在分隔符之后太模糊、整体上又太宽松,宽松到允许注释、空白字符和引号包裹的字符串这些绝大多数人闻所未闻的写法,因此在实际场景里没法用。

请注意这句话的分量。制定网页标准的那群人,公开声明自己在这个点上不打算遵守邮件标准,并且把理由摆了出来。

这意味着什么?意味着“这个邮箱地址无效”这句话,在标准层面根本没有唯一解。你判定的无效,是对着某一份具体定义的无效,而不同定义之间彼此打架,连规范制定者自己都在打架。

标准里真正确定的,只有几个长度数字

那么有没有确凿无疑的部分?有,而且少得可怜。

SMTP那份标准在长度限制一节里给了几个硬数字:本地部分最长64个字节、域名部分最长255个字节、整条路径最长256个字节。这几个数是明确的、可执行的、跨实现一致的。

剩下的东西——哪些字符能出现在分隔符前面、能不能有加号、能不能有连续的点——要么各家实现不一,要么标准写得比现实宽得多。

所以一个务实的校验策略是:硬拦长度和结构,软提示形态。超过长度上限直接拒绝,因为这是确凿的;缺分隔符直接拒绝,因为不可能对;至于本地部分出现了某个不常见字符,最多提醒一句,别拦。

你拒绝的,多半是你的正则不认识的合法地址

这条推论有点扎心,但确实是常态。

一条自己写的邮箱正则,通常是照着团队里所有人的邮箱形态归纳出来的。归纳的样本量大概是二十个,全部来自同一个国家、以及三四家主流邮箱服务商。

然后它上线了,去面对全世界的邮箱地址:带加号别名的、用了新顶级域的、域名里带连字符的、本地部分带点的、企业自建域名只有两个字母的。每一类都合法,每一类都可能被你那二十个样本归纳出来的规则判死。

这批用户的特点是:他们完全不知道自己被拒绝了什么。他们的邮箱在别处一直好用,在你这儿突然无效,第一反应不是自己填错了,而是这站有问题。而你这边不会有任何记录,因为订单从来没产生。

拿加号别名举个具体的:不少技术背景的用户习惯用它给不同网站分配不同的收件标记。这类人恰好又是高客单价品类里占比不低的一群。拦掉他们等于精准地筛掉了一批优质客户,这买卖怎么算都亏。

正确的说法不是无效,是我们这边不接受哪一部分

既然标准本身模糊,报错的措辞就得跟着换个姿态。

“这个邮箱地址无效”是一个关于世界的断言,而你没有资格下这个断言。“我们这边暂时不接受加号”是一个关于自己的陈述,它准确、诚实,而且用户立刻知道该怎么办。

这个区别不只是礼貌问题。前一种说法把用户推进死胡同,他会反复检查一个其实完全正确的地址;后一种说法给了他一个明确动作,换个地址或者去掉那部分。

更进一步的写法是把限制的来源也带上一句。“我们的邮件系统暂不支持这类地址”比“不接受”更能让人接受,因为它把矛头指向了系统的局限,而不是用户的错误。

该规范化的东西不要拿去报错

还有一大类报错,根本就不该存在。

Baymard在测试英国某家电站点时记录过一位受试者,对着手机号字段的报错猜了半天,怀疑是不是要加国际区号,最后判定这个站坏了。真实原因是那个字段不接受空格

一个空格。用户在写自己手机号时习惯性分组,系统因此拒绝了他,并且拒绝时什么也没说。

这类问题的正解不是把报错写得更清楚,是把这条报错删掉。空格、连字符、括号、大小写、首尾空白,这些在入库之前统一清洗掉就行了,一行代码的事,没有任何理由让用户来配合机器。

规范化还是报错,有一条清晰的判据

怎么区分哪些该清洗、哪些该报错?判据只有一句:这个差异会不会改变这个值的语义

用户输入的差异该怎么处理理由
号码里的空格、连字符、括号清洗去掉之后是同一个号码
邮箱大小写清洗为小写后比对域名部分不区分大小写
首尾空白与不可见字符清洗粘贴带进来的,用户看不见
全角数字与符号清洗输入法造成的,语义不变
邮编的字母大小写与内部空格按市场规则清洗各国官方写法本来就不统一
邮箱缺分隔符报错结构不完整,无法推断意图
号码位数不对报错无法确定用户想填哪一个
金额或数量超出范围报错业务约束,不能替用户改

这张表能砍掉的报错量往往超出预期。保哥做过一次统计,某个站前五大高频校验错误里有三个属于表格上半部分,也就是三个本不该存在的错误占了当月报错总量的六成。

拼写建议可以给,但不要替用户改

还有一种介于两者之间的情况:常见域名拼写错误。

几个主流邮箱服务商的域名被打错的方式高度集中,字母顺序颠倒、少一个字母、后缀打成别的。检出这类情况的成本很低,做一张常见错拼对照表就够了。

问题在于检出之后怎么办。这里保哥的立场很明确:提示,不要自动替换

理由是这类判断永远有误判率,而误判的代价是把订单确认信发到一个错误的地址,用户完全不知情。提示的写法可以是在字段下方给一句温和的询问,附一个可点击的建议值,点了才生效。

这个设计还有个附带好处:用户点击建议值的比例,本身就是一个很干净的数据,能告诉你这张对照表的准确度。

登录场景要单独例外

邮箱字段有一个必须例外的地方:登录框。

注册和结账时的邮箱字段,说得越清楚越好;登录时的邮箱字段,恰恰相反。这中间的取舍逻辑后面有一整节专门讲,此处只留一句提醒:这两个场景经常复用同一个输入组件,而组件默认是不区分上下文的。

一次很典型的事故就是这么来的:团队把邮箱校验做细了,做得很好,然后登录页顺带升级了,因为它们用的是同一个组件。

确认邮箱那个字段,能删就删

顺手说一个相关的东西:让用户把邮箱输两遍的设计。

它的初衷是防打错,实际效果是让人复制粘贴第一个框的内容——那样连打错都会被完整复制一份,防了个寂寞,还多制造了一处可能不匹配的报错。

更划算的替代是:单框加上前面说的拼写建议,再把确认信里的地址显示出来让用户核对。用户体验上少一个字段,数据质量上没有损失。

如果非留不可,至少别禁用粘贴。禁用粘贴的表单是这个行业里最没道理的传统之一,它假设用户比系统更容易出错,而事实往往相反。

一套号码规则打天下,会挡掉哪几个市场的真实买家?

给了格式示例,八成九的人照样按自己的习惯填。预防这条路的天花板就在那儿,剩下的必须靠报错那句话接住。

八成九的人不照着你给的格式示例填

在讨论报错之前,先把预防这条路的天花板量出来,否则很容易得出“做好引导就不用管报错了”的错误结论。

Baymard关于输入掩码的研究里有一个数字,比98% 那个更有指导意义:约89% 的受试者,在填写受限字段时没有按照站点给出的格式示例来输入。注意这是给了示例的情况——示例就摆在字段旁边,白纸黑字,八成九的人照样按自己的习惯填。

同一份研究还指出,约98% 的电商站点对某些字段设有输入限制,而约64% 的站点没有使用输入掩码、或者用错了

把这三个数摆在一起,结论很硬:限制普遍存在,引导普遍失效,那么报错就是必经之路,不是可以绕开的边缘情况。任何把资源全押在预防上的方案,都会在这89% 面前撞墙。

掩码和报错各管一段,别互相替代

输入掩码是好东西,但它管的是另一段。

手段作用时刻它能解决的它解决不了的
格式示例输入之前让愿意看的人少犯错八成多的人根本不看
输入掩码输入过程中把分隔符自动补上,形态强制统一位数不对、号段不存在
即时校验离开字段时在用户还记得刚填了什么时提醒需要跨字段才能判定的规则
提交校验点提交之后跨字段规则、服务端规则此时用户已经在等结果,容错心最低

四段是接力关系,不是替代关系。掩码做得好,能把报错量压下去一大截;可压下去之后剩的那批,恰恰是掩码搞不定的硬骨头,措辞必须更准。

顺带说个反例:卡号字段的空格。Baymard的基准显示,仍有约15% 的站点不会自动格式化卡号里的空格——这个比例已经比2019年的一半好太多了,可它的存在本身很说明问题:用户手上那张实体卡就是分组印的,照着念、照着打,然后被系统拒绝。

一套正则打天下,等于系统性拒绝几个国家

号码和地址这两类字段,是跨境站最容易埋雷的地方。

邮编就是典型。有的市场是纯数字定长,有的带字母,有的中间有空格,有的字母数字交替。用一套“必须是若干位数字”的规则去校验,等于把好几个国家的合法邮编整体判死。

更隐蔽的是那种半对半错的规则:允许了字母,但没允许中间的空格;或者允许了空格,但长度上限按不含空格算。这类规则平时看不出问题,只在某几个市场的某几种写法上炸。

站内那篇讲跨境发货时邮编该怎么填的内容里就能看到,光是一个市场内部的写法变体就够写一篇了,何况几十个市场并行。

地址校验器是在做建议,不是在做审判

Baymard另一份研究给了个数:约47% 的站点没有提供地址校验器。他们在某家服饰站的测试里记录到,一百五十多笔订单中约9% 含有收货地址的笔误或拼写错误——门牌号写错、街道或城市拼错。

这批订单不会在结账时报错,它们会一路通过,然后在配送环节变成投递失败、变成客服工单、变成一次逆向物流

地址校验器的价值在这里,但它的用法有讲究:它给的是建议,不是判决。校验库覆盖不全是常态,尤其是新建小区、乡村地址、以及刚改过名的街道。硬拦的后果是把真实存在的地址拒之门外,而这类用户往往投诉无门。

正确形态是弹出一个对照:你填的是这个,我们查到的是那个,你选一个。默认选中查到的那个,但保留用户坚持原写法的权利。

号码字段的正解是先问国家,再定规则

手机号的处理顺序常被搞反。

很多表单是先让用户填号码,提交时才根据收货国家去校验。这个顺序保证了报错必然发生在最晚的时刻。

更好的顺序是国家在前:用户选定国家或地区之后,号码字段的掩码、位数规则、以及前缀提示同时切换。用户从第一个字符开始就在正确的轨道上。

存储上建议统一存成带国家码的国际格式,展示时再按当地习惯格式化。这样做的好处不只在校验:短信通道、物流商接口、客服系统,全都吃这一套,省掉一堆转换。

还有一条小规矩:号码字段的位数报错要报出差几位。说“号码太短”几乎没用,说“还差两位”,用户立刻知道自己漏了什么。

按市场配规则,不是按语言配规则

这是个反复出现的错误,值得单独拎出来。

不少站的校验规则挂在语言上:切到德语就用德国的规则。可德语区不止一个国家,而在德国用英文界面购物的人也不少。语言和市场是两个维度,混成一个必然出错。

规则应该挂在收货国家上,因为格式约束来自那个国家的邮政和电信体系,跟界面语言毫无关系。文案的语言才挂在界面语言上。

这个区分在做多市场支付方式配置时也是同一个道理:可用的支付方式由市场决定,展示语言由用户决定,两条线各走各的。

限制本身也该定期复查一次

还有个更根本的问题:那些限制是谁定的,还成立吗。

表单上的很多约束是历史遗留:某个下游系统当年只接受特定长度,某个物流商接口不吃某些字符,某个数据库字段当初开小了。这些限制被写进校验规则,然后下游系统换了三轮,规则原封不动地留着。

建议每年做一次限制盘点,逐条问:这条限制现在还有下游依赖吗?如果没有,删掉它比给它写文案划算得多。

保哥经手过一次盘点,删掉了十几条规则,其中一条是不允许姓名字段出现连字符——这条规则挡了整整两年的复姓用户,来源是某个早已下线的旧系统。

字符集这件事,宁可放宽不要收紧

姓名和地址字段的字符集,是另一个高频雷区。

只允许拉丁字母的规则,会挡掉带变音符号的德语法语姓名;只允许英文字母加数字的规则,还会顺手挡掉一批地址里的常见符号。这些用户在自己的国家是绝对多数,在你的表单里成了非法输入。

正确姿势是默认放宽,只在确有下游限制时才收紧,并且收紧时明确说明限制来自哪里。如果某个物流商确实只吃拉丁字符,那句报错应该说清楚这是转写要求,而不是说用户的名字无效。

没有什么比一个系统告诉用户“你的名字不合法”更能瞬间消耗掉品牌好感了。

这一节能立刻做的三件事

如果这一节只挑三件事今天就做,保哥的排序是这样。

第一,把号码和邮编字段的清洗逻辑补上,空格连字符括号大小写全部规范化,然后统计一下这一改砍掉了多少报错。

第二,把校验规则从语言维度挪到收货国家维度,挪的过程中你会顺手发现几个从来没配过规则的市场。

第三,把地址校验从硬拦改成建议对照,并记录用户接受建议的比例。这个比例低于一半,说明你的校验库该换了。

支付被拒的那一句,有多少信息压根不在你手上?

拒付码表里有十二条写着未知原因,还有四条明文要求不许告诉用户。这两批之外剩下的,恰好全是用户几秒能修好的。

这一类销毁点不在你的代码里

前面几节讲的都是第一类销毁点,理论上全能修。到了支付这一段,情况变了。

一笔卡授权被拒时,你的服务端确实收到了一个代码。可那个代码是发卡行给的,发卡行给多少,你就只有多少。它不肯说的部分,你再怎么改自己的代码也变不出来。

这是本文的第二类销毁点:信息在进入你的系统之前就已经被丢掉了,丢的人不是你。处理它的方式跟第一类完全不同——第一类是把话捡回来,第二类是在明知说不清的前提下,仍然给用户一个可执行的下一步。

翻开拒付码表,一半以上写着未知原因

把支付服务商公开的拒付码表拉出来数一遍,这件事会变得非常直观。

以Stripe公开的那份卡拒付码文档为例,整张表里有十二条码的描述是同一句话:这张卡因未知原因被拒绝。它们的代码名字看起来各不相同——不予承兑、联系发卡行、未采取操作、安全违规、交易不被允许、授权撤销——可后面跟着的解释全都一样,建议动作也全都一样:让持卡人联系发卡行。

Stripe自己在文档开头说明,他们的拒付码是在发卡行代码的基础上做了扩展,试图给出更细的原因。也就是说,这已经是加工过、尽力细化过的版本,剩下这十二条是真的挖不动了。

为什么发卡行不肯说?因为其中相当一部分与风控判断有关,说清楚等于把风控规则外泄。这是他们的合理利益,不是敷衍。

还有四条,是明文写着不许告诉用户的

同一份文档里更值得注意的是另外一批码。

关于欺诈嫌疑、卡片挂失、卡片被盗、以及命中商户自己的拦截名单这四类,文档给出的建议动作不是“告诉用户原因”,而是明确要求不要向持卡人透露更详细的信息,改为按通用拒绝的方式呈现

这一条写得非常直白,属于行业内的成文规矩。理由不难理解:如果一个人拿着一张挂失卡在试,页面告诉他这张卡已挂失,他立刻知道该换下一张;而真正的持卡人此刻并不在场,你说得再清楚也帮不到他。

所以这四条码构成了本文的第三类销毁点的第一个实例:信息你有,但必须故意说糊。它跟第二类的区别在于责任方——第二类是别人不给你,第三类是你自己决定不给。

于是拒付码天然分成三档

把这两批码去掉,剩下的那批恰好是最有价值的那批。

披露级别典型情形页面上该说什么用户能做什么
可原样说明卡片过期、安全码不符、账单邮编不符、卡号有误指出具体是哪一项对不上当场就能改对
可原样说明余额或额度不足、超出单笔限额、币种不支持说明是额度问题,建议换支付方式换卡或换方式
只能归类说明发卡行未给出原因的那十二条说明是发卡行拒绝,非订单问题换卡,或联系发卡行
必须模糊处理欺诈嫌疑、挂失、被盗、命中拦截名单与通用拒绝完全一致的措辞换支付方式
不是错误需要强客户认证、需要额外验证引导进入验证流程完成验证后继续

这张表最值得注意的是第一档和第二档:能原样说清楚的那批,恰好全是用户自己就能修好的。卡过期换张卡,安全码打错重打一遍,账单邮编不符改成账单地址的邮编——每一条都是几秒钟的动作。

把这几条从通用兜底文案里解放出来,是整个支付环节里投入产出比最高的一件事。它不需要新增任何数据,那些码本来就躺在你的支付日志里。

三档划进代码时有个容易忽略的细节

这张表落地时,有个实现细节值得先说清楚:分档信息应该存在哪儿。

常见做法是在前端按码判断,写一串条件分支决定显示哪句话。这样做的问题是,那些不该外泄的码仍然被传到了浏览器——它就在响应体里,打开开发者工具一眼就能看到。前端把它藏起来了,但它已经离开了你的服务器。

正确做法是在服务端就完成分档与替换:第三档的码在离开服务端之前就被换成通用拒绝的标识,前端从来不知道真实原因是什么。这样即便前端代码被人逐行读过,也拿不到任何额外信息。

与此同时,服务端日志里必须保留原始码。风控分析、对账、以及跟收单机构沟通时都要用到它。对外替换、对内保留,这两件事得同时做,缺一个都会出问题。

最后一行不是错误,别用错误的样式渲染它

表格最后一行值得单独说。

需要额外验证的那类返回,本质是流程的一步,不是失败。可很多实现把它和拒付走同一条错误通道,于是用户先看到一句红色的支付失败,然后被跳去验证页面。

这一下红色,代价可能是一笔订单。用户在验证页面上犹豫的时候,脑子里想的是刚才那句失败。

正确处理是把它归入正常流程分支,用中性的说法:需要在你的银行确认一下这笔付款。站内讲强客户认证规则上线后怎么把拒付率压下来的那篇里,这条链路的完整形态说得更细。

不要把联系发卡行当成默认兜底

处理第二档时有个常见的偷懒做法:既然说不清,就统一显示“请联系您的发卡行”。

这句话有两个问题。

第一,它把动作推给了一个用户很不情愿去做的事。为了买一件东西去给银行打电话,这个门槛高到大部分人会直接放弃。

第二,它常常是错的。发卡行未给原因,不代表问题一定出在发卡行;也可能是这张卡不支持跨境交易、不支持这个币种、或者触发了金额阈值。这些情况里,换一张卡或者换一种支付方式的成功率,远高于打电话。

更好的措辞顺序是:先说明这笔付款被发卡方拒绝了、订单还在、钱没有被扣;再给出最省事的下一步,也就是换一种支付方式;最后才把联系银行作为备选提出来。三句话的顺序不能反。

钱扣没扣,是用户此刻最关心的那件事

这里有个几乎所有支付失败文案都漏掉的信息。

用户看到支付失败的第一反应不是“我该换张卡”,而是“钱是不是已经划走了”。尤其是那种页面卡了几秒才报错的场景,很多人会立刻去查银行应用。

如果这时候看到一笔预授权记录,恐慌就来了:钱没了,订单也没有。而真相往往只是一笔会在几天内自动释放的授权占用。

所以支付失败文案里应该有一句关于资金的明确交代,并且区分两种情况:完全没有扣款的,说清楚未产生任何扣款;产生了授权占用的,说清楚这不是扣款多久会自动释放。这一句话能挡掉的客服工单数量,通常比其他所有改动加起来都多。

重试要有节制,否则你在替别人做测试

还有一件跟报错措辞相邻、但后果严重得多的事:失败后的重试策略。

把重试按钮做得太顺手,等于给拿着一批卡号的人提供了一个免费的验证工具。他们不需要成功,只需要知道哪些卡是活的——而你的报错文案越精确,这个工具就越好用。

合理的约束包括:同一会话内的失败次数上限、同一IP与同一设备的频率限制、以及连续失败之后升级验证。这些约束的存在,是能不能提供精确支付报错的前提条件,不是可选项。

这一条与下一节要讲的登录场景是同一个逻辑,只是标的物不同:那边保护的是账号,这边保护的是卡。

支付这段的度量该看什么

最后给一个这一节专用的观察口径。

别只看整体授权成功率,那个数被太多东西影响。要看的是首次失败之后的挽回率:一笔支付被拒之后,同一会话内最终仍然完成订单的比例。

把这个比例按拒付码分档来看,第一档的挽回率应该显著高于第二档——如果两档差不多,说明你的第一档文案根本没起作用,用户没意识到自己几秒钟就能修好。

这个数还有个用途:它能反过来验证你的三档划分对不对。某个被你归进第二档的码,如果挽回率意外地高,说明用户其实有办法应对,那它值得被挪进第一档,配一句更具体的话。

什么时候把话说清楚,反而是在帮倒忙?

无障碍标准自己写好了这条例外:知道就必须给,除非会危及安全。这句除非,正好圈住了登录框和那四条拒付码。

无障碍标准自己写好了这条例外

讲到这一节,很多人预期会看到一场“体验”和“安全”的拉锯。实际上这场架不用打,因为标准里早就把口子留好了。

Web内容无障碍指南里有两条直接管报错的条款。一条是错误识别,属于A级:如果系统自动检测到输入错误,出错的项目必须被指明,并且错误必须以文本形式向用户说明。另一条是错误建议,属于AA级,措辞更有意思:如果系统检测到输入错误、并且已知纠正建议,那么这些建议必须提供给用户,除非这样做会危及内容的安全性或目的

读第二条时慢一点。它的结构是:知道就必须给,例外只有安全。

这几乎就是本文命题的标准版表述。而且请注意它的时间——这条款是二〇〇八年那版指南里就有的,比任何一份关于自适应报错的体验研究都早得多。

那句除非,正好圈住了登录框

例外条款的存在,意味着你不需要在合规和安全之间做选择题。

登录失败时把话说糊,不是违反无障碍标准,而是标准明确允许的情形。同样地,支付被拒时对那四类码统一模糊处理,也落在这个例外里。

但这个例外是有边界的,它只覆盖“说清楚会危及安全”的那部分。拿它当挡箭牌去合理化整个站点的含糊报错,就是滥用了——收货地址填错这件事,怎么说都危及不到安全。

保哥见过团队用安全理由拒绝细化所有报错,问下去发现真正的原因是不想动那段代码。用一条正当的例外去掩盖一个偷懒的决定,是这个行业里很常见的操作。

说清楚等于替撞库的人验证了一遍邮箱库

登录场景的具体风险,说穿了很简单。

如果登录失败时能区分“这个邮箱我们没见过”和“邮箱对了但密码不对”,那么任何人都可以拿一批邮箱地址来逐个试,不需要知道密码,就能筛出哪些地址在你这儿注册过。

筛出来的这份名单有商业价值,也有攻击价值:它可以拿去别处撞密码,也可以拿来做定向钓鱼——一封声称来自你品牌的邮件,发给确认在你这儿有账号的人,打开率会高得多。

密码重置流程有同样的问题。提示“该邮箱未注册”跟登录报错是一回事,只是入口换了个地方。找回用户名、修改邮箱、账号绑定,这几处也都在同一张网里。

判据只有一句话

那么怎么判断某条信息该不该说?保哥用的判据是一句话:这条信息对攻击者的价值,是否高于它对真实用户的价值

拿几个场景套一下就很清楚。

“密码错了”对真实用户价值很高,他立刻知道该去点忘记密码;对攻击者价值同样很高,因为它顺带确认了账号存在。两边都高,取保守,所以含糊处理。

“这张卡已过期”对真实用户价值很高,换张卡就行;对攻击者价值很低,因为过期与否他自己看卡就知道。所以可以直说。

“收货地址的门牌号缺失”对真实用户价值很高,对攻击者毫无价值。直说,而且要说得越细越好。

这条判据的好处是它能被非安全背景的人执行。不需要懂威胁建模,只要能想清楚“如果打这句话的是个坏人,他会因此多知道什么”。

三个真实样本,含糊与清楚的差距

Baymard的测试记录里有三个登录场景样本,恰好构成一组对照。

一位受试者在某零售站登录时其实打错了邮箱,但收到的提示含糊,于是她把全部注意力放在密码上,反复念叨自己肯定输对了。她找错了方向,而这个方向是提示语引导的。

另一家站给出的是“邮箱地址和密码组合无法找到”,这句话在安全上是标准写法,可它同时也让所有真实用户失去了方向。

第三家站直接说“密码不正确”,用户立刻把注意力集中到密码字段。体验上明显更好,安全上明显更弱。

三个样本摆在一起,正好说明这个取舍是真实存在的、无法两全的。能做的不是消除这个取舍,是把它挪到别处去消化——下一节讲的就是挪法。

限速做到位,才有资格谈精确报错

取舍能不能挪,取决于一件事:账号枚举的成本高不高。

如果登录接口可以被无限次调用,那么含糊报错也拦不住多久——攻击者有的是别的手段推断账号存在,比如注册流程会不会提示邮箱已被占用、重置流程的响应时间差异。

反过来,如果每分钟尝试次数、单IP尝试次数、单账号连续失败次数都有硬约束,超过阈值就升级验证甚至临时锁定,那么枚举的成本会高到不划算。这时候把登录报错做细一点的风险,就在可接受范围内了。

顺序不能反:先有限速,才有资格讨论要不要说清楚。没有限速就直接细化报错,等于把大门拆了再讨论门锁的款式。

如果做不到这些基础防护,那就老老实实用含糊报错,把体验损失认下来。这是Baymard那篇文章的建议,也是保哥的建议。

创建密码和输入密码,是两个相反的场景

这里有个特别容易搞混的地方。

登录时的密码字段要含糊,创建密码时的密码字段要极其清楚。因为创建时那些规则不是秘密——它们本来就该在用户开始打字之前全部公开。

Baymard的基准里,约82% 的站点设置了不必要的复杂密码要求。而更值得注意的是他们记录的后果:某些站点因为密码重置环节的问题,导致老用户的结账放弃率高得离谱,其中与重置邮件相关的问题最为集中。

创建密码时最糟糕的做法,是先让用户打完,提交后才告诉他还需要一个特殊字符。正确做法是规则全部前置显示,并且随着输入实时打勾。

这两个场景在代码里常常共用一个密码输入组件,而组件默认不知道自己此刻处在哪个上下文。需要一个显式的场景参数,把说清楚和说含糊两套行为分开——靠约定俗成迟早会出事。

把体验损失挪到别处消化

回到那个无法两全的取舍。既然登录报错必须含糊,怎么把损失补回来?

三个方向。第一,把忘记密码的入口做得极其显眼,就摆在报错旁边,而不是藏在页面角落。既然用户拿不到方向,就把最可能有用的那条路直接递到他手上。

第二,重置流程本身要快。一次点击、一封信、一个链接,不要再加安全问题、不要再让他回忆注册时间。Baymard记录的那些放弃案例,很多不是卡在报错,是卡在重置流程太长。

第三,减少需要登录的场景。结账不强制注册,是这一整类问题最彻底的解法——用户压根不用登录,也就不会被登录报错卡住。

这一节的一句话结论

第三类销毁点的特殊之处在于,它是唯一一类销毁本身是正确决定的。前两类都在想办法把信息捡回来,这一类要想的是怎么让用户在没有这条信息的情况下也走得下去。

换个说法:前两类的目标是让报错更准,这一类的目标是让报错不重要

从2025年6月28日起,同一件事在欧盟换了性质

那份研究发表时它还是纯体验话题。一年半之后,指令把电子商务服务列进了受管范围,点名的正好是支付与身份那几块。

那份研究发表一年半之后,规则变了

Baymard那篇讲自适应报错的文章发表于二〇二三年十二月,通篇是体验论证:用户会卡住、恢复时间会拉长、严重时会弃单。全文没有一个字提到法律,因为在那个时间点上,这确实是个纯体验话题。

一年半之后,同一件事在欧盟换了性质。

欧洲无障碍法案是二〇一九年通过的一份指令。它的第二条第二款列了受管的服务类别,最后一项写着电子商务服务。而第三十一条第二款规定,成员国自二〇二五年六月二十八日起适用相关措施。

所以那个98% 的数字,如果拆开来看其中卖向欧盟市场的那一部分,它的含义已经从“这些站没做这项优化”变成了“这些站可能不合规”。

指令里对电商的要求,正好指向支付和身份那一段

具体要求写在附件一里,值得逐条对一下。

面向所有受管服务的通用要求中有一条:网站与移动应用必须以一致而适当的方式做到可感知、可操作、可理解、稳健这四个词是无障碍领域的四原则,写进法条正文。

针对电商服务的附加要求里,有一条更具体:身份识别、安全与支付功能,在作为服务的一部分提供时,必须做到可感知、可操作、可理解、稳健;另一条要求身份识别方式、电子签名与支付服务同样满足这四项。

把这两条跟前几节的内容摆在一起看,重合度高得惊人:登录、密码重置、支付失败提示,正好是这份指令点名的三块地方,也正好是本文讨论第三类销毁点时的三个主战场。

可理解这个词不是形容词,它有技术定义

法条里的四个原则听起来抽象,但它们不是留给律师发挥的空间,而是有配套的技术标准去落地。

欧盟层面用的调和标准把无障碍指南的双A级要求纳了进来,而前面提到的那两条报错款项,正是双A级和A级里的内容。也就是说,从法条的“可理解”一路推下去,最终落到的具体条文就是:错误必须以文本形式说明,已知的纠正建议必须提供

这条链路值得完整记住,因为它能改变一场内部讨论的走向。当有人说“报错文案是体验优化,不着急”的时候,链路的存在能把话题从偏好之争变成合规排期。

当然,也别把话说过头。指令是给成员国的,具体执法尺度、过渡安排、豁免范围由各国转化后的国内法规定,各国不完全一致。落地时该看的是目标市场那一国的具体规定,而不是直接引用指令去下结论。

三秒钟消失的提示,不满足以文本说明

把标准落到实现上,第一个被淘汰的常见做法是浮层提示。

移动端很流行用一个从底部滑出的小条来显示报错,三秒后自动消失。它省地方、不打断布局、看起来很现代。

问题有三个。一是它消失之后页面上不留任何痕迹,用户想再看一眼看不到;二是它通常离出错的字段很远,用户得自己找是哪一项;三是屏幕阅读器用户很可能完全错过它,取决于实现方式。

要满足“出错的项目被指明、错误以文本说明”,报错必须和字段绑在一起、持续存在、直到问题被解决。浮层可以留着做提醒,但不能是唯一载体。

报错要跟字段有程序上的关联,不只是视觉上挨着

另一个高频缺陷是关联方式。

很多实现把报错文字放在字段下方,颜色改成红色,视觉上一目了然。可对于依赖辅助技术的用户,这两块内容之间没有任何关系——它们只是恰好在文档里前后相邻。

正确做法是用标准的关联属性把报错文本挂到输入控件上,让辅助技术在读到这个字段时,能连带读出它的错误说明。这是一行属性的事,成本几乎为零,却是合不合规的分界线。

同理,光靠红色来表达“这里有问题”也不够。颜色不能作为唯一的信息载体,得有文字或者图标一起。这条在色觉障碍之外还有个现实理由:不少人在强光下的手机屏幕上分不清那点红。

多个错误时,顶部要有一份汇总

提交后同时冒出五六个错误,是长表单的常态。这时候光靠逐字段提示不够。

比较稳妥的组合是三件套:顶部一份汇总,列出所有出错项并可点击跳转;每个字段旁边有自己的说明;焦点自动移到第一个出错的字段或者汇总区。

汇总区还有个容易忽略的细节:它出现时应该能被辅助技术主动播报,而不是等用户自己浏览到那里。否则对于看不见页面的用户,点了提交之后页面像是什么都没发生。

顺带一提,汇总里的每一条也该是具体的。把五句“输入有误”列成一张清单,只是把含糊报错做成了批发。

移动端还有两个额外的检查项

这些要求落到手机上,会多出两个桌面端不存在的坑。

一是虚拟键盘。报错文字出现在字段下方时,如果键盘正好弹起,那句话会被完全遮住。用户看到输入框变红,却读不到任何解释。稳妥做法是报错出现时把出错字段滚到可视区中部,而不是简单地滚到顶部。

二是吸底的结算栏。很多结账页把提交按钮固定在底部,它同样会遮住页面底部的报错和汇总区。提交失败时应该临时收起吸底栏,或者把焦点移到第一个出错字段。

微型企业有豁免,但多数独立站够不着

指令确实给小主体留了口子,服务提供方达到微型企业标准的可以豁免。判断标准是人数与营业额的双重门槛。

但做跨境电商的要留意两点。第一,这个门槛比很多人想象的低,营收上了规模就不再适用;第二,判断的是提供服务的那个法律主体,不是团队里做前端的那几个人。

更现实的考虑是:就算眼下豁免成立,报错这件事本身的投入产出比也足够高,不需要靠合规压力才去做。把合规当成排期的推力可以,当成唯一动机就本末倒置了。

站内那篇讲网站无障碍改造与自然流量之间关系的内容里,能看到这类改动的收益往往不止在合规这一侧。

这一节的另一面:报错文案是搜索引擎看不到的内容

最后翻一个面,讲讲这件事跟可见性的关系。

报错文案有个特点:它几乎全部活在登录之后、或者提交之后爬虫抓不到,模型训练时也读不到。于是当用户遇到问题去搜“某品牌 支付失败”的时候,搜索结果里能出现的只有论坛抱怨帖、竞品的对比文章、以及一些泛泛的科普。

你自己的站在这个话题上是完全缺席的,尽管你是全世界最了解这件事的那一方。

解法不复杂:把高频报错的确切原因和处理办法,做成不需要登录就能访问的帮助页面,标题直接用用户会遇到的那句报错原文。这类页面的搜索量不大,但意图极其明确,来的人下一步就是回去下单。

顺手把机器能引用的部分也补上

再往前一步,这批帮助页面还有个额外用途。

用户越来越习惯直接问AI助手“这个牌子付款失败是怎么回事”。助手能引用的素材,只能是公开可读的那部分。如果这个话题下全网只有抱怨帖,那答案就从抱怨帖里长出来。

所以把这些页面写清楚,本质上是在给引擎提供这个话题下唯一一份来自当事人的可引用材料。这跟站内讲高客单价品类靠内容与信任在AI搜索里立足的思路是同一条。

写的时候有个小要求:每一条都要带上具体动作和具体条件,不要写成“请联系客服”。对引擎来说,一句请联系客服等于没有答案;对用户来说也一样。

一次把拒付码全都翻译出来的改版,第七个月收到一封信

一次失手复盘。方向没错,数据也一路支持,可页面对所有人一视同仁,包括那些根本不是来买东西的人。

那个站和那个决定

这是一家做办公家具的出海站,主力是人体工学座椅和升降桌,卖德法英三个市场,客单价三百到八百欧。客单价高、决策链长、复购稀疏,所以每一笔走到支付环节的订单都很贵。

当时的痛点很清楚:支付失败率不算低,而失败之后的挽回率低得难看。页面上那句话是标准的通用文案——支付失败,请检查您的支付信息或更换支付方式。

保哥提的方案,本质上就是本文前面讲的那套:既然支付服务商返回了细分的拒付码,那就别浪费,按码分流,把能说清楚的说清楚。

方案上线时做了四十七条码的映射,每条码配一句中文和对应的德语法语英语文案。那个决定的实质,是把上游给的全部信息原样转交给用户,用透明度换挽回率

头四个月的数据好得没话说

效果立竿见影。

支付失败后的会话内挽回率涨了一大截,涨幅主要来自三类:卡片过期、安全码打错、账单邮编与卡不符。这三类的共同点是用户几秒钟就能修好,此前他们全都被那句通用文案劝退了。

客服那边的支付类工单也降了,因为大部分人不再需要问“为什么付不了”。

数据这么漂亮,团队顺势加码:把重试按钮做得更顺手,失败后表单保留已填内容、光标自动定位到出问题的那个字段,一次点击就能再试。这个优化本身也没错,它确实让真实用户少了几步。

四个月里没有任何一项指标发出过警告。

第七个月,问题不是以问题的形式出现的

转折点是一封邮件,来自收单机构的风控团队。

邮件的主题不是欺诈,是授权成功率下降。大意是这个商户号近期的整体授权通过率跌破了他们的观察阈值,希望商户自查交易质量,并附了一句提醒:持续偏低会影响后续的费率与风控评级。

团队的第一反应是这封信搞错了对象——我们的真实订单量在涨,转化率也在涨,怎么会成功率下降。

把授权尝试的原始记录拉出来才看明白。总的授权请求数在过去两个月里翻了几倍,而成功数几乎没变。多出来的那几倍全是失败,且高度集中在少数几个时段、少数几个IP段,每次尝试的卡号都不一样,金额都是最小的那档。

这不是购物,这是有人在拿一批卡号做验证。

你的精确报错,成了别人的验卡工具

逻辑一旦串起来就很难受了。

对于想验证一批卡号是否有效的人来说,最有价值的不是成功,是能区分不同的失败。余额不足意味着这张卡是活的,只是钱不够;卡号错误意味着这串数字压根不存在;安全码不符意味着卡号和有效期都对,只有那三位不对。

通用文案给不了这些区别,所以他们此前很难在这个站上高效工作。而那四十七条精确的中文文案,等于把这台机器的仪表盘擦干净了,还配了说明书。

更要命的是那个“做得更顺手的重试”。表单保留内容、光标自动定位,对真实用户是体贴,对脚本是省事。两者用的是同一条路径。

代价落在了完全无辜的人身上

这件事最让人难受的地方,是损失的分布。

验卡的人没有损失,他们试完就走。有损失的是这家店的正常客户:发卡行侧对这个商户的整体信任度在下降,表现为一部分本来能通过的授权开始被拒,尤其是跨境、大额、以及首次交易的那些——而这三条恰好精准描述了这家店的主力客群。

换句话说,一个八百欧的座椅订单,第一次下单的德国客户,正好落在最容易被误伤的那一格里。

这批被误伤的订单在自己的报表里长什么样?长得像“支付失败”,然后因为报错文案做得好,用户换了张卡又成了——所以连挽回率都还不错。整件事在内部数据里几乎无迹可寻,直到收单机构来信。

原语:当损失通过一个中间机构传导回来时,它在你自己的报表里通常没有对应的科目

修的时候才发现,那张表压根没有这一列

着手修复时,第二层问题浮出来了。

那张四十七行的映射表,字段只有三个:拒付码、文案键、备注。没有任何一列记录这条码能不能对外披露

而支付服务商的公开文档里,对其中几条码是明确写了不要向持卡人透露具体原因的。也就是说,规则一直摆在那儿,只是没人把它作为一个字段落进系统——它停留在“做这件事的人当时读到过”的层面。

做这件事的人当时读到过,然后那个人换了岗,映射表还在扩,新加的行由别人补,谁也不知道有这回事。

原语:一条只存在于某个人记忆里的约束,等于不存在;能约束系统行为的只有字段和校验

第三层最值钱:那四十七条里有二十三条从来没触发过

修复过程中顺手做了件事:统计过去十二个月里每条码的实际触发次数。

结果很有意思。四十七条里有二十三条在整整一年内一次都没有触发过。它们是当初照着服务商文档一条条抄进来的,抄的时候没人问过这些码在这家店的实际交易里会不会出现。

而触发量的另一端更极端:五条码占了全部触发量的八成左右,就是前面提过的卡过期、余额不足、安全码不符、邮编不符、发卡行未给原因这几类。

这个分布直接推翻了当初的做法。团队花在那四十七条文案上的力气——写、评审、翻译成三种语言、验收——有相当大一部分投在了永远不会被人看到的字符串上。

原语:优先级不该按能写多少条来排,该按这条码去年触发过多少次来排。Baymard那篇文章建议给复杂字段写四到七条消息,这个建议是对的,但前提是那四到七条得是从你自己的日志里数出来的,不是从文档里抄下来的。

改了三处

最终的修复不复杂,三件事。

第一,映射表加一列披露级别,取值三档:可原样说明、只能归类说明、必须模糊处理。这一列的默认值设成最保守的那一档,新增码时必须有人显式改过才能放宽,改动进代码评审

第二,重试路径加节流:同一会话失败次数上限、同设备与同网段的频率限制、连续失败之后升级验证。对真实用户几乎无感,因为真实用户很少连续失败三次以上。

第三,砍掉那二十三条从未触发的文案,把省下来的力气全部投进那五条高频码,每条做到三种语言逐字打磨,并配上前面讲过的资金交代那一句。

结果与那笔算不清的账

两个月后授权尝试量回到正常水平,收单机构那边的观察解除。跨境首单的授权通过率用了大约一个季度回到之前的水位。

挽回率呢?只掉了很小一截,几乎在噪音里。因为掉的那部分本来就集中在从未触发的那二十三条上,而它们本来就没在挽回任何人。

真正算不清的是中间那三个月的账。被误伤的那批高客单价首单客户,有多少最终去了别处,这个数字永远不会有确切答案——他们没有留下工单,没有留下失败订单,只留下一次授权被拒和一次关掉页面。

保哥事后复盘的结论是:那个原始决定的方向没错,错在把“把信息交给用户”当成了一条无条件的原则。信息交给谁这件事,你没有选择权——页面对所有人一视同仁,包括那些不是来买东西的人。所以真正该问的从来不是“这条信息对用户有没有用”,而是“这条信息在最坏的那个人手上值多少钱”。

四周之内先动哪几处,才不至于变成又一个文案项目?

第一周不改一行代码,只出三张清单。第二周动最便宜的那一层。真正需要正经排期的只有其中一周。

原创指标之二:报错重犯率

前面给过报错分辨率,它量的是你丢了多少信息。这里给第二个,量的是另一件事:你说的那句话到底有没有起作用

定义:同一个用户在同一个字段上连续触发两次及以上校验错误的会话,占所有触发过该字段校验错误的会话的比例

它的妙处在于不需要问用户任何问题,也不需要猜他在想什么。他看完提示又错一次,这件事本身就是判决书。

三个好处。一是它只需要在现有的报错埋点上加一个字段——字段名加子规则标识——就能算出来;二是它按字段拆之后能直接排出改造优先级;三是它对改动的反应极快,文案改完一周内就能看到位移。

这个数怎么判读

判读的门道比计算更值钱。

观察到的形态说明什么该动哪里
重犯率高于三成那句话在事实上等于没说先查分辨率,多半是信息被压掉了
重犯率低但触发量很大话说清楚了,可这个错误就不该发生改字段设计、加掩码或做规范化
重犯率接近零且触发量小这个字段没问题不要动它,去看别的字段
重犯率单市场偏高规则或译文在那个市场不适用查规则是不是挂在了语言上
重犯率改版后不动用户可能根本没看见那句话查位置、颜色、是否被折叠遮挡

最后一行是保哥踩过的坑:文案改得很用心,数据一点没动,查了半天发现移动端那句话被键盘挡住了,用户从头到尾没看见过。

日志要记子规则标识,不要记文案

要让这两个指标能算,埋点得改一处。

很多站的报错日志记的是展示出去的那句文案字符串。这个做法有两个致命问题:文案一改,历史数据就断了,前后没法比;多语言站上,同一个错误在六种语言里是六条不同的记录,聚合时要么漏统计,要么得维护一张翻译对照表。

正确做法是记两样东西:字段标识和子规则标识。文案是这两者的函数,随时能查出来,反过来则不成立。

顺手把触发时机也记上:是失焦触发、输入中触发,还是提交后由服务端返回。这一列在后面调时机时会救命。

报错时机:什么时候说,比说什么更影响感受

同一句话,出现的时机不同,效果可以差出一倍。

字段类型建议时机为什么
邮箱、手机号离开字段时输入过程中判定必然误报,用户还没打完
密码创建输入过程中规则要实时打勾,让人边打边看进度
卡号、有效期、安全码离开字段时,且配掩码位数确定,离开时判定最准
邮编与地址离开字段时提示,提交时校验需要跟国家联动,中途判定容易打架
数量与金额输入过程中范围约束即时反馈成本最低
跨字段规则只能提交时依赖多个值,提前判定会误伤
服务端唯一性校验失焦时异步查,提交时兜底邮箱已注册这类信息越早说越好

有一条通用禁忌:不要在用户还没离开字段时就判定他错了。那种打到一半就被红字追着骂的体验,比含糊报错更劝退,因为它把用户置于一种一直在犯错的状态里。

十八项上线自查

整套东西落地时,这份清单可以直接拿去当验收标准。前七项管数据,中间六项管页面,后五项管流程。

  1. 校验规则集中在一处,前端与服务端从同一来源生成或读取。
  2. 规则表里每条子规则有唯一标识,且标识不随文案改动而变。
  3. 校验函数返回的是带原因的结构,不是布尔值。
  4. 接口响应结构里保留了原因码,没有在统一封装时被抹平。
  5. 报错日志记字段标识与子规则标识,不记文案字符串。
  6. 日志里记录了触发时机,能区分前端判定与服务端返回。
  7. 支付拒付码映射表有披露级别列,默认值为最保守档。
  8. 每条报错与对应字段有程序上的关联,不只是视觉相邻。
  9. 报错持续存在直到问题解决,不使用会自动消失的浮层作为唯一载体。
  10. 颜色不是唯一的错误标识,配有文字或图标。
  11. 多个错误时顶部有可点击的汇总,且焦点会移到第一个错误。
  12. 汇总里每条都是具体说明,不是重复的通用句。
  13. 移动端触发报错时,那句话不会被键盘或吸底栏遮住。
  14. 新增子规则若缺主语言文案,构建失败。
  15. 次语言缺项进入待翻译队列,超过阈值升级为失败。
  16. 登录与密码重置走独立的含糊策略,与创建密码的组件行为显式区分。
  17. 失败重试有次数与频率约束,且约束早于精确报错上线。
  18. 高频报错有对应的公开帮助页,无需登录即可访问。

四周落地路径

不需要立项做大改造,四周足够把主干跑通。

第一周不改一行代码,只出三样东西:一份按触发量排序的报错清单,一份每个高频字段的报错分辨率计算,一份支付拒付码的三档划分草案。这三样都是读代码和读日志得来的,不依赖排期。

第一周结束时你大概率会发现两件事:触发量的分布极度集中,前五名占了大半;而这前五名里往往有一两个属于本不该存在的那类,清洗一下就能消失。

第二周动最便宜的那一层:前端渲染分支。如果响应体里本来就有原因码,这一周就能把高频字段的报错拆开,一行后端代码不用碰。同时把规范化逻辑补上,把那些不该报错的报错删掉。

第三周动接口与规则表:把校验函数的返回值改成带原因的结构,把规则集中成表,加上构建期的缺项检查。这一周是全程唯一需要正经排期的部分。

第四周做支付与登录这两块特殊地带:拒付码按三档落地,登录侧确认限速已经到位,两个指标的埋点上线,看板搭起来

三档投入怎么选

投入档位做什么适合谁
最小档清洗该规范化的输入,拆开前五个高频字段的报错,补一句资金交代团队没有专职前端,只能挤出几天
标准档最小档加规则表集中、构建期检查、两个指标上线有一到两名工程师能投两三周
完整档标准档加无障碍关联属性、错误汇总、公开帮助页体系有欧盟市场,或正在做合规排期

三档不是递进的必选题。最小档能吃掉这件事六成以上的收益,因为收益本来就集中在那几个高频字段上。如果时间只够做一件事,就去把触发量前五的报错拆开说清楚

它跟整站体验治理是什么关系

最后把这件事放回更大的盘子里看一眼,免得做完之后不知道下一步该去哪。

报错文案属于一类特殊的界面内容:它只在事情出错时出现,所以在日常的页面走查、设计评审、以及大部分可用性测试里都不会被看到。同一类的还有空状态、超时提示、库存不足的说明、以及各种权限受限的提示。

这一整类内容的共同处境是没有主人。产品觉得它是文案,文案觉得它是技术,技术觉得它是产品定的。于是它们在系统里自然生长,谁临时需要谁写一句,几年下来长成一片没人认领的灌木丛。

所以做完报错这一块之后,值得顺手把同类内容列一份清单。有了清单,下次再有人临时加一句提示,至少知道该往哪儿加。

三条反信号:出现这些说明方向偏了

最后给三条自查用的反信号。

第一条,如果这件事变成了一个文案项目,中间产物是一份很长的措辞表,而没有任何一次接口改动,那么它多半解决不了问题。文案早就不是瓶颈了。

第二条,如果报错文案的条数在涨,而报错分辨率没变,说明你在给同一批信息换着花样表达。换十种说法说“无效”,还是无效。

第三条,如果登录和支付这两块被同一套原则一刀切地处理了——要么全部说清楚,要么全部含糊——那说明三类销毁点还没有被真正区分开,早晚会在某一侧出事。

回到那行代码

整篇文章绕了一大圈,落点其实还是最开始那句话。

系统能判定不合格,就必然算出过哪里不合格。这份知情要么被你自己丢在某一行返回值里,要么被上游扣着不给,要么被你出于正当理由故意说糊。三条路径的修法完全不同,但第一步是共同的:先分清楚眼前这句含糊话属于哪一类

分清楚之后你会发现,绝大多数含糊话属于第一类。而第一类是唯一一类,只要你愿意去翻那几行代码,今天就能改掉的。

常见问题解答

后端到底知不知道用户具体错在哪,怎么自己验证一下?

不用问人,半小时就能验完。挑一个高频字段,比如邮箱或者手机号,打开浏览器的开发者工具,切到网络面板,然后故意用三种不同的错法各提交一次。

看那三次请求的响应体。如果三次返回的内容完全一致,那么信息是在服务端丢的;如果响应体里带着三个不同的原因码,而页面上显示同一句话,那么它是在前端丢的。后者只需要改渲染分支,通常一两天就能上线。

如果连响应体都看不到细分原因,再去读服务端的校验函数,数一数它内部有几个判断分支。分支数就是这个字段的真实分辨能力,也是你能拿回来的信息上限。

报错文案改细之后,会不会给恶意用户提供便利?

要分场景,不能一刀切。

格式类的报错几乎没有风险。收货地址少了门牌号、邮编位数不对、数量超出限购,这些信息对攻击者毫无价值,说得越细越好。

有风险的只有两类:一是登录与密码重置,说清楚等于帮人验证账号是否存在;二是支付被拒,尤其是欺诈嫌疑、挂失、被盗这几类拒绝原因,支付服务商的公开文档里明确要求按通用拒绝呈现。

这两类的前提条件是速率限制。如果尝试次数、单IP频率、连续失败升级验证这些约束都到位了,风险可控;如果一样都没有,那就先别急着细化,先把门锁装上。

没有开发资源,只能改文案的话,先改哪几句最划算?

按触发量排序,改前五名,别贪多。

纯文案层面能立刻改善的有三类。第一类是把关于世界的断言改成关于自己的陈述,把“该邮箱无效”换成“我们这边暂时不接受这类地址”;第二类是把结论换成动作,把“号码格式不对”换成“还差两位”;第三类是补齐支付失败时的资金交代,说清楚有没有扣款、授权占用多久释放。

第三类往往是投入产出比最高的,因为它挡掉的客服工单量最大,而它一行代码都不用改,只是把话说全。

做了输入掩码和格式示例,是不是就不用管报错了?

不行,预防这条路有明确的天花板。

Baymard关于输入掩码的研究里给过一个数:即使站点已经在字段旁边给出了格式示例,仍有约89% 的受试者按自己的习惯输入,不照示例来。示例就在眼前,八成九的人视而不见。

所以正确的理解是接力关系:掩码负责把能自动纠正的形态问题消化掉,报错负责接住掩码消化不了的那批。掩码做得越好,剩下的报错越硬核,措辞反而要更准。

把报错做细,跟无障碍合规是同一件事吗?

大部分重叠,但不完全等同。

无障碍指南里管这件事的有两条:一条要求出错项目被指明、错误以文本说明,属于A级;另一条要求已知的纠正建议必须提供给用户,例外只有安全考虑,属于AA级。第二条的措辞跟自适应报错的主张几乎逐字对应。

不同之处在于,无障碍还额外要求了报错与字段之间的程序关联、颜色不能是唯一标识、以及焦点管理这些实现细节。这些细节跟文案写得细不细无关,但它们决定了辅助技术用户能不能读到那句话。

换句话说,报错写细了不一定合规,合规了则一定包含了写细这一步。

多语言站怎么防止某个语言长期停在兜底文案上?

靠构建期检查,别靠人盯。

具体做法是:规则表里每新增一条子规则,如果主语言的文案缺失,构建直接失败;其他语言缺失则发出警告并写入待翻译队列,队列长度超过约定阈值才升级为失败。

这个设计的关键在于,运行期的兜底逻辑仍然保留——线上不会因为缺一条译文就崩,但缺项会在合并代码之前被看见。很多站的问题不是没有兜底,恰恰是兜底做得太安静,替缺项遮掩了两三年。

另外提醒一点,校验规则本身应该挂在收货国家上,文案语言才挂在界面语言上。这两条线混在一起,会造出一批只在特定组合下出现的怪问题。

怎么向老板证明这件事值得排期?

用两个数,都不需要新埋点。

第一个是报错分辨率:挑三个关键字段,数一数后端能分出几种失败情形,再数一数前端实际有几句话。这个比值通常在三到六之间,摆出来很直观——它说明每一个报错用户拿到的信息,只有系统实际掌握的几分之一。

第二个是报错重犯率:同一个用户在同一个字段上连着错两次的比例。这个数高于三成,就是那句话没起作用的直接证据。

另外换个立项科目也很有用。作为文案优化提,它排在新落地页后面;作为“接口返回值丢失业务信息”的技术债提,它进的是另一个队列,竞争对手的量级完全不同。同一件事,通过率能差出一倍。

权威参考资料

分享到
标签
版权声明

本文标题:《你的后端一清二楚用户错在哪里,页面上却只剩下四个字:输入有误》

本文链接:https://zhangwenbao.com/form-validation-error-message-adaptive-copy-checkout.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交