關於程式碼巢狀的思考 | Thoughts about code nesting

波動拳!!,圖片來源
前言
“ 如果你需要三層以上的縮進,那麼你的代碼已經有問題了,應該修正你的程式。 ”
實習一年,出社會一年,雖然只有短短兩年,但因為前後維運了兩個年齡只小我一輪的產品,看到許多巢狀的非常嚴重的程式碼,也就是俗稱的波動拳。
因為是寫 C# ,是一套有 if-else、switch、try-catch、using 的組合拳,然後通常波動拳會跟千百字長文合體,對你展開無量空處。
最可悲的是,一開始接觸只會有一定是我看得不夠熟的心態,說是也不是,畢竟還有可讀性這種東西。
然而當我意識到可讀性時,我跟大部分人一樣,將設計模式視為萬能藥一般,頂禮膜拜,策略模式、狀態模式、模板方法模式,當然還有我們最愛的工廠模式系列,畢竟寫物件導向,必讀GOF的設計模式,那是經典中的經典。
但設計模式真的有這麼神嗎? 來談談我對於程式碼巢狀的思考之旅。
探索
開始分享心得前先疊個甲,我說的不一定是對的,只是分享一下心得,有任何錯誤與模糊的地方歡迎在下方留言討論。
設計模式
“ 我自稱DP哥 工廠模式COMBO策略模式 很常用的。 ”
- 遇到if-else跟switch直接套個簡單工廠加策略模式。
- 遇到物件有狀態直接套個狀態模式。
- 物件初始化直接套個工廠模式。
這邊套一個策略,那邊套一個工廠,再建個介面,一頓操作猛如虎。過度設計又怎樣 ? 我這是未雨綢繆 ! 程式非常聰明與彈性,覺得自己是個天才,然後維護就爆了。
非常聰明的程式只有非常聰明的人能夠維護,Keep it simple, Stupid ,設計模式在解決未知性的同時將物件間的交互變得間接,進而降低了易讀性。很多 IDE 能快捷鍵查看函式定義,但使用設計模式後,你只會看到介面定義。

我本來沒有想要使用這招的,圖片來源
設計模式在解決變化點的同時,增加了複雜度,那如果我將設計模式作為我本來不想使用這招的時候使出的招式,那麼我對付巢狀結構的手段還有哪些呢?
有毒的通用
“ 这种复用其实是有害的。如果一个函数可能做两种事情,它们之间共同点少于它们的不同点,那你最好就写两个不同的函数,否则这个函数的逻辑就不会很清晰,容易出现错误。 ”
複用是必須的,但有些複用是有害的,有沒有遇過一種情形,一段程式每個不同的情境都會經過某個函數,點進去後發現,它針對每個情境作不同處理,為甚麼呢 ? 因為他們可能都要做某個步驟。
void GenerateReport(ReportType type)
{
if(type == ReportType.PDF)
{
a();
b();
}
else if(type == ReportType.WORD)
{
c();
d();
}
// ... 其他選項
z();
}
更慘的情況是,進去這個通用的函式後,它的裡面還有更多通用的函式,此時該函式為有毒的通用,擱這套娃呢。而更好的方法是,分成多個函式。
void GeneratePDF()
{
a();
b();
z();
}
void GenerateWORD()
{
c();
d();
z();
}
Early Return
“ 概念是讓程式碼盡早的完成任務,避免過深的巢狀導致閱讀的不易。 ”
邏輯正向時會有多層巢狀,那反向是否能夠化解 ? Early Return或Guard Clause 就是在描述這個概念。以下是用正向邏輯撰寫領錢的敘述。
void foo(){
// 如果銀行有開
if(bank.isOpen)
{
// 如果排隊的人很少的話
if(waitInLine.FewPeople)
{
// 我的銀行餘額大於 3000
if(account.balance > 3000)
{
// 提款 3000
account.Withdraw(3000);
}
}
}
}
啪的一下,很快的三層縮排馬上不講武德的招呼到你臉上,但如果我們使用 Early Return 的概念,使用反向邏輯呢 ?
void foo(){
if(!bank.isOpen)
{
return;
}
if(!waitInLine.FewPeople)
{
return;
}
if(account.balance > 3000)
{
account.Withdraw(3000);
}
}
看來 Early Return 是我們的解方,但Early Return 假設了一種,幾乎沒有 else 的情況,更多時候else也是有它的邏輯的,那怎麼辦呢?
小而精美的模塊
“ 一个模块应该像一个电路芯片,它有定义良好的输入和输出。实际上一种很好的模块化方法早已经存在,它的名字叫做函数 ”
問題或許不是那層層堆疊的巢狀,問題或許是跟永樂大典一樣長的函式,那個函式或許達成了跟它名字一樣的功能,但它不該這麼長。
將部分步驟定義輸入與輸出,作為一個函數抽出去,最重要的是,給該函式合適的名字。
在抽函數時,你大概會發現兩件事- 某個參數跟他會用到的地方竟然差了一兩百行,而且他還會七十二變。
- 某個參數在另一條 else 線根本不會用到。
重構不只是像收納一樣,單純把 A 放到 B,而是能讓你重新思考功能的數據流,並讓每一個區塊顯得獨立並且可讀。
有些 IDE 提供了預處理命令,可以折疊程式碼,但就像現實一樣,糟糕的事情不是閉上眼睛就會解決的。
那現在呢?
“Writing is easy. All you have to do is cross out the wrong words. ”
作為驗證的 Early Return,作為拆解流程的函數,好像這兩把刀能解決巢狀結構的問題,但還是必須注意,Early Return 會以反向邏輯呈現,而函數要求你能精確命名,遇過一個函術名稱是
HandlingBillTradingCartSandCreditCS
看得出來它在幹嘛嗎 ? 我是看不出來。
所以找到了這趟解決巢狀程式碼的答案嗎? 我不知道,但我知道我有回答這個問題的想法了。