目录企业管理软件领域内棘手故障的一些表现形式
1. 需要复杂的流程才能重现
2. 故障横跨企业管理软件的多个模块
3. 故障只能在客户生产系统重现
4. 故障只能在后台作业模式下重现,在 online 模式运行时一切正常
5. 故障只能在软件正常运行模式时才能重现,单步调试时,软件工作一切正常
一个实际故障排查过程的案例分享
1. 试图找到稳定重现故障的办法
2. 缩小可能引起故障的代码排查范围
3. 利用调试器锁定问题
故障排查过程总结
笔者从2007年大学毕业加入 SAP 成都研究院至今,一直从事企业管理软件领域的开发工作。
企业管理软件面向的是企业级用户,如果软件出现故障(bug),在某些极端情况下,可能会让企业蒙受巨大的经济损失,故而对软件开发人员在编程规范,软件测试和软件交付之前的验证等各方面都提出了更高的要求。同时,由于企业管理软件自身高度的复杂性,有些故障很难重现或者只能在运行了客户特定业务流程的生产系统上才能重现。这些都给企业管理软件分析和故障处理带来了巨大的挑战。
本文从笔者处理过的一个实际软件故障出发,谈谈自己对企业管理软件里一些棘手故障的处理体会。在笔者看来,这些棘手故障,可以分为以下几类。
企业管理软件领域内棘手故障的一些表现形式
笔者在 SAP成都研究院处理过很多曾让我头痛的软件故障,它们具有下列一项或几项特征。
1. 需要复杂的流程才能重现
例如我处理过的一个客户发票(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. 故障只能在软件正常运行模式时才能重现,单步调试时,软件工作一切正常
当故障出现这种特征时,实际给支持人员传递了一个信号:该故障可能与程序特定的执行时序相关。因为程序正常运行,与处于单步调试模式下运行,执行时序显然不同,比如在调试器单步调试时,可能会破坏多线程程序正常的执行时序。
因为缺少了调试器这一强有力的武器,分析该类故障,需要支持人员具有更强的理论分析能力和问题抽象能力。
由于篇幅限制,本文仅举一个实际例子,对上述第五类故障的分析处理流程做一个分享。
一个实际故障排查过程的案例分享
笔者曾经负责 SAP CRM IBASE(Installed Base) 模块。IBase 是一个抽象模型,用于描述已在客户位置安装的资源对象,例如设备、机器,服务或软件。IBase 模型以树形结构,描述了这些对象的层级结构和它们的各个组件,是服务模块的参考基础。有一天,我收到一个故障报告,另一个团队的同事,使用我所在团队负责的 IBASE API,在同一会话过程内创建 IBASE 组件,修改,随后删除,然后保存,会遇到运行时错误(Runtime Error).
在故障描述里提到的运行时错误的截图如上图所示。
这位同事发现,这个错误只能在后台作业模式下重现,并且不一定每次都能够重现。该故障也无法在单步调试模式下重现。并不总是能够重现 != 不能重现。
1. 试图找到稳定重现故障的办法
为了分析这个问题,我得先找到能够稳定重现的办法。因为该故障对单步调试大法免疫,我只能另想他法。
逐字逐句死扣故障报告里的描述,发生故障之前的操作流程为:
(1) 创建 IBASE
(2) 修改 IBASE
(3) 删除 IBASE
(4) 保存事务。
出现运行时错误。
因为我就是 IBASE 模块的负责人,所以我三下五除二就写好了一个不到 200 行的程序,在程序里依次调用 IBASE 的创建,修改和删除 API,再保存事务。
程序源代码如下:
执行这个报表,遇到了期望中的运行时错误。这是一个好兆头,因为我现在找到了稳定重现问题的办法。下一步,我需要缩小问题的范围,找出我这 200 行代码里,到底哪一行代码的执行,引起了运行时错误。
笔者喜欢称自己开发的这种专门用于分析故障,重现错误的程序,为“脚手架程序”或者“故障触发器”。
2. 缩小可能引起故障的代码排查范围
因为这 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 行。如果大于或等于,则退出数据读取逻辑所在的循环体。