「又出錯了?」
「要不要直接叫入帳模組的人過來 debug?」專案負責人連夜打電話叫人上線支援,在會議室裡,我們經歷了一場場無止盡的檢討。
程式碼乾淨,設計模式正確,但現場卻一片混亂。那一刻我突然明白——漂亮的設計模式,恰恰造成了營運的混亂。
這是我工作上實際遇到的困難,為了保護公司的商業機密,我用一個虛構的例子來說明:工程師認為構思良好的設計模式為何在實際商業環境中寸步難行。
有一家大型餐廳開發了訂餐系統,支援3種點餐方式:
- 內用點餐
- 外帶點餐
- 外送點餐
這3種點餐方式在流程上有一些共通步驟,像是點餐、付款,但也有各自不同的業務規則,比如內用需要指定桌號、外帶需要指定取餐時間及外送需要聯絡地址。
由於這3種點餐方式僅有少部分業務處理邏輯不相同,因此資訊部決定使用工廠模式進行開發。
工廠模式 Factory Design
工廠模式是一種建立物件的設計模式,它的核心目的是:整理原先分散在各處負責實體化物件的代碼,將其集中於工廠管理,讓工廠就能決定實體化物件類別。使用工廠模式可以避免各點餐方式共有業務邏輯(像是付款)被重複撰寫,也便於未來新增不同的訂單類型(比如說外膾)。
基於這個概念,資訊部工程師建構了訂單工廠 OrderFactory 跟 4個代表訂單的類別,分別為:
- 用於定義方法的訂單模板(Order)
- 內用訂單(EatInOrder)
- 外帶訂單(TakeAwayOrder)
- 外送訂單( DeliveryOrder)。
參考上圖,訂單模板 Order 中,有3個方法:
- confirm 確認訂單
- payment 付款
- orderRule 訂單檢查規則
內用訂單(EatInOrder)、外帶訂單(TakeAwayOrder)和外送訂單(DeliveryOrder)都繼承了訂單模板(Order)。目前假定訂單跟付款是共用邏輯,不因訂單類型而變,所以在子類別的3個訂單類別中,就不再複寫。但各類型訂單的檢查規則不同,如分別需要桌號、取餐時間及外送地址,所以在子類別中各自實作邏輯。
interface Order {
default void confirm(){}
default void payment(){}
boolean orderRule();
}
class EatInOrder implements Order{
@Override
public boolean orderRule() {
boolean hasTableNo = true;
return hasTableNo;
}
}
class TakeAwayOrder implements Order{
@Override
public boolean orderRule() {
boolean hasTakeTime = true;
return hasTakeTime;
}
}
class DeliveryOrder implements Order{
@Override
public boolean orderRule() {
boolean isAddressValid = true;
return isAddressValid;
}
}
而所有訂單的最終檢查,例如付款核對,由於規則不因訂單類型而異,因此工程師將代碼集中在一個「訂單驗證類別(OrderValidator)」裡共同處理。
class OrderValidator {
public boolean validate(Order order){
boolean commonProcessResult = true;
return commonProcessResult;
}
}
看起來既合理又很漂亮,對吧?但是上線後debug是場惡夢......
營運管理的痛點
想像一下,這是一個大型系統,3種訂單類型各自成為一個模組,訂單驗證則獨立成一個模組,而每個模組都有1個工程師負責維護。
當訂單桌號錯誤時,內用組工程師需要與中央驗證模組工程師協力檢查。
當取餐時間錯誤時,外帶組工程師需要與中央驗證模組工程師相互配合。
當外送地址錯誤時,外送組工程師需要與中央驗證模組工程師共同確認。
也就是說,訂單正確與否的壓力,最後都會累積在驗證模組團隊。當訂單大量產生時,bug也會一一浮現,但中央驗證模組人力有限,無法對每個問題都即時反應,內用、外帶、外送訂單的bug修復也隨之癱瘓。
工程師從「邏輯共用性」出發,設計出看似完美的中央驗證系統;但實際上,這讓所有的營運壓力集中在一組人身上,成為維運效率的瓶頸。
解決方法:封裝
即使訂單驗證的業務邏輯在每種訂單類型都相同,也將其回歸到訂單類別。
interface Order {
default void confirm(){}
default void payment(){}
default boolean validate(){
boolean commonProcessResult = true;
return commonProcessResult;
}
boolean orderRule();
}
訂單驗證類別則封裝了訂單(Order)類別的validate方法。
class OrderValidator {
public boolean validate(Order order){
return order.validate();
}
}
於是,訂單各組的工程師,可以在自身負責的模組內檢查驗證邏輯;訂單驗證模組的工程師,也不需要在短期內消化大量的bug。如果未來有天不同訂單種類需要不同的驗證邏輯,這樣讓訂單類別自行管理驗證邏輯的方式,也保留了修改的彈性。
interface Order {
default void confirm(){}
default void payment(){}
boolean validate();
boolean orderRule();
}
class EatInOrder implements Order{
@Override
public boolean validate() {
boolean processResult = true;
return processResult;
}
@Override
public boolean orderRule() {
boolean hasTableNo = true;
return hasTableNo;
}
}
class TakeAwayOrder implements Order{
@Override
public boolean validate() {
boolean processResult = true;
return processResult;
}
@Override
public boolean orderRule() {
boolean hasTakeTime = true;
return hasTakeTime;
}
}
class DeliveryOrder implements Order{
@Override
public boolean validate() {
boolean processResult = true;
return processResult;
}
@Override
public boolean orderRule() {
boolean isAddressValid = true;
return isAddressValid;
}
}
設計程式時,不僅要考慮某段邏輯是否共用,也要根據營運的人力分配作對應的處理。
畢竟,程式寫得再漂亮,一線撐不住也是徒然。