你好呀,我是歪歪。
前幾天發了一個牢騷:
本來只是單純的吐槽一下,但是好多人對其中的細節比較感興趣。
大家都是搞技術的嘛,對於“踩 BUG”這種喜聞樂見的事情,有興趣是很正常的。
其實我這個 BUG,其實嚴格意義上不能叫做 BUG,因為和程式無關,甚至和技術的關係都不算大。從標題上你也能猜出來,是和一個業務引數相關。
但是在這個過程中,因為我是整個事件全程的親歷者,所以現在回看這個事情,我還是有一些思考在裡面的。
我覺得這是一個程式設計師會遇到的“典型事件”。
那就用這篇文章一起復盤一下吧。
背景
要說明這個問題的背景,甚至不需要一個具體的業務場景,只需要圍繞著以下這個非常常見的利息計算公式,就可以說明問題的起因:
利息=計息金額*日利率。
日利率=年利率/360
由於日利率的計算,涉及到除法,在對應需求第一次開發時,業務的要求是日利率儲存 7 位小數。
在程式中,年利率和日利率是兩個欄位分別儲存的,日利率在初始化的時候就算好落庫了,後續程式直接取這個算好的日利率就行了。
系統上線,相安無事。
跑了一段時間後,業務又提來一個需求:當前的精度不夠,需要調整到 11 為小數。
你不用好奇歪師傅這邊到底是什麼業務場景,反正我去看了業務資料,需求是合理的,那就把需求接過來幹就行了。
儲存 7 位小數和 11 位小數,大家都是搞開發的,肯定也知道這個就是一個小改動,很快就能搞定。
事實也是如此,雖然之前的需求對應的程式碼不是我寫的,但是我看過程式碼,清楚的知道改動點在哪,所以很快就開發完成。
前面說了,這個需求之前線上上按照 7 位小數跑了一段時間,所以存在一些存量配置。
針對這些存量資料,在需求評審會議上的時候,我提了一句:存量配置怎麼處理呢?
業務答覆:這次需求上線的時候,你按照 11 位小數重新算好,然後寫 SQL 更新一下就行。
我心裡一盤算:計算公式明確,年利率我也有,算一把,沒啥問題。
就答應了。
然後,不出意外的出意外了。
假設年利率是 2.5%,除以 360 之後,保留 11 位小數,應該是 0.00006944444。
而我不知道當時為什麼手抖了,在 SQL 裡面寫成了 0.00069444444。
我給你對比一下:
0.00006944444
0.00069444444
相當於我寫出來的日利率被擴大了十倍。
然後再回頭看看這個公式:
利息=計息金額*日利率
日利率被擴大十倍,那麼對應的計提金額也會被擴大十倍。
這就是問題的背景。
一個單純的人為失誤,和程式沒有任何關係,所以嚴格意義上不屬於程式 BUG。
但是這個問題確實是足夠低階。
為什麼沒被發現?
那麼這個錯誤的 SQL 是怎麼透過程式碼評審、測試驗證這兩道關卡被帶到生產環節的呢?
首先,這一次提交的程式碼,壓根就沒有評審環節。
我有程式碼提交許可權,也有程式碼稽核許可權。所以我自己提交,自己就稽核透過了。
其實這個需求應該是組裡面另外一個小夥伴來做,但是當時他被調到其他組了。
他還在我們組的時候,我們的合作模式是他提交程式碼,我進行稽核。
如果有這個環節,我覺得我有 50% 的機率發現問題。
為什麼是 50% 呢?
因為這取決於我稽核程式碼時是否有正在處理其他的事情,如果有其他事情處理,我可能會形式主義的看上幾眼。如果沒有其他事情,而這次提交的程式碼量又不大的話,我基本上都會認真的過一下提交的內容。
透過程式碼評審之後,接下來就應該是測試環節。
測試主要關注的是精度從 7 位變成 11 位之後,最終計算出來的利息是否符合預期。
他測試時是走了整個業務的全流程。
在“全流程”中,這個 11 位精度的日利率,是在頁面配置年利率的時候透過程式自動計算出來的,不會錯的。
而他在驗證 SQL 語句的時候,測試環境又沒有生產環境的配置,所以他拿著我提供的 SQL,只能保證寫的語法沒問題,能正常執行,並不能確保裡面資料的正確性。
而我也記得很清楚,我當時給他說過:你執行一下 SQL 不報錯就行,值的正確性,我來保證。
而且戲劇性的是,測試同事很仔細的去看了值,他去數了確實是 11 位小數。但是可惜,站在他的視角,他發現不了值被擴大了十倍。
所以,測試環節也沒有發現這個問題:
0.00006944444
0.00069444444
就帶著上生產了。
一個問題正常來說不應該被帶上生產,但是我們確實不能保證測試環節一定能把所有問題都測出來,所以新專案、新迭代的生產驗證也是非常有必要的。
這個我們也做了。
按理來說,生產上的資料已經是錯誤的了,而且是一個“利息金額擴大十倍”的明顯的錯誤,如果主動去做了資料驗證,應該能被發現才對。
那為什麼做了生產驗證,卻沒有發現問題呢?
因為當時存量配置有三條,我提供了 3 個 SQL,其中有一個是算對了的。
每一條存量配置都對應著大量的利息資料,而算對了的這個對應的資料更多,在比例上超過 60%。
我進行生產驗證的時候,在大量的利息計提資料中隨機抽選了兩條,選中的這兩條,恰好都是正確的 SQL 對應的資料。
所以我發現符合預期,得出了生產驗證透過的結論。
站在這個節點,回顧整個事件,這個時候應該是最有可能發現問題的時候。
但是沒發現。
根本原因是驗證方案不嚴謹,玄學原因是運氣不站在我這邊。
怎麼暴露的?
你想想,這種業務引數配置錯誤的問題你能透過什麼監控規則監控到嗎?
其實很難的。
我們一般來說做技術層面的監控,都是監控程式是否按照預期正常執行。比如在計算的過程中出現異常,那我們是可以監控到的。
但是在這種只是參與計算的值不對,但是能正常計算出一個值的情況,並不會報錯。
這種問題透過技術手段很難監控到。如果硬要去做監控,肯定是能做的,比如從異常浮動的維度、橫向資料對比的維度,但是配套的開發成本又上去了。
我是怎麼發現這個問題的呢?
也是純粹的運氣。
是一個週五的晚上,我做另外的一個和本問題毫無關係的場景下的資料驗證的時候,偶然間看到了一筆資料的金額和前幾天比,明顯大了很多。
這是不符合業務規律的。
然後進一步跟蹤,最終定位到了前面的問題 SQL。這個時候距離這個 SQL 上線,已經過去了三天,已經產生了一批錯誤資料了。
如果我沒有偶然間看到這個問題資料,那麼這個問題會在什麼環節暴露呢?
就是在業務使用這個資料做核對的時候。
那個時候整個問題的性質就變了。不僅是處理時間來不來得及的問題了,而是這個問題是由“開發自主發現”還是由“外部反饋發現”這兩個完全不同的性質了。
一般來說,不管是什麼問題,先拋開嚴重程度,只要是開發自主發現的,都能一定程度上讓事情變得不那麼難堪。
所以我們才一度強調“可監控”的重要性。
隨後,我聯絡了業務,反饋了這個情況。他表示在他下次使用這批資料之前,把資料修復好就行。大概一個月後,他會用到這批資料。
這樣,我有接近一個月的時間來處理這個問題,防止問題擴大化。
時間非常充足,站在這個角度,我運氣還不錯的。
問題已經暴露出來了,隨後就是制定針對這批錯誤資料的修復方案了。
修復方案就和業務場景相關了,屬於多個業務場景疊加在一起,所以修復方案其實是比較複雜的,涉及到“修數”和“補數”,沒有展開描述的必要了。
只是想簡單提一句,這個修復方案是我利用週末的時間想出來的,很多細節問題我都需要考慮到,甚至在心裡寫了一遍虛擬碼。
確實是浪費了週末的時間,但是這是為自己的錯誤買單,半點不怨別人,就是活該。
而對於參與後續方案討論的同事來說,在這件事情上付出的時間,才是屬於無妄之災。
這就是整個事情的過程,一個小數點引發的血案。
再回首
現在整個事情的全貌都在你眼前了,你得到了什麼經驗教訓?
因為手抖了,寫錯了一位小數,這確實是直接原因,所以是想著下次再處理這種資料的時候,更加小心一點嗎?
我覺得不是這樣的。
我得到的經驗教訓就是我的標題:開發人員,千萬不要去碰那該死的業務引數!
如果在最開始需求評審會,我們討論到存量資料的時候。
業務說:這次需求上線的時候,你按照 11 位小數重新算好,然後寫 SQL 更新一下就行。
我說:不行,這個屬於是業務引數,我不能去動。上線完成後,就具備這個功能了,你可以透過頁面配置去修改。
我知道他們修改業務引數的流程,很長很複雜。
首先業務需要發起一個引數變更的 OA 流程,然後走到他的部門負責人審批。
業務部門負責人審批完成後,會到具體負責業務引數配置的人員手裡,還需要該人員對應的部門負責人稽核。
稽核完成後有許可權的人員才會去修改這個業務引數,而這個引數的修改,在對應的系統功能上還有兩級甚至三級稽核。
整個完成之後發起 OA 的人員還需要進行變更確認,看看頁面上是否是自己想要的配置。
這一套流程走下來,你覺得還會出錯嗎?
很難出錯了。
你可以批判這個流程過於臃腫,但是你最終總是會認識到,這個流程其實是在保護打工人。
我知道他流程比較複雜,而我寫個 SQL 幾乎是沒有成本的,但是這是在 SQL 正確的前提下。
如果當時不答應透過 SQL 的方式幫他處理存量資料,他其實有更加正規的流程去處理這些資料,而且不會出錯。
事後我們覆盤的時候,也有同事私下向我提出了這個的問題:為什麼不走 OA 流程去調整這個引數?
另外,關於流程,我給你舉一個程式設計師方面的例子。
一個核心開發人員擁有線上資料庫的操作許可權,我們先假設這個人絕對忠誠、絕對可以信賴、絕對恪盡職守、絕對不會刪庫跑路。
某一天,他收到一個預警資訊,經過排查發現需要去修改資料庫裡面某個資料的狀態,他直接就去修改了。
這個操作非常常見,特別是在小公司或者在一些在快速發展階段的公司。
後來這個公司成長起來了,開始更加註重操作風險了,回收了所有人員的資料庫許可權,以前的事兒既往不咎,以後想要修改資料庫資料,必須要發起一個審批流程,經過層層審批之後才能執行。
這個流程和“直接去修改”這個動作比起來,就重了無數倍了。
站在程式設計師的角度,前幾年都是可以直接操作生產資料,突然這個制度出來了,極大的影響了之前的開發慣性。所以剛剛開始執行的時候,你可能會罵一句:xxx。
但是長遠來看,這個流程其實是在保護你。
當你有資料庫許可權的時候,操作對了,沒有人會誇你。操作錯了,你就是罪魁禍首。
有了一個審批流程,在加重了操作成本的同時,也降低了錯誤成本。
處理問題的時長可能增加了,對於問題處理的敏捷度可能降低了,但是站在公司的角度,隨著公司的發展“穩定”才是永恆的主旋律,在穩定面前,敏捷度反而是可以犧牲的。
歪師傅在第一家公司業務野蠻發展的時代,曾經就有這樣的許可權,那個時候剛剛參加工作兩年多的時間,覺得事情就應該是這樣的,這樣才是正確的,可以足夠敏捷,足夠迅速的處理問題。
後來許可權回收了,當時我也在私底下罵罵咧咧了幾句。
再回來,隨著經驗和在職場上見過得事兒越來越多,才漸漸認識到:蠻荒時代確實出英雄,但是我沒有把握好機會成為英雄。蠻荒時代之後的流程規範,規章制度其實是在保護那批沒有成為英雄的人,其中就有我。
最後,給你,也給我自己一個忠告:開發人員,你最好要知道你資料庫裡面每一個業務引數背後的業務含義,但是千萬不要去碰那該死的業務引數。也輪不到你碰,該碰的人會在正確的流程下去碰。
無論什麼時候,心中都要繃著這根弦。