衡量质量的常用指标
质量保障的基本要义,就是确保各项指标长期维持在合格线以上。具体操作上,一般由 QA 团队定期整理,公开发布。
线上故障需进行分级管理,定级标准主要看影响面和影响程度。比较重大的故障,通常有两种类型:一是影响了核心指标,如单量、交易额等,二是阻塞主流程,比如某个核心功能无法使用。
还有两类故障需要格外关注:一是低级错误,比如小数运算不考虑精度,这会严重影响技术团队的声誉和专业可信度;二是重复错误,同类问题再次发生,说明上次发生之后的复盘和后续保障动作没有做到位。
对于线上故障,技术团队应该定期复盘,研讨学习,管理者应该将其作为质量保证的长期管理手段,提高团队的质量意识和排查技能。
技术故障是指像按钮点不动、js 报错、接口报错、图片不展示、页面白屏、服务器宕机、接口请求死循环等纯因技术问题导致的故障;逻辑故障是指非技术的业务逻辑故障,即一切技术指标都正常,代码运行什么错都没有,但功能表现和执行结果不合逻辑或不符合预期,例如明明输入了手机号,却提示手机号不能为空。
技术异常通常能够被监控发现,而逻辑异常一般很难靠监控发现,技术手段上一般是靠自动化测试发现,或者靠人工深入到业务逻辑中主动埋点监控(例如监控订单总额,如果小于 0,就上报异常)。
内部故障是指团队负责范围内发生的故障,故障修复职责在己方。除了内部故障,其他的都是外部故障,也就是外部依赖发生的故障,需要由其他团队解决。
外部依赖,对业务方而言,不妨称之为供应链。对前端来说,供应链主要包括网络、客户端/容器、桥方法、接口、前端框架、各类第三方 sdk 等等。
任何外部依赖问题,其实也是内部问题,反映出对外部依赖的不了解、技术选型不合理、对供应链的风险评估与监控有缺失。
img
质量保障是一个系统化工程,从开发周期上,可划分为事前、事中、事后三个阶段,事前重【预防】,事中重【检测】,事后重【监控】;从保障手段上,可划分为四个方面:基建、测试、风控和管理。接下来本文将分阶段讲述其中的几个重点事项。
质量保障最基础的措施是测试。所以测试环境、测试工具、测试平台服务是否给力,对于质量保障很重要。比如兼容性问题,如果有工具能一键驱动几十台机子渲染某个页面,截图比对,直接生成报告,标出问题机型,那这样兼容问题的发现基本就不用人操心了。再比如自动化测试,如果能方便的录制流程,无需人工写代码驱动,那么回归的覆盖面和效率都会大大提升(比如网易的 airtest)。
技术架构标准化是指有约束力的最佳实践。最佳实践的两个核心利益点是:开发效率、开发质量。也就是说一种经验能否算作最佳实践的基本门槛,是看它能不能提高效率,以及是否有利于质量保障。
技术架构标准化之所以能保障质量,主要原理是预防甚至消解导致故障的基本隐患。软件开发领域的一个元问题,是规模。简单、小规模的软件就不容易出问题,也就不需要太多工程化手段。规模是现实需求决定的,也即技术不可控的,可控的是技术实现的复杂度。为什么用框架、组件化、mvvm,核心原因是降低复杂度。
约束开发模式,统一团队开发风格,也是为了控制复杂度,因为每个人的个性设计杂糅在一起会带来额外的复杂度。
除了控制复杂度,最佳实践的另一个主要目的是预防故障,避免重复踩坑。这些开发实践中总结出的一系列经验(比如:前端业务开发的通用经验 - JavaScript 篇),需要用技术手段自动检验(比如静态扫描),形成约束力,才能成为技术架构的一部分。
一个页面到底都依赖啥?通常一个业务时间一久,维护的人换过一茬,基本就不容易搞清楚了。通用的依赖一般来说有:CDN、网络、容器、接口、基础框架/库、sdk(埋点、监控之类的)、打包工具等等。
每个项目都需要盘清依赖项,明确边界。关键有两点:第一,依赖是否可靠,比如出问题了能否找到维护方;第二,依赖是否彻底解耦,比如某些配置不应该跨项目或者跨团队存在,否则会相互中伤。
针对每一种依赖,还需要一些防范和监控措施。
CDN 的一个主要问题是节点故障,该问题的一种常见表现是:某个手机上请求静态资源耗时很长,但是切换下网络,发现就好了,再切回去,还是耗时很长,其它手机一切正常。说明有可能是某个 CDN 节点发生了故障,而因为 DNS 缓存的缘故,出问题的手机可以稳定复现这个问题,清缓存后问题可能就不再复现。监控这类问题的基本策略是拿到所有资源的加载时长,如果超过阈值,则上报。
网络的一个主要问题是劫持。比如这种场景:因为 js 静态资源是单独部署的,因此需要解决跨域问题,script 标签上要加 crossorigin=anonymous,响应头要带 Access-Control-Allow-Origin: *。资源被劫持后,响应没有带这个头,就会导致资源因跨域而被浏览器拒绝执行,页面肯定就挂了。监控劫持的一种思路是 IP 白名单,另一种思路是 hash 校验,参考 Subresource Integrity。
Webview 可能出各种问题,举个我遇到过的例子:h5 页面自适应设计依赖 rem,rem 的计算需要取页面宽度,这段 js 通常会放到 <header>
标签内执行。然后因为容器背后的预加载逻辑有问题,导致 html 解析到 rem 计算脚本的时候,取出的页面宽度是 0,相当于容器页面还没有初始化好,但 html 已经开始执行了。结果就是所有以 rem 为单位的尺寸,实际都等于 0px,页面就白屏了。那这怎么监控呢?基本思路是从 html 元素开始递归遍历每个元素,用 getBoundingClientRect 算出尺寸,如果发现所有元素都没有尺寸,就证明白屏了(细节上还要考虑 display、visibility、opacity 等问题),发现白屏,就把整个 dom 文档报上去(document.documentElement.innerHTML)。很暴力吧,不过上述问题就是通过这种方式发现的(发现 html 标签的 font-size 为 0)。
容器提供的桥方法,业务方最好做一次封装,统一处理异常上报。
接口异常一般有三类:网络异常(5xx、超时);业务异常(一般用自定义状态码标识);数据结构问题(缺字段、数据不合法等)。
网络异常监控主要是通过网络库的能力,业务异常和数据结构异则需通过代码监控。举个数据异常的例子:后端某天发现一个接口的经纬度写反了(显然前端也是反的,所以功能没问题),于是单方面修改后,悄悄上线,上线后没实际在 app 上测,自以为问题修复了,那肯定就出问题了。这种情况,前端可以对经纬度的范围做下校验,有问题就报出来,这就是主动监控。
除了异常监控,还需要考虑对接口的调用量进行监控,调用量的骤增或骤降其实也是一种异常。我遇到过因前端死循环导致接口调用量上涨几千倍的案例,就是因为缺调用量监控,故障持续了整整一天才被发现。不过后端只能对总量做监控,如果只是小样本用户遇到死循环不断调接口,不一定能触发后端的监控报警,因此前端最好主动监控下接口短时间重复调用的情况。
我从来没有遇到过 vue 和 react 本身的 bug,只能说用得晚而浅吧。一般通用框架的质量还是很可靠的,毕竟有那么多人在用(当测试)。框架也提供了一些异常处理机制,需要利用起来,比如 vue 的全局配置方法 errorHandler 和 react 的 ErrorBoundry 组件。
引入第三方库,原则上需要锁定版本,不要完全指望开发者遵循 semver 版本语义。
我们引入的 sdk 也是可能出问题的。像监控类的 sdk 必须先加载,且必须是同步加载,可考虑内联,避免网络加载失败(网络成功率不会是百分之百,一般 99.9% 上下)。然后一定要控制版本。
举个我见过的例子,某 sdk 内部有一个上报逻辑,为减小代码体积,自己手写了 ajax 方法,但忘了设置异步,搞成了同步,而容器不允许同步网路请求,就崩了,这种业务方除了猛怼似乎也没什么辙。sdk 的质量必须得由服务方保障。所以就需要明确:依赖的服务得找得到负责人。
像 webpack 这样通用的工具也会出问题吗?一般工具本身的 bug 不容易遇到,常见的是用法导致的 bug。比如打包出的代码包含 es6 导致兼容问题(没错,2020 年安卓 5.x 仍有不小的用户量),那显然是某个环节出问题了(比如 import 了 node_module 里未经编译的 js),还有像我之前用 tapable.js 导致 es6 代码被引入,都莫法编译:
img
所以说选择外部依赖时,要么是一个通用依赖,有很多人在用,并且跟你是同一种用法(tapable 是一个通用依赖,但很少有人直接使用),要么非常清楚背后的代码实现,再不然就得靠全面的测试。否则贸然引入就会有风险。
针对打包环节,一般需要的监控手段有:
日志覆盖不全,意味着监控有漏洞。但也不是越多越好,增加流量消耗,浪费服务器资源。
首先是做分类和分级,主要目的是有助于针对性的配置报警,比如可以针对某个页面的 x 类问题的 x 级别配置一个报警策略。
手动上报的日志名称,最好和自动上报的日志名有所区分(例如统一加个前缀),同时带上标记,例如页面名、接口路径、方法名,好处是一目了然。日志的参数内容,应该尽可能包含问题排查所需的必要信息(通用的如客户端型号、系统版本、网络类型、地理位置等等)。
很多时候,技术方案都是后端主导,毕竟业务逻辑主要在后端,前端只是负责展示,而且后端涉及库表、缓存、接口、系统调用关系、逻辑流等,很容易画出一堆图来讲,前端要是没个明确指南,技术方案往往不知道写啥。
一般估时超过一定值的有规模的需求,需要书面的技术方案和评审环节,尤其是涉及多人分工开发的场景。做技术方案主要目的有三个:
提前想清楚关键问题,比如:
影响范围:本次需求波及的改动范围,漏掉的话排期肯定就不准,QA 也特别关心改动范围,因为这决定了 QA 的测试范围
依赖资源:要完成本次需求,哪些必备功能需要外部提供。需要明确这些功能是否可靠(可能需提前调研)、如果没有能否及时提供、无法及时提供又怎么办
数据来源:展示层所需的所有数据都来自哪里,放到哪里或由谁提供才合适
核心逻辑:用图表说话
接口方案:有时候接口前端定比较好,毕竟作为需求方最知道自己需要啥
兜底方案:假设这个功能出问题了,有没有备份方案,例如开关、降级等手段
监控方案:通过什么样的方法能知道我这个功能上线后是有问题还是没问题。这个其实很考验水平,一定程度也决定了自动化测试的设计用例,主动监控和用例设计某种程度上是一回事
测试方案:需要准备哪些条件通过哪些过程达到哪些预期才能证明我的功能是没问题的。如果在需求开始前,能在脑子里模拟出整个功能的逻辑实现过程,发现其中的关键点和风险点,乃至倒推出需求设计的不合理处,其实很能体现做事的水平。有时候经验不足的开发,可能开发完后,才发现好些功能很难测甚至没法测。如果能尽早预见,就可以调整技术方案,或者增加估时。免得提测后,还手忙脚乱
上线方案:主要关心谁先谁后,出问题了回滚顺序如何,还有避开封禁期,因为确实出现过灰度发布到一半结果刚好抵达封禁时间,剩下一半发不了的情况(虽说应算作发布系统的问题)
排期的依据:大型需求只有经过拆解才可能给出准确排期
同行把关:从团队管理角度,一般在各个细分领域有至少主备两人(或多人)专门负责。评审时视情况将涉及模块的负责人拉进来,协助确定方案,以便于提前发现问题
如果是多人协作开发的需求,需要仔细划分,避免相互产生依赖,不能避免则要明确依赖关系。各自负责的范围也最好有明确边界,否则代码容易出现冲突。解冲突其实很容易出问题,凡是依赖人的谨慎与仔细之处,都容易出问题。
技术方案完成后,需要组织评审。评审的一个重要功能是需求再确认,同时也为后续测试方案提供依据,所以产品和 QA 一般都要参加。技术评审需要注意哪些是内部问题,哪些是公共问题。内部问题内部消化,避免在多方参与的会议上讨论内部问题。比如我多次遇到过会上几个后端在争论内部的技术细节问题,前端、产品、QA 都不知道他们在说啥,这样的会议效率就特别低。
啰嗦一句开会这事儿,原则上如果一个人只需听两分钟的内容,那他就不应该参加会议,最好由会议主持私下单独沟通。像著名产品经理纯银就倾向于开小会,哪怕多同步两次。这个比较依赖人的素质,多数人不懂怎么开会但又不得不组织各种评审会议,如果你被迫加入这样的会议,那听完两分钟就应该走人,别不好意思。
SOP(标准操作程序)是一种涉及多环节的办事指南,SOP 应该达到的标准是任何新人看着文档就能一步步完成整个操作。每个团队 wiki 里,都应该有个专门的目录,记录各类 SOP。SOP 其实是贯穿所有环节的,比如上线 SOP、下线 SOP、线上故障处理 SOP、值班 SOP、运营活动配置 SOP 等等。
有人可能觉得这咋是开发需要关注的事情呢?而且这事儿跟质量保障有毛关系?
通常做开发的人都习惯于执行,而不善于质疑。可能是因为公司未建立起相应的制度或共识,不过【对需求有判断力】的确是个比较高的要求。个人认为作为研发至少得主动争取表达意见,不表达就不可能有话语权,否则产品一句话,你加班仨小时,能忍?有些大厂就很明确,讲不清楚需求的收益,需求评审未通过,研发有权利拒绝执行。这就倒逼产品必须想清楚,说明白。理论上,不做需求就没有 bug 对不?做经过深思的需求,中途不要瞎改,相比起需求频繁变更,出 bug 的几率更小对不?
此外,需求的规模也是一个问题,这里要分情况,有时是客观现实决定一个需求就是很大,有时是因为需求拆分不合理,把多个需求揉到一起导致一个需求显得很大,有时是因为解决方案超出了“问题和目标”所定义的范围,夹带了太多“私货”。
需求规模过大,会带来一系列问题,导致出问题的概率增大:
对研发而言,需求管理可以做的事情包括:
涉及多个需求的整体管理问题,一般需要 TL 关注,比如需求间是否存在耦合关系,时间安排是否合理,优先级谁先谁后等。比如这样一种场景:关联需求分两拨人同时做,其实是有风险的,假设 a 依赖 b 的某功能,b 也依赖 a 的某功能,必然只能一起测试、一起上线,否则就死锁了对不。但这么做中间沟通协调的成本就比较高,出问题的概率也比较大。
如果需求间出现了重复,甚至冲突,那就是产品内部管理的事故了,TL 之间就需要友好沟通一番。
发错分支,或者本地 master 分支合入了一些测试代码然后不小心推到远程,这些绝对出现过不止一次。分支管理必须通过代码管理平台从技术上限制:
封禁期是一种风控手段,避免节假日上线,除了可能因用户量增加放大故障的后果,还有就是避免因休假无人修 bug,也算是对员工的一种保护吧。不过有时会出现为了赶时间多个上线堆积到封禁期前一天的情况,这也是风险,需管理者注意。
灰度发布主要是避免一次性上线,导致潜在故障也跟着瞬间全量。在逐步开量的过程中如果发现问题,可以及时中止回退,从而降低影响和损失。灰度发布需要注意比例控制,比如单纯因为谨慎,第一阶段只开很小的量,考虑到监控阈值都是按全量配置的,往往因量太小而无法及时暴露问题。
渐进式是一种基础理念,不仅体现在发布过程,也体现在日常做事中。像我入行一年多终于进了大厂,刚进去就干了件非常愣的事:把 webpack 1 升到 3,以解决编译速度等问题。因为项目有一定规模,吭哧干了一星期,一个 pr 改动文件好几百个,甚至都超出了代码管理平台的 diff 上限。这么大的改动,没有 QA 测试,自己测了几遍,就直接上线了。结果就出了一个偶现问题(特定条件下发生),这就懵了吧,几百个文件 diff,是哪个改动导致的呢?如果分成几批渐进式改造,不仅降低风险,还有助于事后定位问题。
估计好多团队都不搞 Code Review,从实践来看,通过 Code Review 发现问题的比例,的确很少,因为太依赖人的素质了。不过 Code Review 仍有必要性,首先低阶新人的代码必须 Review;其次,Code Review 是代码质量保障的一种手段,每个人都在闹,业务代码是坨翔,那 Code Review 的时候干嘛去了呢;第三,可作为团队内部技术交流的一种方式,互相学习编码经验。所以从机制建设上看,Code Review 也许某阶段做不好,但不能没有。不好可以改进,没有,那整个技术管理机制就太弱了。
为了提高 Code Review 的可行性和有效性,贺师俊在这篇文章里提到一点:限制单个 pr 的粒度。也有一定道理,不过操作成本略高,视团队情况而定吧。
一般需要 Review 的内容:
重点强调:第一版代码(新增页面、新增业务组件)必须严格 review,一个变量都不能放过。因为第一版代码奠定了某种基础,如果设计上存在问题,后续迭代大概率会在错误的方向上越走越远,到后面每加点新东西都举步维艰。想重构优化,改动范围、回归成本都成倍放大,极其痛苦。第一版代码一定要找最靠谱的人写,然后重点 review。初期看可改可不改的问题,只要有改的理由(哪怕只是潜在风险)都应该改,不要小看破窗效应。
参考:Google Code Review Developer Guide
Review 有两种形式,一种是提 PR 时 review,一种是定期复盘型 review。提 PR 一般是提测的同时就要提出来,等上线前提就晚了,一是审核人不好提意见,会因顾虑阻塞上线而被迫 approve,二是就算提了意见,改还是不改,改了要不要测?所以 PR 提太晚,Code Review 就是无效的。
Code Review 在操作上一般还会面临一些问题:
解决责任心问题,主要机制是 reviewer 一起担责,如果事后出问题,复盘时判定这个问题应该可以在 review 时发现却因没有仔细 review 而被忽略,那么 reviewer 是需要承担一部分责任的。当然,有责任就有权益,approve 的数量和 comment 的数量都可以算作研发的贡献。
监控系统的责任,是尽快发现问题。要做好这点,有两个基本前提:
全
监控的覆盖面全,能尽可能多的捕获到问题
记录的信息全,对问题的描述足够充分,能为后续定位问题的原因提供足够的信息
准:报警准确,误报过多会降低感知问题的敏锐性
img
监控系统做得比较深入,可以做很多功能,比如页面回放,直接复现用户的操作过程。
参考:浅谈前端监控
前端习惯了完全动态化,随时上线,无版本概念的开发模式,有时无法体会兜底措施的重要性。
像 native 这种缺热修复能力,也没办法回滚,一发版就没后悔药的,各个重点功能都必须考虑备份方案,出问题了可自动降级,或通过开关控制,关闭入口。通常大厂 APP 都有专门的配置下发方案。前端可以利用 json 配置平台,或者接口,或者最粗陋的,直接扔个 json 文件到服务器,借此实现远程开关控制。
拆解事故,不为追责;看清根因,逐步提升。
处理故障,如何在最短时间内止损、定位原因、修复上限,非常考验水平和经验。总结下来,有一系列标准化动作供参考:
Step 0 明确问题
首先是明确问题的严重性。如果问题很严重,需要将周知对象上升到更高级别管理者。原因是:更高级别的管理者本身有更多的经验,同时可以调动更多的资源,有助于尽快解决问题。毕竟线上故障,往往优先级最高,快速解决问题是最高目标。如果因为没有及时上报,而是自己硬抗,导致故障处理时机延宕,那可就有责任了。
除了判断严重性,还有就是要搞清楚真正的问题究竟是什么,避免后续排查出现方向性错误。尤其是非技术人员反馈的问题,很多时候不见得能描述清楚,可以要求截图或视频佐证,最好还是自己能复现。
Step 1 及时止损
明确问题后的第一件事一定是止损,而不是埋头去找问题。当然也需要判断影响是否足够严重,不是很严重的问题,或者止损措施可能导致更大的损失,那么需要酌情处理。
Step 2 定位原因
原因定位有很多思路,很考验能力和水平,也比较有意思,像是在探索解密。
定位过程需要遵循一定的科学方法,不能全靠瞎猜,虽然有些隐蔽问题一开始可能也需要靠猜。
Step 3 修复上线,回归验证
故障修复后,需要周知通报。
Step 4 复盘总结
一般线上故障,都需要文字化的记录整个排查过程、分析根本原因、明确后续措施。
故障管理主要围绕复盘文档进行,从技术管理角度看,无论对团队还是个人,故障复盘文档其实是一种重要资产和财富,是拿千万流量和真金白银喂养出来的,可以沉淀和提炼出大量的实战经验,甚至可以结集出版。
复盘文档主要包括四个部分:
1、时间线
从产生问题到发现问题的分钟级过程分析,主要目的是看处理的过程和动作是否合理,是否走了弯路,能否更快发现问题,能否找到更好的解决思路,技术层面、管理层面怎么改进更有利于故障的排查和处理。从管理角度看,主要关注三个时长指标:
如果时间都太长,那就更有必要深度复盘。
2、影响范围、定级
影响范围是定级的依据,定级是一种管理措施。比如:
3、根因
必须定位到最初的源头,找到根因,才算真正定位到了原因。找到根因的基本标识,一是能复现,二是无法再追问,换句话说,找到根因的基本方法就是复现 + 持续追问(5 why 原则)。
判断原因是否有效,主要看能否针对原因提出可执行的解决方案。按这个标准,以下原因都是无效的(通常都是主观意识类的):
从故障复盘角度看,无效的原因,不一定无用。比如长期的疲劳工作肯定会增加出错的概率,管理者也应该关注,不能过于机械主义,没人情味,出了故障一顿犀利追问,把人搞成了人犯。
4、后续措施
后续措施主要是针对本次故障的原因给出的改进措施,主要目的是解决这样几个问题:如何保证下次不出错?如何保证下次出了错能及时发现?如何保证别人不出同样的错?如何保证同一类型的问题不再发生?后续措施也需要跟进结果,每次故障复盘会议第一步就是回顾上期布置的 TODO。
事前:未经测试禁止上线
事中:无法回滚禁止上线
事后:无法回归禁止上线
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8