擦屁股的藝術 – 聊聊前人 Bug 的緊急修補

身為程式開發人員,多少都有這種經驗:

線上系統出錯,原開發者已浪跡天涯,程式碼沒人熟,老闆面色猙獰問誰會修。
(遇到這種擦屁股的屎缺,同事們默契十足全都退了一步 )
老闆說「很好,想不到你剛進公司就想立此奇功!好好幹,公司不會虧待你的」...
你說「暗陰羊咧,陳近南是你?」「沒問題,這交給我!」(心中滿是狂奔的羚羊)

這類狀況跟修自己的 Bug 截然不同,有幾個特點:

  • 狀況緊急必須限時修好,砍掉重練不是選項。
  • 屬臨時救急,策略上不打算多花資源深入了解及翻修。例如: 有計劃另建新系統取代,舊系統已進入插管維生階段。
  • 處理者對系統架構、程式碼全然不熟悉,時間壓力下難以全面了解,也不敢大幅更動,需以最小幅度修改解決問題為目標,是一種「微創手術」的概念。
  • 最重要的一點,修復過程通常會五毒攻心氣血逆流,只求速速搞定:
    「喵的,又不是我搞壞的,為什麼是我」
    「暗!這是什麼鬼寫法啦?」

基於上述因素,修補者常會無視原本程式碼的明顯缺陷,陷入「讓修改幅度極小化」的迷思。最後,雖然只改一個字元就把問題修掉,但錯失了防範類似問題再次發生的機會,為下次爆炸埋入引信。

用一個範例來模擬情境。假設有個網頁程式由資料庫取得下拉選單選項,並取第二選項為預設值,前人的程式這麼寫:

    ddlSource.Items.Clear();
    ddlSource.Items.AddRange(
        GetSourceOptions()
        .Select(o => new ListItem(o.Value, o.Key)).ToArray());
    ddlSource.SelectedIndex = 1;

有一天資料庫端忽然冒出新選項,原本的第二選項變成第三個,使用者抱怨送出表單的預設選項錯誤,後續作業大亂。

你被指派修復這個問題,千辛萬苦追程式碼找到問題點,基於「微創手術」的概念,所以...

ddlSource.SelectedIndex = 2;

改完收工,跟老闆回報問題修好了。

只改了一個字元就把Bug修好了,但,這是良好的修復方式嗎?

小天使說: 如果下回資料庫再被塞入一筆資料,是不是系統又要再壞一次? 又不知是哪個倒楣鬼被踢下來辛苦追 Code,找出這段再改一次。(說不定還是你)
小惡魔說: 程式又不是我寫成這樣,我只奉命修好它,再壞掉也不是我的責任。更何況,使用者說這個選項不太會動,這次異動是個意外,以後應該不會再動。

看似兩難情境,分析利弊後不難抉擇:「如果成本不高,你應該把它改成強韌不易出錯的版本」,理由很簡單:

  • 依據墨菲定律,別人愈說不會修改,它愈可能被修改
  • 踢到石頭跌倒,把石頭搬到路邊防止別人摔跤,累積陰德抵過扶老太太過街三次
  • 「上回 XXX 改好過,這回又壞了」。就像修過的水管又漏水,就算主因是管線設計先天不良,你覺得倒楣鬼水電工會不會背負功夫差做事兩光的評價?

SelectedIndex = 2 寫法必須建立在「資料來源項目不變,順序固定」的前題上,在我眼裡脆弱得像玻璃,稍有風吹草動便碎裂一地。如果修改成本不高,改用防禦式設計可讓程式更強韌,不易因外部因素故障。這點也是有些人的程式三天兩頭故障,有些人的程式像大同電鍋一用數十年都不壞的關鍵之一。

基於預設選項順序可能不固定的考量,程式可以改成這樣:

    ddlSource.Items.Clear();
    var sources = GetSourceOptions()
        .Select(o => new ListItem(o.Value, o.Key)).ToArray();
    ddlSource.Items.AddRange(sources);
    var defaultSource = sources.SingleOrDefault(o => o.Value == "A2");
    if (defaultSource == null)
        throw new ApplicationException("GetSourceOptions未包含A2項目");
    ddlSource.SelectedIndex =
        sources.ToList().IndexOf(defaultSource);

       
透過 SingleOrDefault() 用 Value 值找出預設選項的順序,既使查詢結果大風吹,它也能自動選對預設項目。要是資料庫裡的預設項目不知何故被他X的誤刪,這段程式還能明確指出問題出在"GetSourceOptions未包含A2項目",而不是噴出莫名其妙的 NullReferenceException,是不是貼心多了?

更進一步,如果某一天,預設值他X的要從 A2 改成 A3(對,那個規格書說永遠固定的A2),只能挖出程式碼重新編譯才能調整。於是我們還可以把預設值改成由 web.config appSettings 決定:

static string defaultSourceValue = 
    System.Configuration.ConfigurationManager.AppSettings["DefaultSource"] ?? "A2";
 
//...略...
 
    ddlSource.Items.Clear();
    var sources = GetSourceOptions()
        .Select(o => new ListItem(o.Value, o.Key)).ToArray();
    ddlSource.Items.AddRange(sources);
    var defaultSource = sources.SingleOrDefault(o => o.Value == defaultSourceValue);
    if (defaultSource == null)
        throw new ApplicationException($"GetSourceOptions未包含{defaultSourceValue}項目");
    ddlSource.SelectedIndex =
        sources.ToList().IndexOf(defaultSource);

醬子,連改掉預設項目都不用重新編譯程式呢,是不是好捧捧?

原本只要 2 改成 3 就可以交差,多寫幾百個字元是比較費工,但說穿了仍在舉手之勞的範圍,多花不到10分鐘讓程式材質從玻璃升級到不鏽鋼,很划算。

擦屁股是苦差事沒人愛,你可以衛生紙抹一下交差,也可以搬出免治馬桶座洗個痛快,有人還會順便把脈開藥治好烙賽,
即使做到何種程度與經驗能力相關,重要的是開發人員的心態。(Yo Yo,拎杯也有 Freestyle )

要成為別人眼中專業又可信賴的開發人員,先從擦得一手好屁股開始吧~

歡迎推文分享:
Published 09 October 2017 11:14 AM 由 Jeffrey
Filed under:
Views: 12,060



意見

# wing said on 17 October, 2017 04:03 AM

小小新手我

有時查資料 都會不自覺得查到黑大的blog裡

每次看文章都有種感動

希望能有天成為像黑大一樣的人

感謝黑大

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<October 2017>
SunMonTueWedThuFriSat
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication