# Study Group - Refactoring (P125~P155) ### Extract Function ```js function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(); //print details console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); } ``` Refactor to: ```js function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(); printDetails(outstanding); function printDetails(outstanding) { console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); } } ``` **Motivation** * 程式長度(螢幕長度)...🤔 * 復用性(reuse)...🤔 * 意圖與實作分離(separation between intention and implementation)🤩 * 白話來說,**需要花時間理解的程式碼就應該拉成函式** **Mechanics** 1. 建立一個新函式(name it by what it does, not by how it does it) 1.1 如果程式語言有支援 nest function,可以減少確認後續的作用域變數(out-of-scope variables) 1.2 nest function 未來可以用 `Move Function(232)` 來處理 2. 複製程式碼到新函式中 3. 檢查變數作用域(呼應1.1),用 `arguments` 傳入或者拉進函式內 4. 處理變數後,進行編譯(compile-time checks) 5. 改呼叫新函式 6. 測試 7. 尋找與剛剛提取出來相同或類似的程式碼來置換 - `Replace Inline Code with Function Call(260)` 7.1 有些重構工具有支援 ``` 個人想法: 沒有工具支援的話,第七點執行起來是有難度的,頂多同個 File 內掃過或者成為關鍵字達人 討論: VS code & WebStorm 其實都有相關的功能,只是 VS code 比較陽春😂 ``` **討論** 大家對 Nest function 的習慣是 `const foo = () => {}` 還是 `function foo (){}`呢? 大家的觀點 * 前者會需要都寫在上面,後者可以讓程序碼的表達力更強 * TypeScript 用 `const foo = () => {}` 的話,如果是呼叫 3rd-party 的 function,可以少寫 type 🤩 **Example** ```js function printOwing(invoice) { let outstanding = 0; printBanner(); // calculate outstanding for (const o of invoice.orders) { outstanding += o.amount; } // record due date const today = Clock.today; invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30); //print details console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); console.log(`due: ${invoice.dueDate.toLocaleDateString()}`); } ``` **Example: No Variables Out of Scope** 沒有難度,直接抽就是了 ```js function printOwing(invoice) { printBanner(); } function printBanner() { console.log("***********************"); console.log("**** Customer Owes ****"); console.log("***********************"); } ``` **Example: Using Local Variables** 用參數傳遞 ```js function printOwing(invoice) { printDetails(invoice, outstanding); } function printDetails(invoice, outstanding) { console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); console.log(`due: ${invoice.dueDate.toLocaleDateString()}`); } ``` **Example: Reassigning a Local Variable** 運用 `Split Variable(260)` 與 `Slide Statements(262)` * 看到對 paramter 賦值,會馬上用 `Split Variable(260)` * 用 `Slide Statements(262)` 把變數拉到要被抽取的程式碼旁 ```js function printOwing(invoice) { let outstanding = 0; printBanner(); // calculate outstanding for (const o of invoice.orders) { outstanding += o.amount; } } ``` ```js function printOwing(invoice) { printBanner(); // calculate outstanding let outstanding = 0; for (const o of invoice.orders) { outstanding += o.amount; } } ``` ```js function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(invoice); } function calculateOutstanding(invoice) { let outstanding = 0; for (const o of invoice.orders) { outstanding += o.amount; } return outstanding; } ``` ```js function printOwing(invoice) { printBanner(); const outstanding = calculateOutstanding(invoice); } function calculateOutstanding(invoice) { let result = 0; for (const o of invoice.orders) { result += o.amount; } return result; } ``` **如果要把函式提取到其他環境(例如 top level)?** Fowler 喜歡一步一步做,先提取成 nest function,再拉到新環境。 不過就常遇到變數的問題,也許放在同層會更好😏 ### Inline Function ```js function getRating(driver) { return moreThanFiveLateDeliveries(driver) ? 2 : 1; } function moreThanFiveLateDeliveries(driver) { return driver.numberOfLateDeliveries > 5; } ``` Refactor to: ``` function getRating(driver) { return (driver.numberOfLateDeliveries > 5) ? 2 : 1; } ``` **Motivation** * 實作(內容)已經夠清楚了 * 對一堆分解很差的函示們用 * 太多間層 ``` 個人想法: * 範例的 function,用 Change Function Declaration(147) 也是一個選擇 * 重新打成一包,再重新組裝 ``` **Mechanics** 1. 確定方法不是多型(polymorphic method) 2. 找出所有的 callers 3. 替換內容 4. 每次替換都測試 5. 移除 function definition **Example** 如果很複雜的形況,可以使用 `Move Statements to Callers(254)` ```js function reportLines(aCustomer) { const lines = []; gatherCustomerData(lines, aCustomer); return lines; } function gatherCustomerData(out, aCustomer) { out.push(["name", aCustomer.name]); out.push(["location", aCustomer.location]); } ``` 第一次 ```js function reportLines(aCustomer) { const lines = []; lines.push(["name", aCustomer.name]); gatherCustomerData(lines, aCustomer); return lines; } function gatherCustomerData(out, aCustomer) { out.push(["location", aCustomer.location]); } ``` 第二次 ```js function reportLines(aCustomer) { const lines = []; lines.push(["name", aCustomer.name]); lines.push(["location", aCustomer.location]); return lines; } ``` ### Extract Variable ```js return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100); ``` Refactor to: ```js const basePrice = order.quantity * order.itemPrice; const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; const shipping = Math.min(basePrice * 0.1, 100); return basePrice - quantityDiscount + shipping; ``` **Motivation** * 難以閱讀的運算式(Expressions * 好處:提供 debugging 的 hook * 考慮 context * 只在這個函式使用,用這招很適合 * 會在很多地方用到,或者用 `Extract Function(126)` 比較好 **Mechanics** * 確保 Expression 沒有 side effects * 宣告 immutatable 的變數,並把 Expression 的 copy 指派給它 * 置換 * 測試 **Example** 看上面 **Example: With a Class** 由於整個 Order 都在使用,所以用 function 會比 variable 好 而 Class 的優點可以讓我們用 function 的實作方式,但是對外卻是用 自己名稱(類似變數)提供。 ```js class Order { constructor(aRecord) { this._data = aRecord; } get quantity() {return this._data.quantity;} get itemPrice() {return this._data.itemPrice;} get price() { return this.quantity * this.itemPrice - Math.max(0, this.quantity - 500) * this.itemPrice * 0.05 + Math.min(this.quantity * this.itemPrice * 0.1, 100); } } ``` ```js class Order { constructor(aRecord) { this._data = aRecord; } get quantity() {return this._data.quantity;} get itemPrice() {return this._data.itemPrice;} get price() { return this.basePrice - this.quantityDiscount + this.shipping; } get basePrice() {return this.quantity * this.itemPrice;} get quantityDiscount() {return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05;} get shipping() {return Math.min(this.basePrice * 0.1, 100);} } ``` ### Inline Variable ### ```js let basePrice = anOrder.basePrice; return (basePrice > 1000); ``` Refactor to: ```js return anOrder.basePrice > 1000; ``` **Motivation** * Expression 已經夠清楚 **Mechanics** * 確保 assignment 的右邊沒有 side effects * 如果變數還沒有被宣告成 immutable,先這樣宣告並測試 * 用來確認這個變數只被 assign 一次 * ... ### Change Function Declaration ### 亦即 `Rename Function` ``` function circum(radius) {...} ``` Refactor to: ``` function circumference(radius) {...} ``` **Simple Mechanics or Migration Mechanics** 在兩個狀況使用 Migration Mechanics * 函式很複雜 * 可以用在 Published API **Migration Mechanics** ```js function circum(radius) { return 2 * Math.PI * radius; } ``` ```js function circum(radius) { return circumference(radius); } function circumference(radius) { return 2 * Math.PI * radius; } ``` ```js function circumference(radius) { return 2 * Math.PI * radius; } ```