1. 先说透:为什么“用户愤怒模式”才是软件质量的试金石
那天的上线前的平静,是被一条工单撕裂的。某云盘项目在凌晨接到了大批用户投诉,说上传图片一直转圈,转着转着直接闪退。团队所有人都在前排排队看监控,结果后台的崩溃率曲线像心跳骤停一样从水平线拉成了垂直线。问题是,那天我们所有自动化测试都是绿的,用例覆盖率六成以上,核心链路压测也过了一千并发。为什么会出这种事?
后来复盘才搞清楚,真正触发崩溃的操作顺序是:用户连了几格信号极差的移动网络,点开相册后立刻切换到“上传原图”,等了两秒没有反应,又疯狂地拉下通知栏、切换后台、再点回来,重复同一个上传按钮。这一整套“愤怒操作”,我们所有测试脚本都没有覆盖。因为我们的测试脚本太讲礼貌了:规规矩矩地输入用户名,规规矩矩地等网络返回,规规矩矩地点了一个按钮就不再碰它。
从那以后,我开始在团队里推一套叫做“用户愤怒模式”的测试方案。简单说,就是把真人用户在被崩溃、卡顿、转圈圈逼到情绪失控时的操作特征,归纳成一套可执行的测试用例,专门用极端、重复、快速、复合的操作去轰炸应用,看它还能不能撑住。这套东西不是什么玄学,也不用买昂贵的设备,它本质上是把“人的情绪”翻译成“机器能跑的脚本”,然后让它每天替你折腾产品。
这套方法适合谁?适合所有做客户端产品、Web应用、后端服务的开发和测试。不管是几十个人的创业团队,还是中大型产品已经跑了很多年,只要你觉得“该测的都测了,但上线还是出事”,那用户愤怒模式就是你现在最需要的质量补充手段。
2. 为什么“平静测试”永远测不出真问题
2.1 测试员的双手和用户的怒火,根本不是一类操作
大部分团队的测试用例,不管是手点的还是自动化跑的,都有一个共同特点:单线程、顺序执行、每一步都等前一步完成、输入的都是合法数据。这在实验室里看起来无懈可击,但真实用户不是这样的。
我做了一个简单的对比表,每次给团队分享时都会贴出来,非常直观:
| 维度 | 测试用例里的“上帝用户” | 现实中的“愤怒用户” |
|---|---|---|
| 点击节奏 | 每次点击间隔2~5秒,等待页面响应 | 每秒4~6次疯狂点击,不等响应 |
| 输入内容 | 有效账号、合法手机号、标准昵称 | 超长文本、粘贴各种符号、空格连打 |
| 网络环境 | 固定Wi-Fi,几乎零丢包 | 电梯弱网、地铁抖动、5G切4G再切飞行模式 |
| 操作顺序 | 严格按页面流程下一步 | 乱序、跳步、反复退出重进 |
| 后台状态 | 一直停留在当前页面 | 频繁切换后台、反复拉起、中途杀进程 |
| 系统资源 | 内存充足、存储充裕 | 内存剩余不足200MB,存储只剩几百MB |
看到这个表,你大概就明白了。常规测试验证的是“正确的路径下功能是否正确”,愤怒模式验证的是“错误的路径下系统是否还能保持体面”。这两者缺一不可。
2.2 那些年被“正常操作”蒙蔽的线上事故
我复盘过好几个典型事故,其实背后都是同一个问题。
第一个是下单场景。某电商小程序,用户在支付页点击“确认支付”后,页面卡了1.8秒没有跳转。用户着急,又连点了三下。前端按钮没有防抖,接口没有做幂等性校验,后端三笔订单全部创建成功,用户最终被扣了三份钱。这种问题在常规测试里几乎不可能出现,因为测试脚本点击一次后就会等待跳转,根本不会连续重复触达。
第二个是弱网下的重试风暴。某社交App在图片上传时,一旦网络抖动就会自动重试。正常测试里重试一两次就能成功,所以一切正常。但愤怒用户会在弱网下反复触发上传,而每条上传通道的失败重试又各自独立,导致底层同时积压了一百多个重试任务,内存峰值飙到几百MB,最终被系统直接杀掉。
第三个是权限剥夺。某拍照类应用在正常用例里,相机权限、相册权限、定位权限全部允许。但真实用户不想泄漏位置,拒绝定位权限,或者用安卓的“仅本次允许”,再或者干脆只给相机不给相册,然后点击“从相册导入”。结果模块里没有对空指针做防护,当场闪退。
这些事故都有一个共同点:每一个单独的操作都在代码里被“考虑到”了,但组合在一起,没有任何一个“考虑到”能覆盖它们。用户愤怒模式的价值,恰恰是把这些组合操作变成常态化的回归测试项,而不是靠运气等他们上线后再暴露。
3. 设计一套“愤怒模式”:别只会随机乱点
3.1 愤怒行为可以归类,不是玄学
很多人一听“愤怒模式”,第一反应是写一个脚本随机乱点屏幕。这其实是个误区。随机乱点只能覆盖一部分点击事件,而且复现成本极高,真正有效的设计必须把愤怒行为拆成四类:
- 输入攻击:给所有能输入的控件灌入异常数据。包括超长文本、纯空格、emoji炸弹、粘贴带换行的内容、混入脚本标签的字符串。目标不是测试XSS或者SQL注入,而是让纯文本输入在异常长度和编码组合下不崩溃、不卡死。
- 操作风暴:对同一控件在极短时间内高频触发,或对多个控件无序交叉触发。常见对象是提交按钮、Tab切换、下拉刷新、列表滑动。这一类的核心是找出没有防抖、没有幂等、没有互斥锁的代码路径。
- 环境恶化:主动制造弱网、高延迟、低内存、低存储、断网恢复、时间跳变这类外部条件。用户愤怒时往往不会正好待在性能良好的环境里,反而是在电梯、地铁、快速切换网络时情绪更大。
- 状态穿越:打破页面状态机的正常流转。比如页面还在加载时点下一步,表单还没提交完就切后台,WebSocket还没连上就发消息,在线支付正在结算就杀进程。这一类的难点是构造“时间窗口”,在状态转移的中间态下手。
这四类的测试策略完全不同,前两类偏前端和客户端,第三类偏网络和系统,第四类偏架构设计。但不管哪一类,都需要先盘点产品里的“风险触点”,而不是全产品无差别轰炸,否则只会收获一堆无关紧要的白屏截图和崩溃日志。
3.2 先给用户画一个“愤怒画像”
我每次负责的新项目测愤怒模式前,都会先开一次短会,问五个问题:
- 我们的核心用户是谁?是赶时间的小镇青年,还是做实体的老板,还是爱拍照的大学生?
- 他们在这个产品里最不能失去的是什么?是草稿内容、支付结果、还是聊天记录?
- 如果他们失去价值,会是在哪个环节?上传中、支付中、还是杀进程后?
- 他们最急的时候通常是什么场景?拍完照立刻发朋友圈,还是下单时信号不好?
- 他们用什么设备?两年前的千元机、内存不足的旧平板,还是旗舰机?
这五个问题回答完,基本就能确定愤怒模式的优先级。比如核心用户是小商家,那么最不能失去的是订单数据,支付和订单列表页的愤怒测试就是P0;如果核心用户是内容创作者,那么草稿和发布环节就是焦点。相反,如果产品是工具类软件,用户打开就为了看一眼结果,那状态恢复测试就不需要做得太重,把精力放到首次加载速度和弱网表现上更划算。
3.3 给愤怒分级:别把子弹浪费在低价值目标上
没有分级,愤怒模式就容易变成测试团队的体力活,天天跑出一堆“边缘废案”,但真正的重大风险反而没有覆盖。
我给团队用的分级标准是这样的:
- P0:资金流向、数据持久化、账号安全、核心链路(登录、下单、上传)。这类场景的任何一次崩溃或数据错乱都不可接受。
- P1:主功能的高频交互路径,比如列表加载、详情页跳转、缓存刷新。这类场景出现闪退或者卡死,用户会直接卸载。
- P2:低频次功能、营销页面、偏好设置。这类场景即便有点问题,用户可能会短暂吐槽,但不会造成真正损失。
分级之后,测试时间和资源的分配就有了依据。P0用最狠的组合拳,网暴式地反复打;P1用标准组合覆盖;P2只需要跑一轮冒烟级的异常输入即可。把精力集中在P0和P1上,愤怒模式的投入产出比会非常健康。
4. 实操落地:搭建一套愤怒模拟实验台
4.1 选型:别一上来就上重型框架
愤怒模式测试需要持续稳定地运行,不像手工测试那样点两天就行。选型上我倾向“轻装上阵、关注本质”。
- 客户端UI自动化:移动端用设备官方框架(Android的Espresso、iOS的XCUITest)或者成熟的跨端方案,负责驱动点击、滑动、输入。
- 接口/后端压力工具:负责生成指定路径的高并发请求,比如下单接口反复提交、文件上传接口并发启动,用来验证服务端幂等性和限流。
- 故障注入工具:负责捣乱网络、内存和存储。网络这块用代理工具模拟丢包和延迟,设备层面直接通过系统Shell调整网络模式、内存压力、存储剩余空间。
- 自定义脚本来协调整个流程:用Python或者JavaScript写一个“导演”脚本,按时间线把UI操作、网络抖动、内存告警、杀进程等动作排列组合起来,让自动化脚本真正模拟出一个气急败坏的人。
这种组合方案不需要完整的Appium网格,也不用上大型云测试平台,大多数团队用一台电脑加两三台真机就能跑起来。等验证流程跑通了、有价值了,再考虑翻倍扩充设备群。
4.2 搭一套统一的“愤怒套件”
我以曾经在某个模拟项目里搭过的“极简愤怒套件”为例,列一份可直接复用的清单。
硬件和系统环境:
- 一台Mac或Linux主机,用于跑脚本和起代理服务
- 两台Android真机(一台低配千元机,一台旗舰机)和一台iPhone
- Adb工具链(Android调试桥)
- 网络代理工具,用于模拟弱网和断网
- 一个简单的HTTP服务,用来接收客户端上报的日志和崩溃堆栈
核心脚本(Python为例,控制Android设备):
import subprocess import random import time import threading DEVICE_ID = "XXXX" # adb devices 里的序列号 PACKAGE_NAME = "com.example.angryapp" def adb(cmd, timeout=10): full_cmd = f"adb -s {DEVICE_ID} {cmd}" result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True, timeout=timeout) return result.stdout.strip() def tap_random(): width, height = adb("shell wm size").split("x")[0], adb("shell wm size").split("x")[1] x = random.randint(0, int(width) - 1) y = random.randint(0, int(height) - 1) adb(f"shell input tap {x} {y}") def fast_click_loop(button_x, button_y, times=30, interval=0.05): for _ in range(times): adb(f"shell input tap {button_x} {button_y}") time.sleep(interval) def set_weak_network(): # 通过代理工具切换到弱网配置,这里假设用系统命令切换网络模式 pass def kill_app(): adb(f"shell am force-stop {PACKAGE_NAME}") def background_foreground(): adb("shell input keyevent KEYCODE_HOME") time.sleep(random.uniform(0.3, 1.0)) adb(f"shell monkey -p {PACKAGE_NAME} 1") # 重新拉起到前台 def run_angry_scenario(): set_weak_network() for _ in range(10): fast_click_loop(500, 1000, times=15) background_foreground() tap_random() time.sleep(random.uniform(0.1, 0.3)) kill_app() if __name__ == "__main__": run_angry_scenario()这个脚本非常简陋,但足够说明核心思想:它不会按UiMap去找控件,它就是模拟一个不耐烦的人,在一个位置疯狂点、中途切后台、乱点、再杀掉重来。如果App在点击风暴期间出现无响应、崩溃,或者杀进程后数据丢失,这些就是我们要抓的目标。
在真实环境里,我会让脚本干得更精细一些:点击某个明确的按钮(用uiautomator dump拿到坐标)、等待精确的时间、在特定截断点发弱网。但骨架一定不是“走一遍流程”,而是“在随机和重复中寻找系统的破绽”。
4.3 关键参数设计与计算逻辑
愤怒模式测试的效果,很大程度取决于参数设计。网上随便找的脚本一跑,可能会跑出几百个崩溃,但至少一半是脚本自身问题,比如点击了系统设置、误触了状态栏。为了避免这种情况,我的参数设计规则如下:
- 点击频率:真人愤怒时的点击频率大概在200~400毫秒一次,连续10~20次是一个爆发单元。我用随机值在50~100毫秒之间制造更极端的连点,用来暴力验证防抖逻辑和闭包隔离。
- 等待时间:正常测试中每个步骤等待约2秒,愤怒模式下全部压到300毫秒以内。这能放大异步回调未完成、动画未结束时接收新指令的竞态窗口。
- 并发倍数:服务端压测并发数按用户峰值的1.5~2倍计算。比如一个接口常规峰值是200QPS,那愤怒模式的持续压力至少是300~400QPS,并保持每组请求之间存在200~500毫秒的随机间隔,模拟多个用户互相踩踏。
- 内存水位:Android上用
adb shell am hang或者通过分配大对象的方式占空内存,把可用内存压到危险水位,再启动App。这一步能有效暴露图片加载、列表缓存没有做释放的问题。
这些参数不是拍脑门定的,而是基于崩溃日志里用户操作的时间戳反推出来的。很多平台崩溃上报都会记录“didRequestInterruption之前的用户事件序列”,把这些事件串起来看,你会发现所谓的愤怒用户,其实操作节奏非常接近我上面说的范围。
4.4 给混沌加一点秩序:让愤怒可复现可回溯
随机性是愤怒模式的核心,但随机性也是复现问题的天敌。好在我们可以用种子的方式,把随机性约束在可控范围内。
我在脚本里会给每一次运行打上一个随机种子,并把种子和每次操作的完整时间戳、事件序列、截图一起记录到日志文件。一旦出现崩溃,研究员可以根据种子精确重放同一串随机事件,哪怕点击坐标不同,但事件的分布规律保持一致,复现概率大幅提升。
同时我建议每台设备在跑测试之前都做一次系统轻净化:清理缓存、恢复权限设置、重启App。这样能最大程度排除上次测试残留状态对本次结果的干扰。
5. 实战拆解:几个“逼疯用户”的典型场景
5.1 狂点提交按钮:重复支付的最后防线
场景:用户在支付页确认订单,卡了一秒多没跳转,于是疯狂点击“确认支付”。
现象:后端在同一毫秒级时间窗内收到多条请求,数据库里出现重复订单或重复扣款记录。
排查路径:
- 先看前端交互。按钮点击后有没有立即禁用?点击事件有没有做门槛(例如300毫秒内最多触发一次,或者是否触发后立即进入loading态)?
- 再看后端接口。接口是否是幂等的?同样的支付单号重复请求,是直接拒绝还是又重新扣一次钱?
修复建议:前端在点击提交后,立刻把按钮置灰且不可恢复(除非失败回滚),同时用防抖函数包住事件处理器。后端必须做幂等校验,可以用“用户+金额+时间窗口”生成请求唯一键,重复请求直接返回同一个响应。这个场景是P0级别,因为直接涉及资金。
5.2 空输入、超长文本、表情轰炸:输入框的尊严
场景:昵称输入框、评论输入框、标题输入框,用户一气之下粘贴几百个emoji、空格、换行、特殊符号。
现象:页面卡顿,有的机型直接白屏。原因多半是字体渲染和布局测量对极长字符串缺少上限控制,或者TextWatcher里对每次输入都做了昂贵的富文本处理。
排查路径:用自动化脚本快速灌入1000字符、5000字符、20000字符,同时观察内存曲线和UI帧率。然后再混入全角半角、零宽空格、emoji组合,看是否触发崩溃。
修复建议:所有输入框设置最大长度,最好在输入框底层做截断,而不是在TextWatcher里做字符串裁剪;对于需要实时渲染的富文本,改为延迟渲染或内容分片;在列表展示用户输入内容时,同样需要限制显示高度。
5.3 弱网抖断:全屏转圈变成全屏白
场景:三格信号满格,但实际是假信号,请求发出去后无人应答,用户下拉刷新又再下拉,然后切后台回来。
现象:网络请求超时后回调里做了“隐藏loading”但没做“内容兜底”,页面空白。如果用户疯狂触发刷新,会出现多个并发请求竞争同一块数据,UI状态被反复覆盖。
排查路径:用代理工具模拟30%丢包加800毫秒超时,然后跑“下拉刷新、不等待、再下拉刷新、中途切后台、再回来”的组合脚本,观察页面加载状态和网络请求数量。
修复建议:请求管理器加全局请求去重,相同URL和参数的请求如果还在飞行中,就不要发第二个;页面对加载失败要有明确的“重试按钮”和“上次缓存数据兜底”,不要让用户看到一片白。弱网下最忌讳的是转圈结束后无提示地清空整个页面。
5.4 后台切换加杀进程:状态保存不是理所当然
场景:用户在填写一个很长的表单,切到微信回一条消息,再切回来,App已经被系统回收,页面重新启动,所有填写内容全丢了。
现象:进程被系统杀掉后,Activity的onSaveInstanceState里没有保存关键字段;或者界面重建逻辑依赖本地变量而不是持久化数据,导致状态失真。
排查路径:通过adb shell am kill或者开发者选项里的“不保留活动”来强制复现进程回收,然后检查页面恢复后表单内容是否完整,网络请求是否重新发起。
修复建议:对任何包含用户输入内容的页面,在onSaveInstanceState里保存所有表单字段;更稳妥的做法是把草稿自动存入本地数据库或持久化存储,在onCreate时依次读取“持久化数据->内存缓存->界面字段”的恢复顺序。这一条对记录类App和电商购物车尤其重要。
5.5 多设备并发同账号:踢线还是互相踩踏
场景:同一个账号在手机上登录,平板上又登录,用户气呼呼地两边同时下单。
现象:服务端没有做会话版本控制,同一账号同时在多处建立WebSocket连接,消息互相覆盖,已读状态错乱,严重时出现重复支付回调。
排查路径:用脚本同时从两个设备登录同一账号,各自执行下单、改资料、消息发送等操作,观察服务端会话管理和数据一致性。
修复建议:服务端维护会话版本号,新设备登录后给旧设备下发强制下线或“该账号已在其他设备登录”的通知;在同一账号的写操作入口加分布式锁,防止同一用户并发改动同一份数据。
5.6 手机内存爆红:应用不是唯一还活着的App
场景:手机内存仅剩200MB,后台有微信、导航、音乐等多个应用,用户强行打开我们的App,进出几个大图页面后直接卡死。
现象:图片加载库在解码高清图时一次性分配了过多内存,没有复用池机制;列表滑动时旧位图没有及时回收,导致最终OOM崩溃。
排查路径:用adb shell am hang把内存压低,然后进入图片瀑布流,快速滑动。用Android Profiler看Java堆和Native内存曲线,重点关注图片解码后的位图大小。
修复建议:图片库统一走采样缩放,比例为显示尺寸的1.5倍即可,不要全尺寸原图解码;列表图片开启复用池;进入低内存提示时,主动降级到低清晰度图,同时清理无用的缓存引用。
5.7 权限全部拒绝:没有相机、相册、定位,还想拍照
场景:用户手滑把权限全部拒绝,或者用了“仅本次允许”,之后点位恢复App进入拍照页,点击拍照直接崩溃。
现象:代码里直接调用Camera开启操作,没有检查权限的持久授权状态;或者相册选择器回调里,拿到的URI为空,没有做空指针处理。
排查路径:清空App权限,改为拒绝所有权限,然后逐个页面打开,操作每一个涉及敏感权限的功能。再测一次“授权后立即撤销授权,回到页面再操作”的场景。
修复建议:所有权限相关操作统一封装,在入口处检查授权状态,未授权就展示系统设置页或引导弹窗,不能再假定“用户一定给了权限”;对相册、相机类回调的URI参数,全部加空值兜底。
6. 踩坑实录:模拟愤怒时,工具比用户更容易破防
6.1 自动化脚本的“温顺”陷阱
第一次跑愤怒模式,我满怀期待地看到一个崩溃,结果一查崩溃堆栈,发现是自动化脚本自己点到了系统的“开发者选项”设置项。原因很简单:adb tap是绝对坐标点击,没有考虑屏幕分辨率适配,也没有验证目标控件是否真的在当前页面存在。
解决方法:如果必须用adb tap,启动时先读取当前系统的宽高,把点击目标用相对坐标计算;或者改用uiautomator dump去解析控件位置。更稳定的方案是优先用UI自动化的控件定位,只有在控件定位不到的时候才退回坐标点击。
我还有一个习惯:每跑完一套脚本,先用截图确认脚本执行到了期望页面,而不是脚本自己把App带到了某个未知页面。一旦发现脚本在到处乱点,第一件事不是修业务代码,而是先修正脚本逻辑,不让无效测例污染结果。
6.2 真机环境稳定性:设备先成为瓶颈
垃圾堆里淘出来的测试真机,往往在跑第一个场景时就过热降频,屏幕自动锁死,脚本跟着卡死。这种情况下的测试结果不能代表真实用户场景,因为真实用户不会拿一块烧烫的屏幕去戳应用。
我的做法是准备一台“专机”专门来跑愤怒模式,系统尽量干净,关闭一切应用商店自更新,充电保持,屏幕常亮。同时用设备群时,每跑一个场景就清理一次后台进程和缓存,保证系统状态回到基线。用手机分身或双开方案来替代真机多开,只能用于功能测试,不适合愤怒模式这样的资源竞争测试。
6.3 数据污染与还原策略
愤怒模式会输入一堆垃圾数据、创建一堆重复订单、上传一堆半截文件。如果不做数据还原,跑完几轮之后,App里的数据状态已经面目全非,后面的测试结果就会失真,甚至出现“因为脏数据导致崩溃”而被误报为产品质量问题的情形。
我规定每一轮完整测试结束后,必须执行一套数据还原流程:重置应用数据,清空本地缓存,删除测试环境里创建的订单和文件。后端也要定期清理测试造成的脏数据,不然测试环境迟早变成一个黑洞,影响到其他团队。
6.4 别把愤怒模式放在深夜无人值守时运行
听起来在深夜里让脚本自己跑,第二天来看结果,很高效率。但我的亲身体会是:当脚本在凌晨三点卡死,没人去处理,后面的所有场景全部白跑,还占了一晚上的设备资源。等到第二天上班看到“大量崩溃”,结果一查是脚本自己卡在某个弹窗上,重复同一个动作八个小时,那是又好笑又气人。
愤怒模式需要有人在现场盯着,至少需要每隔半小时看一眼日志和截图。我的安排是:白天工作时段跑主链路P0,晚间只跑P1和P2的回归集,并且设置“异常自恢复”机制——一旦脚本连续多次失败,自动重启App、清理缓存、重新开始下一轮。
7. 让愤怒模式从测试工具变成质量护城河
7.1 把用户工单变成新的“愤怒种子”
愤怒模式不能只靠测试团队拍脑袋设计场景,还要持续从线上吸取新词汇。我每个月会把用户工单里涉及崩溃、闪退、无法操作的内容拿出来,按照标题、现象、操作路径排序,从中挑选出现频率最高的前二十个,把它们翻译成新的愤怒测试用例,加进回归集里。
每一条新增的愤怒用例,我都要求写明它源自哪一期用户反馈。等于说,产品在线上被捶一次,测试集就多一个护城河,下次上线前先被自己捶一遍。
7.2 给“不愤怒”定义一个可以量化的基线
愤怒模式跑完,不能只说一句“还没崩,还行”。必须定义具体的通过标准。我给团队的建议是:
- P0场景连续三轮零崩溃、零数据错乱
- P1场景单轮崩溃数不超过1次,且无中等以上系统的卡顿(帧率低于10帧持续超过2秒记为卡顿)
- P2场景允许出现非核心功能的部分不可用,但应用不能直接闪退
- 所有弱网场景下必须有可读的文案提示,用户不能面对一个空白页面和转圈圈干瞪眼
有了基线,测试报告才有说服力,研发和测试才能对齐“什么叫过得去”。
7.3 把愤怒模式嵌入发布闸门
一款产品就算单次测试通过,也不能保证以后没有回归。所以愤怒模式必须跟CI/CD结合:每次提测前、合入主干前、发版前,跑一套精简版的P0+P1组合;每周跑一轮全量的P0+P1+P2。一旦出现崩溃或者数据错乱,自动阻断发布流程。
这套机制的落地工程量并不大,核心就是把跑脚本的入口做成命令行,丢进流水线即可。不存在“没有时间测”的借口,因为它是机器跑的,只不过每轮任务需要分配机器和时长。
7.4 全员“破防日”:让产品经理和研发一起体验
愤怒模式最有意思的副产品,是它能让原本只看代码和原型的人,真正体会到用户为什么骂人。我每季度组织一次所谓的“破防日”,把测试脚本跑出来的复现操作组合成一份“愤怒SOP”,让研发和产品经理手动照着操作一遍。亲自感受那种点了三次没反应、转了五秒没结果、切换后台回来丢了数据的感觉之后,不用再多说一句,需求评审时他们自己就会主动去问“这个操作如果快速连点会怎样”。
这个认可度比任何质量指标都有效。
我现在做任何一个新产品,第一周不是写功能代码,而是先按这套思路搭好愤怒模式的基础脚本。因为我越来越相信,功能的正确完成只是起点,真正把产品从“能用”推向“耐用”的,是那个始终举着放大镜等着你崩溃的人。与其等用户来当这个坏脾气角色,不如自己先请一个“愤怒机器人”,让它在每一次发布之前先把所有难听的话都骂一遍。