这是 Jerry 2021 年的第 67 篇文章,也是汪子熙公众号总共第 344 篇原创文章。

Jerry 从 2007 年大学毕业退出 SAP 成都研究院至今,始终从事企业管理软件畛域的开发工作。

企业管理软件面向的是企业级用户,如果软件呈现故障(bug),在某些极其状况下,可能会让企业遭受微小的经济损失,故而对软件开发人员在编程标准,软件测试和软件交付之前的验证等各方面都提出了更高的要求。同时,因为企业管理软件本身高度的复杂性,有些故障很难重现或者只能在运行了客户特定业务流程的生产零碎上能力重现。这些都给企业管理软件剖析和故障解决带来了微小的挑战。

本文从 Jerry 解决过的一个理论软件故障登程,谈谈本人对企业管理软件里一些辣手故障的解决领会。

在 Jerry 看来,这些辣手故障,能够分为以下几类。

企业管理软件畛域内辣手故障的一些表现形式

我在 SAP 成都研究院解决过很多颇让人头痛的软件故障,它们具备下列一项或几项特色。

1. 须要简单的流程能力重现

例如我解决过的 SAP Business ByDesign 里一个客户发票(Customer Invoice)相干的故障。这个故障只有在每次 release 发票时能力重现。为了 release 发票,咱们必须先创立一个销售订单(Sales Order),基于该订单创立 Customer Demand,而后创立捡货工作(Pick Task),生成交货单(Delivery Note),最初能力生成一张新的客户发票。

这些简单的流程往往也须要零碎当时保护好对应的主数据(Master Data)和事务数据(Transaction Data)能力顺利执行。简单的业务流程削减了故障重现的难度。

2. 故障横跨企业管理软件的多个模块

因为企业管理软件本身的复杂度,终端用户眼中看到的貌似简略的一个故障,背地可能横跨了软件实现的多个模块。

以上述模式 1 形容的故障为例,假如软件帮忙文档上形容的反对性能为:客户在销售订单界面上增加了一个新的自定义字段并保护了对应值,该值可能从销售订单,经捡货工作,交货单,最初传递到客户发票上。咱们称这种字段值从多个文档间的传递称为 data flow.

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

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

3. 故障只能在客户生产零碎重现

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

这类故障因为只能在客户生产零碎重现,在剖析和定位问题时更加困难重重,尤其当重现步骤会在客户生产零碎进行写操作时,通常只能分割客户相干人员,采纳远程桌面+电话会议的形式,让客户相干人员进行操作,而后软件供应商的反对人员在线调试。

4. 故障只能在后台作业模式下重现,在 online 模式运行时一切正常

在企业管理软件畛域特地是 ERP 畛域,后台作业经常被用来执行一些费时的批处理工作,比方订单批量解决,报表数据分析和聚合汇总等等。后台作业模式不同于挂接了用户界面的 online 模式,给单步调试也带来了艰难。

5. 故障只能在软件失常运行模式时能力重现,单步调试时,软件工作一切正常

当故障呈现这种特色时,理论给反对人员传递了一个信号:该故障可能与程序特定的执行时序相干。因为程序失常运行,与处于单步调试模式下运行,执行时序显然不同,比方在调试器单步调试时,可能会毁坏多线程程序失常的执行时序。

因为短少了调试器这一强有力的武器,剖析该类故障,须要反对人员具备更强的实践剖析能力和问题形象能力。

因为篇幅限度,本文仅举一个理论例子,对上述第五类故障的剖析解决流程做一个分享。

Jerry 已经负责过 SAP CRM IBASE(Installed Base) 模块的保护工作。IBASE 是一个形象模型,用于形容已在客户地位装置的资源对象,例如设施、机器,服务或软件。IBASE 模型以树形构造,形容了这些对象的层级构造和它们的各个组件,是服务模块的参考根底。

有一天,我收到一个故障报告,另一个团队的共事,应用我所在团队负责的 IBASE API,在同一会话过程内创立 IBASE 组件,批改,随后删除,而后保留,会遇到运行时谬误(Runtime Error).

在故障形容里提到的运行时谬误的截图如上图所示。

这位共事发现,这个谬误只能在后台作业模式下重现,并且不肯定每次都可能重现。该故障也无奈在单步调试模式下重现。

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

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

逐字逐句浏览故障报告里的形容,产生故障之前的操作流程为:

(1) 创立 IBASE
(2) 批改 IBASE
(3) 删除 IBASE
(4) 保留事务。

呈现运行时谬误。

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

程序源代码如下:

执行这个报表,遇到了冀望中的运行时谬误。这是一个好兆头,因为我当初找到了稳固重现问题的方法。下一步,我须要放大问题的范畴,找出我这 200 行代码里,到底哪一行代码的执行,引起了运行时谬误。

Jerry 喜爱称本人开发的这种专门用于剖析故障,重现谬误的程序,为“脚手架程序”或者“故障触发器”。

因为这 200 行代码是我本人编写的,所以我能够任意批改。

  • 首先把所有代码全副正文掉,只留下 IBASE 创立 API 的调用。执行程序,一切正常。
  • 再解除 IBASE 批改 API 调用代码的正文,让其参加到程序执行中,一切正常。
  • 再反正文 IBASE 删除 API 调用代码,执行程序,呈现了运行时谬误!

