Rider Refactoring 之 Extract Higher Order Function
Higher Order Function 是 JavaScript 或 FP 語言常見的語言機制,事實上 C# 也支援某種程度的 FP,如 Func、 Predicate 與 Action。
透過 Rider,我們也能將既有 method 重構出具 FP 風格的 Higher Order Function。
Version
macOS High Sierra 10.13.4
.NET Core 2.1
Rider 2018.1.2
Func
重構前
OrderService.cs
price
實務上會從 repository 而來,為了簡化起見,暫時 hardcore price
為 100
。
因為 全館八折
,所以 price
一律打八折。
Program.cs
Client 呼叫 OrderService.CalculatePrice()
。
重構後
OrderService.cs
目前 全館八折
的 price * 0.8
是寫死的,但考量折扣的算法常常會變,寫死並不是一個好方法,因此想將 price * 0.8
整個 expression 抽成 parameter。
- 選擇
price * 0.8
- 熱鍵
⌃ + T
,呼叫出Refactor This
- 選擇
Introduce Parameter
- 將下方的
Select parameters to enlambda
的 parameter 打勾,parameter 型別將為Func<double, double>
- 輸入 parameter 名稱為
discountStrategy
- 原來的
price * 0.8
重構成discountStrtegy(price)
discountStrategy
parameter 型別為Func<double, double>
Program.cs
- 原來的
price * 0.8
重構成price => price * 0.8
以 Lambda 形式傳入
Q : 這樣重構有什麼好處呢 ?
若將來折扣規則改成
全館折價 100
,只要傳入price => price - 100
即可,OrderService
完全不用修改,符合開放封閉原則
要求
Predicate
重構前
OrderService.cs
price
實務上會從 repository 而來,為了簡化起見,暫時 hardcore price
為 100
。
因為 滿千送百
,所以 price
超過 1000.0
會自動折價 100.0
。
Program.cs
Client 呼叫 OrderService.CalculatePrice()
。
重構後
OrderService.cs
目前 滿千送百
的 price > 1000.0
是寫死的,但考量折扣的算法常常會變,寫死並不是一個好方法,因此想將 price > 1000.0
整個 expression 抽成 parameter。
- 選擇
price > 1000.0
- 熱鍵
⌃ + T
,呼叫出Refactor This
- 選擇
Introduce Parameter
- 將下方的
Select parameters to enlambda
的 parameter 打勾,parameter 型別將為Func<double, bool>
- 輸入 parameter 名稱為
discountPredicate
- 原來的
price > 1000.0
重構成discountPredicate(price)
discountStrategy
parameter 型別為Func<double, bool>
理想上當然是抽成
Predicate<double>
,但目前 Rider 只能抽成Func<double, bool>
,但因為兩個型別等價,所以不滿意但可以接受
- 原來的
price > 1000.0
重構成price => price > 1000.0
以 Lambda 形式傳入
Q : 這樣重構有什麼好處呢 ?
若將來折扣規則改成
只有 1000 ~ 2000 之間有折扣
,只要傳入price => price > 1000.0 && price < 2000.0
即可,OrderService
完全不用修改,符合開放封閉原則
要求
Action
重構前
OrderService.cs
price
實務上會從 repository 而來,為了簡化起見,暫時 hardcore price
為 2000
。
因為過年活動 全館折價 100
,所以 price - 100
。
因為過年,所以特別顯示 Happy New Year!!
。
Program.cs
Client 呼叫 OrderService.CalculatePrice()
。
重構後
OrderService.cs
目前 Happy New Year!!
是寫死的,但考量不同節慶顯示不同訊息,甚至不同顯示方式 (呼叫其他 service ?),寫死並不是一個好方法,因此想將 Console.WriteLine('Happy New Year!!')
整個 statement 抽成 parameter。
- 選擇
Console.WriteLine('Happy New Year!!')
- 熱鍵
⌃ + T
,呼叫出Refactor This
- 目前並無
Introduce Parameter
可選,只剩下Extract Method
與Introduce Variable
,試試看先Introduce Variable
後再Introduce Parameter
- 若選擇
Introduce Variable
,則顯示Expression type is void
,無法繼續重構
- 這裡必須自己寫 code,先自行宣告
Action
型別變數,將Console.WriteLine('Happy New Year!!')
包在 Lambda 內,最後再用Action.Invoke()
執行action
- 選擇
action
變數 - 熱鍵
⌃ + T
,呼叫出Refactor This
,選擇Introduce Parameter
- 已經抓出 parameter 型別為
Action
- 輸入 parameter 名稱
- 原來的
Action action = () => { Console.WriteLine("Happy New Year!!"); };
重構成action
parameter action
parameter 型別為Action
- 原來的
Action action = () => { Console.WriteLine("Happy New Year!!"); };
重構成() => { Console.WriteLine("Happy New Year!!"); }
以 Lambda 形式傳入
Q : 這樣重構有什麼好處呢 ?
若將來顯示訊息改變,甚至呼叫其他 service 顯示,只要傳入
()=> {}
即可,OrderService
完全不用修改,符合開放封閉原則
要求
Conclusion
- 隨著 C# 支援 FP 越來越完整,Extract Higher Order Function 的需求會越來越多,而 Extract Method 卻無法抽出 Higher Order Function,必須靠 Introduce Parameter,才能抽出 Func、Predicate 與 Action
- 目前 Rider 無法直接抽出 Predicate,所幸 Predicate 也都可以用 Func 表示,雖不滿意但尚可接受
- 目前 Rider 無法直接抽出 Action,必須搭配一些小技巧,才能順利抽出 Action
Sample Code
完整的範例可以在我的 GitHub 上找到