性能预算要在设计之前定下,而不是等到被投诉之后
把性能当作一个优化阶段,它永远会输给功能。把它当作设计开始前就已达成的约束,它会悄悄地、免费地带来更好的决策。
几乎每个团队都说性能重要。也几乎每个团队都把它排在接近尾声的某个阶段,在那里它和"按时发布"竞争,然后输掉。
最终做出快产品的团队做的是另一件事,而且不是什么英雄式的优化。他们在任何人打开设计工具之前,就先定义清楚"够快"是什么意思,然后像对待浏览器支持矩阵那样对待这个数字:把它当作约束,不是目标。
"以后再优化"为什么失败
三个原因,而第三个才是真正击垮你的那个。
- 没有东西强迫做取舍。 单独看,每个功能都值它的成本。没有预算就没有东西会说"不",加总起来就是慢。
- 便宜的修法会用完。 压缩、缓存和图片格式只能买给你一份固定而有限的额度。之后你就进入架构地带了。
- 昂贵的成因都属于架构。 过度取数的数据层、阻塞在网络上的渲染路径、位于关键路径上的第三方脚本。这些都在早期决定,到后期拆除代价极高。
等你能测量的时候,决定结果的那些决策已经是几个月前的事了。
预算长什么样
预算是少数几条具体的限制,一次构建可以通过或不通过。以我们给 KoiSpeak 查询功能定的为例:
| 指标 | 预算 | 理由 |
|---|---|---|
| 首个结果出现时间 | 低于 150 毫秒 | 人们在说话说到一半时打开词典。超过这个数就有等待感。 |
| Largest Contentful Paint | 中端手机上低于 2.5 秒 | "良好"的常见阈值。 |
| Interaction to Next Paint | 低于 200 毫秒 | 打字绝不能有卡顿感。 |
| 发送的 JavaScript 体积 | 硬上限,按路由分别设定 | 唯一能预测其他所有数字的那个数字。 |
有两个性质让预算成为真的,而不是装饰:
- 它是数字,不是形容词。 "快"没法让构建失败。150 毫秒可以。
- 它在你用户真正拥有的设备上测量。 在开发者笔记本上通过办公室 wifi 测出来的预算,什么也没测。
在真实存在的硬件上测试
这是大多数性能工作在开始之前就跑偏的地方。
开发发生在快机器、快网络上。你的用户在几年前的中端安卓手机上,用着移动数据,后台还挂着十几个应用。这两种体验之间的差距不是 20%。在 CPU 密集的工作上,它经常是 5 到 10 倍。
实际的最低要求:
- 桌上放一台真正的中端设备,每周都用。
- 默认打开开发者工具里的 CPU 降频,而不是当成一次特别演练。
- 采集真实会话的现场数据,而不只是实验室数字。实验室告诉你什么变了;现场告诉你用户实际拿到了什么。
预算如何改变决策
价值不在于测量,而在于它在任何东西被造出来之前,就把那些争论早早地、便宜地解决掉。
- 一套字体变成一笔成本。 四个字重加两个字族,在移动端预算里是实打实的一块。你会在设计阶段就改成两个字重,毫无戏剧性。
- 一个动画库必须自证其值, 与你本来会手写的那点 CSS 相比。
- 统计脚本和客服挂件会被计入。 第三方脚本常常是最大的一项,而没人注意到,因为团队里没人写过它们。
- 数据结构跟着预算走。 如果首个结果必须在 150 毫秒内落地,你就不能拉一大包数据再在客户端过滤。这会把你推向前缀索引,而那本来就是正确答案。
最后这条就是那个模式。预算不会拖慢你;它让正确的架构更早地显而易见。
发布之后再做的性能工作是一次救援行动。从一开始就被当作约束的性能,只不过是设计。
不搞仪式地执行它
没人检查的预算只是一个愿望。让执行保持廉价:
- 体积超标就让构建失败。 一行 CI 配置,抓住大部分回退,维护成本为零。
- 每个 pull request 跑一次合成审计, 只针对最重要的两三个路由。
- 按周对现场指标告警, 而不是按每次提交。真实用户数据噪声大;看趋势。
- 产品变化时重新审视预算。 预算不神圣。带着理由、有意识地把它调高是可以的。悄无声息地漂移过去则不行。
把预算花在哪里
预算讲的是分配,不是极简。有些东西配得上它们的重量:
- 关键路径。 用户为之而来的东西必须排在最前,其余的可以等。词典是搜索框和结果。商店是产品图和价格。
- 感知速度优先于测量速度。 立刻显示结果骨架,常常胜过把响应再快 40 毫秒。
- 输入的即时反馈。 对一次按键在约 100 毫秒内作出反应会被感知为即时。超过大约 300 毫秒,人们就会注意到。
让人不舒服的那部分
真正的预算意味着要对那些单独看都很好的东西说不。这正是它奏效的原因,也是团队悄悄放弃预算的原因:当它第一次挡住某人想要的功能时,最省事的做法就是把预算调高。
纪律不在定下数字的那一刻,而在你守住这个数字的那场会议里。