# 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;
}
```