谈谈企业管理软件领域内那些很难稳定重现故障的处理技巧

前言

我是做企业管理软件的程序员,有一次我遇到一个问题,一段后台作业代码,运行时偶尔会出现运行时异常(runtime exception),但这个异常不是 100% 能重现,运行十次,大概能重现2,3次。而且在系统负载很重的时候,反而一次也不能重现。

更折磨人的是,如果在交互式单步调试模式下,这段代码运行完美,一点问题也没有。既然不能通过单步调试来排错,我的同事们都觉得棘手,最后让我来和这个问题死磕。

后来我采用类似二分查找的方式,把可能引起这个问题的代码层层过滤,最后定位到几行有高度嫌疑的代码,我自己编写了一个测试程序来模拟后台作业执行出错时的运行环境,才找到罪魁祸首:我们的一个模型创建 API,不支持模型创建和模型删除,在一秒钟之内完成。

也就是说,用户在 UI 界面正常操作时,手速再快,也不可能完成在一秒钟之内,做到先创建模型,然后马上删除的操作。但是后台作业是用代码调用 API,在系统负载不高的情况下,一秒钟之内完成创建并且删除的操作,不是一件困难的事情。这个时序问题也解释了为什么这个问题在单步调试模式下无法重现。

下面是文章的正文。

企业管理软件面向的是企业级用户,如果软件出现故障(bug),在某些极端情况下,可能会让企业蒙受巨大的经济损失,故而对软件开发人员在编程规范,软件测试和软件交付之前的验证等各方面都提出了更高的要求。同时,由于企业管理软件自身高度的复杂性,有些故障很难重现或者只能在运行了客户特定业务流程的生产系统上才能重现。这些都给企业管理软件分析和故障处理带来了巨大的挑战。

那么如果客户在发票页面上,看到这个字段的值为空,客户可能认为是发票模块出了故障。然而,在 data flow 的每个节点对应的模块处理,可能都是造成该故障的罪魁祸首。销售订单和客户发票属于 CRM 模块,而捡货任务和发货单则归属 SCM 的范畴。

在实际开发工作中,这意味着分析该故障往往需要跨团队间协作,因为 CRM 和 SCM 模块往往分属不同的开发团队负责。

3. 故障只能在客户生产系统重现

在企业管理软件交付之前,必定在内部开发,测试和验证系统(validation system)进行过不同层次的测试。即便如此,由于种种客观原因,比如当应用运行在客户生产系统上,基于某些只有该客户才会用到的特定业务流程的配置时,故障才会暴露,而这些配置并没有被企业管理软件供应商的内部系统测试所涵盖到。

这类故障因为只能在客户生产系统重现,在分析和定位问题时更加困难重重,尤其当重现步骤会在客户生产系统进行写操作时,通常只能联系客户相关人员,采用远程桌面+电话会议的方式,让客户相关人员进行操作,然后软件供应商的支持人员在线调试。

有一天,我收到一个故障 告,另一个团队的同事,使用我所在团队负责的 IBASE API,在同一会话过程内创建 IBASE 组件,修改,随后删除,然后保存,会遇到运行时错误(Runtime Error).

并不总是能够重现 != 不能重现。

为了分析这个问题,我得先找到能够稳定重现的办法。因为该故障对单步调试大法免疫,我只能另想他法。

逐字逐句阅读故障 告里的描述,发生故障之前的操作流程为:

(1) 创建 IBASE
(2) 修改 IBASE
(3) 删除 IBASE
(4) 保存事务。

出现运行时错误。

因为我就是 IBASE 模块的负责人,所以我三下五除二就写好了一个不到 200 行的程序,在程序里依次调用 IBASE 的创建,修改和删除 API,再保存事务。

程序源代码如下:

通过运行时错误的上下文调用栈,我找到了 CRM_IBASE_COMP_GET_DETAIL API 没有返回任何 IBASE 数据的原因:下图第 53 行高亮代码的 CHECK 语句,检查当前传入的时间戳(默认为 IBASE 创建时的时间戳)是否小于待读取 IBASE 抬头的 valto(即 valid to,指 IBASE 有效截止日期的时间戳)字段。如果小于,则顺序执行 CHECK 下一条即 54 行。如果大于或等于,则退出数据读取逻辑所在的循环体。

这一回,在调试器里,所有的谜题都揭晓了:当前时间戳 = IBASE valto 字段值,因此导致 API CRM_IBASE_COMP_GET_DETAIL 读取失败,抛出运行时错误。

而在后台作业模式以及脚手架程序正常运行情况下,如果 IBASE 创建,修改和删除的 API 执行得足够快速,能够在一秒钟之内完成,则 t3 与 t1 之差小于 1 秒,故 CHECK 语句执行失败,直接返回。

换言之,这个故障提交时,CRM IBASE API 的开发人员,并没有考虑到 IBASE 的创建和删除会在完成的场景。毕竟正常情况下,客户不可能在 1 秒钟之内,在 UI 完成 IBASE 先创建再删除的操作。这种场景只可能在客户使用 IBASE API 进行一些二次开发场景下才有可能出现。

当然,最后这个问题,也绝非仅仅是把 53 行 CHECK 语句的

回到这个故障分析过程本身,最开始接到故障时,因为单步调试无法重现,因此Jerry很是一筹莫展了一阵,后来想到编写脚手架程序来稳定重现该故障,这一步是问题分析的突破口。

有了脚手架程序之后,先注释掉所有的 API 调用,再逐步开放 IBASE 创建,修改和删除的代码,最终把问题范围缩小到 IBASE 删除过程。

通过脚手架应用的直接执行触发的运行时错误,利用调试器查看程序抛出错误时的变量值,将问题锁定到时间戳的处理逻辑上,进而找出根源。

这一分析步骤有点像上世纪末本世纪初电脑 DIY 发烧友们遇到组装机无法启动时的排查措施。当组装机无法启动时,只保留电源,主板和 CPU,尝试启动,如果成功,再逐一添加显卡,硬盘等其他设备。当新添加的设备导致系统重新回到无法启动状态,说明该设备有问题。当时发烧友们把这种方式称为“最小系统法”。

相关阅读

  • Jerry的反省:程序员不要轻易说出”这个功能技术上无法实现”

  • 记一次 SAP 开发工程师给微软 Azure incident 的体验

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年4月4日
下一篇 2022年4月4日

相关推荐