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) discountStrategyparameter 型別為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) discountStrategyparameter 型別為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!!"); };重構成actionparameter actionparameter 型別為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 上找到