從一個 SAP CRM 軟體實際的故障處理出發,談談企業管理軟體領域內那些很難穩定重現故障的處理技巧

JerryWang_汪子熙發表於2021-11-09

這是 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的原創文章,盡在:"汪子熙":

相關文章