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

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

這類狀況跟修自己的 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 )

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


Comments

# by wing

小小新手我 有時查資料 都會不自覺得查到黑大的blog裡 每次看文章都有種感動 希望能有天成為像黑大一樣的人 感謝黑大

# by ByTIM

弱弱的問,為何 var defaultSource = sources.SingleOrDefault(o => o.Value == defaultSourceValue); 要用SingleOrDefault,用FirstOrDefault好像也可以,還是只是寫法習慣問題?

# by Jeffrey

to ByTIM, 用 SingleOrDefault() 的差別是萬一發生有兩筆以上相同 Value 程式會出錯,依程式邏輯這事不該發生,萬一發生程式會爆炸迫使人員進場處理,這是我選用 SingleOrDefault() 而非 FirstOrDefault() 的理由。

# by ByTIM

To Jeffrey: 原本查詢出超過一筆,會跳例外訊息喔! 目前遇到的VALUE值大部分都PK主鍵,或是會檢查VALUE有沒有重複。 所以沒遇過這問題,謝謝您的回答。

Post a comment