本文主要介绍我们是如何处理前端线上问题的,希望能够帮助到大家。
文章先介绍常见的线上问题以及对应的解决方案,其次阐述了我们对整个项目流程做了哪些优化。
线上问题分类
线上问题多种多样,这里将分门别类介绍给大家。
无兜底
无兜底有很多种,包括但不限于:
•对象为 null 或 undefined 报错,常见于根据接口渲染页面
javascript
Object.keys(obj); // 需要为 obj 增加兜底值 {},即 obj || {}
•数组为 null/undefined 报错,常见于根据接口渲染页面
c
data.map(() => {...}) // 需要为 data 增加兜底值 [],即 data || []
•对 null/undefined 解构报错
arduino
const { name } = obj; // 需要为 obj 增加兜底值 {},即 obj || {}
•页面中某个字段显示为 null(可能是接口返回的)
css
<div>
<div>价格:</div>
<div>{data.count || 0}</div> {/* 增加兜底文案,比如短横杠 '-',0 等 */}
</div>
•图片加载失败,显示错误图片
ini
{/* 使用兜底图替代;在图片的 onError 事件里也添加上兜底图片 */}
<img src={data.img || defaultImg} alt="" onError={handleError} />
•接口返回的链接为 null
javascript
{/* 链接为 null 时处理成点击不跳转 */}
{res.link ? <div onClick={() => (location.href = res.link)}>xxx</div> : <div>xxx</div>}
•接口某些字段返回 null,前端又没设置容器尺寸,导致样式错乱,应该提前为容器设置尺寸占位
无兜底场景远远不止这些,不再一一列举。根据以往经验,兜底宁多勿少,否则轻则样式走样,重则页面白屏,引起客诉。
变量为空判断
这个问题非常非常常见,也很容易被遗忘,最高效的做法就是为变量增加 ?.
?. 是 ES6 提供的链判断运算符,避免报错,变量不存在时返回 undefined
kotlin
res?.name // obj 是接口返回的对象(map),可能是 null
data?.length // arr 是接口返回的数组(list),可能是 null
兼容性
兼容性一直是前端同学诟病的一个问题,虽然早期的 IE 已经退出历史舞台,但随着移动端的兴起,低版本的手机浏览器又成了大家吐槽的对象,所以兼容性还是不能视而不见的。这里举几个我们遇见的例子:
•JS:location,在低版本浏览,如 Android 8.0 中会"找不到此对象"报错,需要增加 window.location
•JS:Promise.allSettled,在 67 以下的 Chrome 浏览器中无法适配,需要增加降级方法
•CSS:nth-last-child,不兼容 IE11,对于需要兼容 IE 的项目需要注意
查看 API 兼容性可戳 caniuse.com/
体验
前端是直接面向客户的,用户体验至关重要,体验做的不好,很有可能会造成客诉,比如:
•一个列表耗时 10+ 秒甚至更久也没加载出来,页面也没有"加载中"提示,客户会以为网站出错了,直接投诉,事实上是请求接口太慢,如果增加个 loading 提示,就不会让客户误会了。
•接口有错误信息返回时,页面无提示,客户就会很疑惑,极有可能投诉。一般的解决方案是,在统一封装的接口请求方法中,集中处理错误提示。
架构设计
•自动化配置预发和生产接口域名,即生产环境接口使用生产域名,预发环境接口使用预发域名
•调试工具 vConsole 等,切勿带到生产环境中
减少影响范围
减小影响范围,会间接降低线上事故率。
•使用 react-error-boundary
react-error-boundary 是 React 一个很香的发明,用过的都说好,他能保证页面不会因为某个模块出错而无法访问。Vue 中对应的是 errorCaptured 生命周期。
•内嵌系统的处理
有些项目是被内嵌到其他项目中使用的,被嵌入的项目存在差异化,包括主题、功能等,当其中某个项目有需求变更时,就要考虑是否会影响其他项目。
我们的解决方案是对各个被接入的项目进行归类,每类都新建不同的文件或路由,实现解耦,当某一类有需求变更时,只修改相应分类的文件。如果需求涉及到多个类,可以再抽离出公共小组件。
其他
剩下的我都放在其他里了。
•一个项目有人员变更再正常不过了,恰好以前的代码逻辑又非常复杂,各种 if else,根本不敢动。假如有新的需求,最稳妥的方法是新增判断分支,而不是修改以前的分支,除非你有十分的把握,否则改一下就是个线上事故。
•需要展示一段文本时,要有文本溢出的敏感度,因为文本可能只有英文字母或数字,需要添加额外的样式处理:
kotlin
p {
word-break: break-all
}
•节流&防抖
文本框输入,表单提交时要形成条件反射,考虑是否需要增加防抖。
监听 scroll 或 drag 事件时也要问问自己,是否需要增加节流。
•封装的必要性
记得在某个项目中,有段代码在不同文件中被重复写了 3 次,在一次需求中,恰好需要修改这些代码,但是研发只识别到了两处,结果偏偏用户就命中了第三处,引起了客诉。试想,如果把这三处封装在一起,不就很容易避免这次事故了吗?
流程优化
流程优化贯穿整个开发流程。
重视 CR
CR(Code Review) 能提前发现很多非业务问题,比如是否增加了兜底,是否增加了防抖、节流等。
CR 的时间建议安排在提测后。
建议每个团队都输出自己的 CR 规范,既统一了大家的代码风格,又能节省 CR 时间,更重要的是能避免一批线上问题。
前期做好监控
监控能让研发对问题先知先觉,有效避免很多客诉,90% 的问题都应该通过监控发现。
监控平台可配置以下告警:
•用户性能
•API 请求
•JSError
•资源
•自定义监控
•白屏
测试
•测试已发布静态资源
正常情况下,项目的测试工作都会在预发环境上进行,预发环境和生产环境有一模一样的配置,目的就是严格模拟生产环境,降低真正上线后的风险。
单页应用的上线顺序,一般是先发布静态资源,然后再修改模版(html)里的静态资源版本号。
这里建议大家,发布完静态资源后,在预发环境上测试下线上的静态资源,提前发现一些潜在问题。
•自测
自测是开发人员的基本素质,不自测就交给测试童鞋是可耻的。自测能发现测试童鞋遗漏的问题,自测能发现和修复代码中的错误、问题和潜在的缺陷,有效避免线上问题,所以要增加自测的重视度。
回滚
一旦遇到线上问题,第一时间就是回滚,回滚,回滚,重要事情说三遍。不要着急去找问题原因,很多同学都会陷入这种惯性思维,可能 Ta 们感觉很快就能找到问题原因吧,但是往往很多时候都事与愿违,造成很多客诉,甚至损失,最后不得已再回滚。既然如此,何必当初,早回滚不得了,要分清事情的轻重缓急。
对照现实中的例子,假如你看到你的朋友和陌生人发生了肢体冲突,被打的头破血流,你第一时间会去问他们发生冲突的原因吗?肯定不会呀,相反你会先把你朋友送到医院,等确定他脱离危险后,才会再追问他们冲突的原因。因为你知道,你朋友的命更重要。
同样的道理,现实中很清醒,到了工作中为啥就糊涂了呢?
复盘
复盘是一种很好的习惯,既能分享成功的经验,又能总结失败的教训。
出现了线上事故并不可怕,可怕的是反反复复。为了避免团队内部类似事情的重复发生,团队 leader 需要及时组织大家一起复盘,最好约个线下会议室,让大家都详细的了解事情的来龙去脉,才能在一定程度上有效的避免这类错误。会议结束后,也要做好会议纪要,以备警醒。
最后,非常期待大家在评论区也晒出你们遇到的前端线上问题,共同学习,让线上环境更稳定。