需求的擴充及變化,幾乎是軟體開發中不變的現象。面對這樣的狀況,設計者若是嘗試著預見未來,提前做好相關的因應措施,一次便將滿足需求的設計到位,便很容易落入過度工程化(over-engineering)的陷阱當中。
試著一次到位的心態,是產生了問題,然而,面對許多設計者所不願處理的需求擴充及變化,我們還曾經給過以下的建議:「讓你的軟體架構,具有好的體質,足以快速的反應。具有彈性,又不會過度擴張彈性。」,「初期只有基礎的彈性,每次變化後,都再加強一個等級的彈性,使得它足以通過下一次變化的考驗。」,其實,這樣的建議想表達的,便是讓你的程式碼持續保持演化的能力。
真實世界中的生物,必須面對環境的變遷,進而持續演化。但是,沒有任何一種生物,能夠預知千萬年後世界的風貌,使得自己在最初的時候,就變成最適合未來的長相。因為,最適合未來的,不意謂著最適合當下。一直隨著環境動態地演化,才能保持在最適合當下的狀態。
最初的設計需適當地加以預估
程式碼所面對的需求變更,就好像生物所面對的環境的變化,只有持續演化,才能讓程式碼不斷保持在最合適的狀態。因此接下來,我想試著探討程式碼的演化之路。
程式碼的變化,基本上有幾大類:一、擴充;二、變異;三、萎縮。
擴充指的是為系統擴充新的功能,使得它具備更多的能力。變異指的則是修改系統現有功能的行為,使得它改變固有能力的展現方式。萎縮,指的則是將系統現有的功能予以廢棄,之後不再使用,這形同削減了系統的既有能力。
當我們遭遇到上述的三種變化時,我們應當如何因應呢?
在談如何因應這三種變化前,我們應當先來談談「如何做出最初版的設計」。最初版的設計,無疑地,必須符合最初的需求。
設計必須滿足需求,這是最基本的。但我們的設計還必須提供更多的可能性。許多設計者會在最初版的設計就遭遇到困難,因為他們的腦海裡,一心只想要解決眼前的問題,也就是滿足自己所看到的這一份需求規格。運氣好的時候,需求不動如山,「能捉老鼠的就是好貓」,可以滿足需求的,就是好設計。但是,很少會有這種運氣──不論專案或產品的開發,需求的變更,皆屬兵家常事,所以在做最初版的設計時,就必須先預測需求下一步會往哪裡變化。例如,在開發一個電子商務網站時,雖然最初的需求規格,僅提到要連接某一個付款的閘道(payment gateway)。但是,從對專案的背景及氣氛,甚至是客戶的預告,有時可以嗅出未來必須支援其他付款閘道的可能性。
一名好的設計者,在做最初的設計時,雖然只會實作連接單一付款閘道的機制,但是,他會在設計中暗自埋下伏筆──預留擴充的空間。使得他日一旦連接其他付款閘道的新需求正式納入規格時,就可以輕易地加上另一臺付款閘道的連接機制,卻同時又對現有程式碼的影響很小。
如何辦到上述的設計呢?舉例來說,當你已經預知付款閘道,可能要同時支援多臺而不只是單臺時,你可以選擇用一個介面來表示付款閘道的功能,而以一類別實作立即必須支援的付款閘道介面。假如要用這臺付款閘道,你可以有很多選擇,最簡單的方式是直接產生該類別的實例,但將它轉型成為介面後再使用。好比:
PaymentGateway pg = (PaymentGateway) new PaymentGatewayImpl1();
你也可以考慮在此時,便引入一個Simple Factory Method的設計模式,直接讓程式碼與PaymentGatewayImpl1完全無關。
PaymentGateway pg = PaymentGatewayFactory.create();
倘若採取第一種寫法︰當確定要加入第二個付款閘道的機制時,程式碼的變動就會比較多。因為你得逐一找出程式碼中,所有利用new去產生PaymentGatewayImpl1的地方,然後改以泛工廠族系的設計模式,來產生具體的付款閘道實作。不過,有個優點是,所有使用PaymentGateway介面的程式碼都不會影響到,而這正是先前在「類別物件的生成」一文中所提到的。
倘若採取第二種寫法︰確定要加入第二個付款閘道的機制時,程式碼的變動較少。因為只需要擴充PaymentGatewayFactory的內容,其餘程式碼毋需變動,變動的幅度因而更小了。
從已知的情境去推估,謹慎控制準備的規模
從這個例子,你可以觀察出來幾個要領。
首先,雖然要避免過度工程化,卻意謂著我們還是得預測未來,但並非全面的想像。我們在演化程式碼時,儘管不要求要精確的認知未來,卻需要試著從大方向上去察覺趨勢。我們不確定會加入什麼付款閘道,甚至難以確定究竟客戶會不會真的想加入,所以,我們提前做了一些準備,可是這準備又沒有做足。
不立即做,是因為不想將力氣浪費在也許不可能發生的事情上,但我們仍舊要做一些準備,是因為必須考慮到這事會發生的可能性。
好比上例中的設計,我們沒有真的實作出所有的付款閘道機制,在第一種設計中,我們甚至連Simple Factory Method都省去了(雖然省去Simple Factory Method節省不了多少力氣,不過在此例中是為了突顯預先做準備,還是會產生投入程度的差異)。但是,這樣的準備,就足以讓我們確定一件事──要支援其他付款閘道時,對自身的程式碼衝擊應該控制到最低的程度。這便是我所謂的「為下一次的變化做準備」。我們並不是直接就變成那樣,但是妥善地準備,盡可能將節省額外再投入的力氣。
準備多寡與花在變更的力氣大小成反比
上例中,我們同樣是試著為下一次的變化做準備,方式卻有兩種不同等級的差異。有時,我們會選擇花較少的力氣、準備少一點,等到需求確實發生時,在變更上再多花一點力氣。有時,我們會選擇花比較多的力氣,多做準備,這麼一來,倘若需求確實發生時,花在變更的力氣就會少一些。
事實上,這中間或許存在多個不同的層次,如何選擇呢?這端視設計者對需求發生可能機率的評估。倘若設計者認為機率偏高,那麼就可以考慮多做準備。反之,準備少一點也無妨。因為機率多半沒有任何量化的標準,設計者不妨選擇C/P比最高的準備方式,選擇初期投資(預先做的設計準備)以及未來成本(需求變化造成的衝擊)的最佳平衡。或許這是一場設計者在開發過程中的賭局吧!
在每一階段,設計者都應該試著替下一個階段作打算,儲備變化的能量,盡量讓一個階段與下一個階段之間保持小幅度的調整,記住!不要一次引入過於巨大的變化,讓程式碼往正確的方向演化,做法上將更靈活,不必多走冤枉路。
作者簡介:
王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。
熱門新聞
2025-01-26
2024-04-24
2025-01-25
2025-01-26
2025-01-27
2025-01-24