当前位置: 首页 > 科技观察

一行代码引发恐惧,深入思考提高线上代码质量的方法

时间:2023-03-18 20:27:52 科技观察

工作前5年,从事基础系统研发,从事后端接入层工作,后端存储系统,RPC框架。我不怕你的笑话。那时候,我一直对代码有一种恐惧感。这种恐惧从何而来?让我慢慢说。我们搭建的基础系统,都是用在上亿甚至上亿用户的产品的业务系统上。从客户端(前端)到后台业务逻辑层,再到基础设施层,编写的代码运行在整个调用链路的后端。你可以认为几乎每一个用户的每一个请求都会去到我们写的那部分代码。这对系统的影响是:代码出错后,受影响的用户范围会非常大。在数亿甚至数十亿用户的情况下,每天的请求量可能是数千亿甚至万亿。如此庞大的请求量,几乎会遇到各种奇奇怪怪的异常。代码必须极其健壮,一个小异常如果处理不当就会造成大麻烦。这让我想起了我们失败时的情况。几年前,每次改版本,我们基本上晚上睡不着觉,担心改的代码有问题。我对手机的报警短信特别敏感。如果有什么问题,我会立即打开电脑VPN查看,即使是在半夜和清晨。我有一个习惯,每次变化后每隔几个小时查看一下监控曲线和日志,看看有没有异常的迹象。一旦发现不对劲,我会立即着手排查,直到确定没有问题为止。.即便如此,还是不??可避免地会出现问题。半夜两三点钟,你的手机突然响起,报警语音机器人告诉你,你有重要监测曲线异常,请检查。然后你的血压立马升高,心跳加快,你从床上跳起来,打开电脑,连上公司的VPN,立马开始调查。几分钟后,QA(QualityEngineer)电话通知您,该故障已上报部门故障系统,当前受影响用户数为XXX。请加快处理速度,然后您的心跳会再次加快。半小时后,我终于有了主意。这时候你的Leader打来电话问你是怎么回事,要多久才能办完。含糊其辞的回复你的Leader后,你又开始埋头,一行一行排查。一个小时后,你终于找到了问题所在,并实施了故障排除方案,比如回滚新代码,或者屏蔽某些机器等,你终于有时间喘口气了。(PS:这里正确的流程是出现问题后立即回滚代码,但是因为数据的关系,存储系统在确定原因之前是不敢回滚的,怕影响数据.)你赶紧爬上床睡了2-3个小时,因为第二天要早起赶往公司处理故障善后工作,数据损坏,数据混乱。那时候我们写代码的时候非常小心,改动的时候也是格外小心。所以我对代码改动有一种焦虑和恐惧。编写代码不是一件容易的事,至少在那时是这样。这件事,我现在回过头来看。你可以认为有一部分是人为造成的,但仔细想想,要写出没有bug的代码几乎是极其困难的,所以这里的研发过程其实是少了一些东西。前期因为业务发展太快,团队整体人力跟不上。所以,很多工艺一开始都是非常原始的。当时想做,但客观条件不允许。后来业务稳定了,流程就更加规范了。比如引入了Coverity的代码检查,也实现了测试用例覆盖和持续集成。但最终,并非所有流程都得以延续。比如代码测试用例覆盖,后来有的团队放弃了,需求变化太快,测试用例成本太高。Coverity自动化程度高,没有人工输入,所以实现了。但我相信并不是所有的公司、所有的团队都会有这个标准化的流程。一是研发流程成熟度建设的问题,除此之外还有成本和业务迭代速度。在互联网上,产品在高速迭代的时候,产品还没有存活下来,更不可能有成熟的流程。整体而言,建立标准化但相对繁重的研发流程,也要根据具体情况而定。需要考虑产品的形态、产品迭代的速度、团队的人力预算成本、产品的生命周期等等。当然,不管怎么说,这都不是个人能决定的事情。如果你的团队有完整的研发流程,那是好事,但如果不是那么完美,你能做什么?我的经验来自Look,下面的一些措施对个人也有很好的效果。测试驱动开发(TDD)已经有一段时间了,因为业务的快速发展,性能需求的不断提升,存储模型的不断迭代完善,所以那段时间有很多代码修改,而且当时的焦虑感也很高。非常重。我记得在《重构:改善既有代码的设计》学习过TDD。简单来说,就是先构建测试用例,然后再开始编写你的功能代码。在设计测试用例之前,需要定义模块的对外接口,包括接口的类型、参数、返回值等,然后针对定义好的接口编写测试用例。在此过程中,您可能会发现界面设计不合理,需要相应修改。你的测试用例写好之后,基本上你的界面就修改好了,所以TDD也可以改进你的界面设计。然后为每个接口实现具体的代码逻辑。我在磁盘存储引擎中使用了这种方法,发现它非常好。我专门花了一周左右的时间编写测试用例。以后每天实现一些功能后,马上跑测试用例。每一次通过考试,心里都会有一个安稳的B。有一种妈妈,不再担心我写的代码有bug,会被老板抓到,导致扣工资。因为有完善的测试用例,随着你的测试用例不断增加和覆盖,你的信心会越来越足,焦虑自然会减少很多。但是这种方式更适合底层系统和核心稳定系统。对于需求变化的系统,构建测试用例的人力成本过高,如果需求发生变化,现有的测试用例可能会失败,导致投入产出比不足。简单来说,灰度发布就是当一个功能即将上线时,并不是立即对所有用户开放。有点像对产品的内测,只是用在技术上。比如我新增了一个产品需求,比如微信里的“看一看”入口,一开始是不对所有用户开放的。首先,将推出一个新的客户端版本。代码逻辑已经嵌入,但设计了一个开关,对所有用户关闭。前期可能会找到千分之一甚至万分之一的用户(随机或特定用户群),让他们使用。在此过程中,收集各种日志、监控和用户反馈,以确认和修复系统中的各种问题。一般两三周后,如果没有大的问题,会进一步放行用户。比如变成百分之一、十分之一,不断迭代,直到覆盖所有用户。灰度的思想在网上特别常用。客户端、前端、后端都可以使用。比如在后台,一个新的修改上线后,并不会立即对所有用户开放。但是根据一定的规律,比如以QQ用户为例,可能是这样一种规律:计算Hash值(QQ号)%1000<=灰度用户比例(取0---1000).增加音量的最小努力是1/1000。已灰度的用户会看到新功能,未灰度的用户不受影响。这个技巧对新功能、系统优化和代码重构都有好处。额外付出的成本并不大,有的公司自研灰度系统。如果没有,在重要的不确定的功能上加几行灰度控制代码也不难。监控和日志监控和日志记录并不是什么新鲜事。在工作的第一年,我们的技术总监在一次会议上告诉我们:你写的代码是死的,只有在线运行的代码是活的。Monitoring和Log(尤其是monitoring)就像是你代码的物理信息,可以随时反映你的代码在实际环境中的运行状态,应该被高度重视。这段话,在后面,我感触很深。通过完善监控和日志的设计,提前发现了很多问题,避免了很多因为代码bug或者系统设计缺陷导致的重大故障。后来监控和Log的设计也成为了我们方案设计的一部分。一般在plan之后会加上必要的监控点和Log点,比如请求次数、成功次数、失败次数、各种异常次数、极端逻辑执行次数等等。您应该意识到监视和日志记录的重要性,并且应该花时间专门设计它们。设计良好的监控和日志能发挥出那种随便加的监控日志无法比拟的价值。双写双读校验在新的业务代码中用的不多。更多的是针对基础系统或者核心系统的优化重构。并且有前提条件要求重复执行一个操作(例如只读操作和幂等数据操作)。简单点说就是把新旧代码分成两个进程(两个接口),上线到实际环境,然后在同一个模块中调用。一个请求进来后,两个进程分别执行,逐字节比较新老进程的结果(比如Memcmp)。新流程的结果仅用于对比,返回的结果仍然是旧流程的结果,不影响线上业务。如果比对失败,则可能存在异常。找到并解决,在实际环境中运行几天后,如果没有问题,可以使用灰度法进一步增大音量。不过在一般业务中并不经常用到,在基础系统中用的比较多,这里就不展开了。另外,对于客户端,还有热补丁机制,客户端Log收集系统等,但是这个需要的开发量比较大,可能不是一个人可以完成的,可能需要一个小团队来完成完全的。最后,软件工程是个很大的话题,这么大的话题我也不好说。在这里给大家讲一个以前的故事,分享一些我经常用的低成本,但是可以提高在线代码质量的方法,供大家参考。如果您有好的做法,请在评论中分享。