在軟體開發中,為了不重複、解耦合、易維護等各種彈性需求,程式碼必然地都會有一定程度往抽象化發展,然而,這是一把雙面刃,也容易形成兩個極端:抽象化的極端擁護者,以及抽象化的極端反對者。
複製、貼上,好棒棒!
無論是結構化程式設計、物件導向,或者函數式等典範,無論是狹義的設計模式或廣義的各種模式,只要程式碼是為了追求涵蓋更多的需求,程式碼必然都會朝向一定程度的抽象化發展,一個最簡單的例子排序,若有針對整數排序的sortInt()與字串排序sortStr()兩個函式,其中使用了相同的排序演算法,其程式碼內容必然相近,若因此而定義了一個通用的sort()函式,相對sortInt()、sortStr()來說,sort()函式就是沒那麼具體!
然後,某天程式碼審查(Code review)時,某位主管看到sort()就開飆了:「你寫這個是三小?sortInt()、sortStr()不是用的好好的嗎?」,你辯解著:「可是這樣排序日期或其他物件也可以用sort()啊!」,主管繼續開飆:「你是不會複製、貼上寫個sortDate()嗎?你這樣會害我還要追到sort()才知道它在幹嘛!」
以上是個杜撰的故事,不過可以確信地,身邊一定有人或者是你本身,就遇過類似的情況。遇上極端的複製、貼上擁護者,對他們來說,任何的典範、手法,只要會朝向抽象化,一概反對,理由大概不乏是「書上的東西不能拿來現實中用」、「這樣會影響效能」、「其他人看不懂怎麼辦」、「沒站在使用者的角度想」,或者態度上表現出「老子就是對的」的其他各種理由。
如果搬出可維護性的大旗,提出「複製、貼上程式碼的方式,將來需要修改時,會需要改很多地方」的類似理由,複製、貼上的另一組好夥伴就會上場救援,像是「你是不會搜尋、取代嗎?」
抽象之惡
撇開程式碼審查被拿來當成鬥爭工具的情況,不良的抽象化確實有著不好的名聲。在《約耳趣談軟體》的〈別讓架構太空人嚇到你〉中,一開始就是個鮮明的案例:「他們看到人們要傳送文書處理檔案給別人,又看到人們要傳送試算表給別人,然後就會發現裡面有一個通用的模式:傳送檔案。這已經是一層的抽象。然後他們會再上一層……現在這件事變得愈來愈神秘,再也沒人真的懂他們在說些什麼。」
其實這個案例還算好的,因為至少還是個自下而上的抽象化過程,真正可怕的是自上而下,這就好比公司大頭揭示未來的營運方針「我們要為使用者創造價值」之後,底下各級主管就各自再揣摩,定出各部門層級的抽象方針,如此一層一層地往下無根據地具體化,最後做出一個沒有真實感,甚至損及既有使用者價值的東西。
另一個著名的抽象之惡,就是〈抽象滲漏法則〉了,所有重大的抽象機制,在某種程式上都是有漏洞的,只是抽象滲漏的程度輕重不一而已,自上而下的抽象過程必然有著較大的滲漏,就算執行抽象化的人過去有著豐富的經驗,事先定義的抽象化,終究也是照顧著過去的需求,而不是現在的全部,也因此,任何既有的程式庫或框架一定會有不足之處,最後必然是不斷地加入新功能,終究使得框架臃腫,或者是出現另一個框架,來照顧不滿的另一群人。
就算是自下而上的抽象化過程,基於隱藏複雜度的抽象化,也會引起抽象滲漏。舉例來說,為了隱藏一個物件建立時的複雜度,而採用了工廠方法,就目前來說,這個工廠創建出來的物件是符合需求的,然而,將來終會遇到不合用的場合,就像約耳說的:「總會有一天我們得去追查因抽象滲漏而產生的問題,到時候就得查上兩星期了」,如果這發生在當客戶火燒屁股電話追殺的時候,也難怪會有主管那麼厭惡抽象化了。
重構與分層次地抽象化
毫無根據地抽象,最後會導致架構太空人,最好的方式是基於重構,如此演化出來的抽象化有其根據,如果程式碼有適當地版本控制或文件,也易於追查原始來源。
在《重構》這本書中也會看到,重構不見得一定是抽象化的過程,必要時會朝反方向,無論是為了去除不必要的間接呼叫、委託或者繼承的複雜度等。重構就是基於程式碼的現況,讓程式碼進行抽象化,甚至是解除抽象化,必要時基於需求來解除抽象化,總比一開始就一昧擁抱複製、貼上、搜尋、取代來得好吧!
除了可避免架構太空人之外,基於重構的抽象化,也才有機會建立屬於自己的一套架構邏輯,當必要進一步瞭解抽象化背後的實際機制時,也才容易掌握。例如,在JavaScript界有個虛構的Vanilla JS框架,實際上並不是真的有這麼一個框架,你可以說它是個嘲諷,也可以說它是個鼓勵――鼓勵開發者弄懂抽象原理的來源,以及所隱藏的東西。
至於分層次地抽象化是指,你可以提供一種方式來隱藏複雜度,用以應付大多數的需求,然而,仍保留一定的管道,讓開發者在必要時可以循著它,探索、掌握細節。
舉個例子來說,Python中,每個模組是一個功能概念,各模組往往會提供一些工廠函式(例如open()),協助建構常用的物件或處理常見需求,然而,模組中,也會公開工廠函式實作時,所使用到的相關函式與型態(例如io模組就是如此),它們的抽象層次更往下一層,使用起來更為複雜,然而卻能用來掌握更多的細節,有時,模組中細部的函式與類別,會引用另一更低層次的模組(例如io模組使用了os模組),一旦習慣了這樣的抽象層次規則,在運用時,就能在方便性與複雜度掌握之間,取得一個平衡。
其他人看不懂怎麼辦?
「其他人看不懂怎麼辦」是個偉大的理由,我曾親耳聽到客戶講出來許多次,更別說網路上三不五時看到的開發者靠背或抱怨了。看不懂的原因之一,也許是資料不足,而前面談到,基於重構的抽象化(或解除抽象化),分層次地抽象化,有機會建立屬於自己的一套架構邏輯,這套架構邏輯若有一定的記錄與傳承,應可協助團隊成員必要時快速進入狀況。
當然,記錄傳承與學習需要成本,正如約耳曾經談到的:「抽象機制雖然替我們節省了工作的時間,不過學習的時間是省不掉的」,如果只是為了節省記錄傳承與學習的成本,而摒棄合理的重構或抽象化,必然會在許多需要重複進行修正動作的地方,一點一滴地浪費掉更多的時間。
看不懂的原因之二,也許確實是團隊成員程度不夠,考量團隊整體水平,決定使用某套方法或技術進行開發是必要的,不過,若只是單純地以「其他人看不懂怎麼辦」的理由,通殺各種合理的重構或抽象化,極端擁抱複製、貼上,這感覺就像宣稱:寫程式時根本用不到資料結構、演算法、數學一樣地畫地自限。
而且,也像是在暗示著一個訊息:「我的團隊成員程度就是這麼爛,爛到只能規定他們只能用複製、貼上」,至於為何找來的人程度都這麼差,就讓人有著許多的想像空間了!
專欄作者
熱門新聞
2024-12-24
2024-12-22
2024-12-20
2024-08-14
2024-11-29