由此阐明,这个运行时谬误和 IBASE 删除的场景相干。

回到故障提交报告里的运行时谬误截图:第 103 行抛出了一个类型为 X 的谬误,因为调用函数 CRM_IBASE_COMP_GET_DETAIL, 并没有读取到通过输出参数 i_date 和 i_time 指定的工夫戳对应的 IBASE 数据,因而程序决定通过抛出谬误的形式来终止执行。

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

在后台作业运行模式,以及我的脚手架程序执行时,第 53 行工夫戳判断条件没有满足,因而退出了循环,导致 CRM_IBASE_COMP_GET_DETAIL 读取失败,所以引发了故障。

要想满足 53 行的判断条件,只有两种可能:

以后工夫戳 > IBASE valto 字段值
以后工夫戳 = IBASE valto 字段值

须要强调的是,ABAP 编程语言里的工夫戳字段,准确到秒,比方 20211024102424 代表 2021年10月24日10点24分24秒。

尽管我的脚手架利用在单步调试模式下也无奈重现故障,然而间接执行能够重现。因而,执行执行脚手架利用,在运行时故障页面点击工具栏的 Debugger 按钮,能弹出调试器,查看应用程序抛出运行时谬误的各种信息:

这一回,在调试器里,所有的谜题都揭晓了:以后工夫戳 = IBASE valto 字段值,因而导致 API CRM_IBASE_COMP_GET_DETAIL 读取失败,抛出运行时谬误。

在调用 IBASE 创立 API 时,会把待创立的 IBASE 低头的 valfr 字段,赋以零碎的以后工夫戳。

在调用 IBASE 删除 API 时,会把该待删除 IBASE 低头的 valto 字段,赋以零碎的以后工夫戳。

为什么在单步调试模式下,无奈重现这个谬误呢?咱们来看一张简略的时序图。

横轴代表工夫戳。t3 代表代码 53 行判断语句里的 <ibinadm>-valto 字段值, t1 代表代码 53 行判断语句里的 lv_timestamp 字段值。

在单步调试模式下,假如咱们从 IBASE 创立 API 开始顺次单步执行,则因为按键手速起因,t3 必然大于 t1.

而在后台作业模式以及脚手架程序失常运行状况下,如果 IBASE 创立,批改和删除的 API 执行得足够疾速,可能在一秒钟之内实现,则 t3 与 t1 之差小于 1 秒,故 CHECK 语句执行失败,间接返回。

换言之,这个故障提交时,CRM IBASE API 的开发人员,并没有思考到 IBASE 的创立和删除会在同一秒之内实现的场景。毕竟失常状况下,客户不可能在 1 秒钟之内,在 UI 实现 IBASE 先创立再删除的操作。这种场景只可能在客户应用 IBASE API 进行一些二次开发场景下才有可能呈现。

当然,最初这个问题,也绝非仅仅是把 53 行 CHECK 语句的 < 符号,改成小于等于操作那么简略。咱们认真评估了改变可能带来的其余副作用,并和提交该故障的团队开发人员进行了探讨,最初采取了其余的形式来防止这个故障的产生。

回到这个故障剖析过程自身,最开始接到故障时,因为单步调试无奈重现,因而Jerry很是束手无策了一阵,起初想到编写脚手架程序来稳固重现该故障,这一步是问题剖析的突破口。

有了脚手架程序之后,先正文掉所有的 API 调用,再逐渐凋谢 IBASE 创立,批改和删除的代码,最终把问题范畴放大到 IBASE 删除过程。

通过脚手架利用的间接执行触发的运行时谬误,利用调试器查看程序抛出谬误时的变量值,将问题锁定到工夫戳的解决逻辑上,进而找出本源。

这一剖析步骤有点像上世纪末本世纪初电脑 DIY 发烧友们遇到组装机无奈启动时的排查措施。当组装机无奈启动时,只保留电源,主板和 CPU,尝试启动,如果胜利,再逐个增加显卡,硬盘等其余设施。当新增加的设施导致系统从新回到无奈启动状态,阐明该设施有问题。过后发烧友们把这种形式称为“最小零碎法”。

而整个剖析过程的重中之重,就是把故障报告中无奈稳固重现故障的后台作业里执行的内容,形象成一个不到 200 行的脚手架程序。

《编程珠玑》第五章已经分享过一个对于故障调试的乏味故事:IBM 钻研核心一位程序员,新装置了一台工作站,发现一个故障:他只能采取坐着的姿态登录零碎;一旦站起来,就无奈登陆零碎了。大家晓得这个故障最初怎么定位的吗?去读读原书吧!

心愿本文能给大家在企业管理软件畛域内的故障排除办法带来一些启发,感激浏览。

相干浏览

  • Jerry的检查:程序员不要轻易说出"这个性能技术上无奈实现"
  • 记一次 SAP 开发工程师给微软 Azure 报 incident 的体验

更多Jerry的原创文章,尽在:"汪子熙":