作者:曹挚(iyft_hws150@sina.com)
【引言】
这是一起偶发的、非典型的必然故障,它表面上是由几个不相关联的事件促成,然而用户所谓的“事实陈述”非但无助于解决问题,反而将问题的指向导入了歧途。
故障的症状是用户在人力资源管理中任何涉及员工“入转调离”的操作都无法进行流程审批,程序提示“页面数据没有完全载入,暂时不能完成”错误,表面上是人员岗位的调整功能无法进行,实际上用户的人力资源管理已经“停摆”了,进而引发整个绩效管理系统也被迫“挂起”了。然而笔者在北京的测试环境中反复按照用户现场操作人员描述的操作过程进行模拟实验,始终无法重现该故障。到达用户现场对当事的操作人员进行了访谈,反映的情况和之前基本一致:故障起因系某一名员工被无意中执行了两次相同的【离岗】操作,一个在流程审批环节中执行了全程同意并提交的操作处理,而另一个则执行了不同意并提交的操作,“导致”故障形成,无论维护人员做了何种尝试均无法恢复正常。笔者尝试对数据库进行跟踪,也未发现任何SQL语句执行异常,至此,问题陷入了僵局,说实话,本人也是一头雾水。
看起来,页面报告的数据载入错误是一个程序“可预见”的错误,那么,究竟这是一个什么错误?引发该错误的原因是什么?如何跟踪并找出错误的源头是解决该故障的关键,尽管笔者曾参与过两个Java软件项目的开发(一个是Servlet+ Ajax,一个是SpringBoot +MyBatis),但此次不仅没有源代码(该软件的流程审批部分系国内某知名软件公司定制开发),更为麻烦的是,现场除了浏览器以外基本上没有任何可借用的其它工具。
- 故障研判:
根据现场的情况可以断定:用户的程序代码完好,不存在篡改代码的可能,那就只剩下一种可能性,一定是数据库内部发生了什么变化,可是涉及流程的基础字典有十多个,每个都有若干个字段,究竟是哪个表的哪条记录的哪个字段发生变化了呢?这是个相当麻烦的局面。事实上,笔者出发之前就已经有所准备,将实验室模拟环境的基础字典全部导出,以备不时之需。
从理论上讲,将用户的基础字典全部清空,置换成笔者的“正常”字典,问题应该能够得解,但是,如此以来,该故障将无法重现,无法剖析,下一个不幸掉进此坑的“受害者”将不可避免。当然,如果尝试了多种办法都无济于事的话,这或许能算作最后一个“托底”的解决方案。
显然,这些都是后话。

图 1页面载入未完成错误
为了继续“刨根问底”,笔者被迫走上了一条此前自己未曾涉猎的在没有源代码支持状况下的页面调试之路。这既是本文得以啰嗦的成因,也是本文对读者或许有某些裨益之所在。
- 页面调试
- 打开页面,查看控制台输出细节
以下以google的chrome浏览器为例,登录程序,打开故障页面,点击F12键,弹出开发者工具(Developer Tools)窗口,控制台(Console)标签列举出当前页面的控制台输出,可见一个黄色警告和一条红色错误信息:

图 2控制台输出的页面错误
- 定位错误,判断故障可能的原因
警告信息可以忽略,错误信息就是页面没有完全载入错误的直接原因,由图可见错误出自flowToolBar.js文件的第111行,函数调用层级如图所示:

图 3出错函数的级联调用树图
观察ShowSinoButton函数可以猜出,该函数是用于显示某个“按钮”的,而且该按钮的DivId为“sinoDiv”加数字序号,至于这个按钮的用途需要进一步跟踪。

图 4函数错误的代码行

图 5函数错误的抛锚点
- 设置断点、观察单步调试的变量
找到故障的锚点,在此行设置断点,刷新故障页面,执行单步调试,并观察右侧中部本地变量(Local)区域的当前值,可见,当nPosition= 3时,程序因无法捕获的意外而抛锚了。

图 6设置断点、刷新页面、单步调试

图 7故障点的当前变量值

图 8抛锚点未捕获的错误
- 故障出现,搜索页面缺失的元素
程序试图获取sinoDiv3元素的属性,而该元素在页面上不存在,问题出现了,这个元素从哪儿来的?为什么它应该出现却未出现?这些“按钮”从何而来?可以配置么?在哪儿定义?

图 9程序预期的元素在页面上不存在
- 故障定位
至此,故障原因其实已经找到了,那就是程序预期应该出现的元素在页面上没有出现。这第四个按钮应该是什么呢?经过与原厂工程师的沟通,原来在审批流程的定义中所有流程都支持定义其“允许操作项”,此处定义了流程审批页面的所有按钮。

图 10流程的允许操作项定义

图 11缺省的流程操作按钮效果示意
该项中的“退回”功能默认应该是【未选中】的,程序在此处存在bug:那就是当用户选择了某个操作项之后,还应该在jsp文件中显式的定义该按钮的功能,如果未定义,页面就抛锚了,本文所述及的故障就因此而注定了。这就是我在本文开头所说的现场用户反映了一堆“误入歧途”的“实话”,而这个“要命”的操作被所有人轻描淡写地“抛诸脑后”了。
-
故障起因
故障源于一个被所有人忽略的“无意识”操作,而此操作恰巧触发了程序中原本就撂下的代码bug,数百个用户多年以来不曾踩到的雷被莫名其妙地踩到了。当然,感谢这个雷,包括笔者本人在内的所有读者顺便“温习”了一下JavaScript的调试方法,也算学习了一个小技巧。 -
BUG修复
下面就是那个bug的故障点修复示意代码以及运行修复后的效果图。

图 12流程允许操作的修补代码

图 13增加“回退”按钮后效果图
- 故障小结
本文所述及的JavaScript调试方法普遍适用于大多数浏览器和Java开发的网页程序,在某些特定的情况下,对页面的调试可以帮助我们分析并进而解决部分页面尚未捕获的错误,而这些错误的原因大多是由于后台数据库的某些变更引发的,只不过某些变更对程序而言并不致命,而有些却足以造成应用系统被迫“中断”的严重后果,掌握JavaScript的调试方法为我们侦测故障、分析流程、阅读程序等提供了一种额外的技术手段。
2021-10-20




