函式擷取術 (Extract Function)
函式擷取術 (Extract Function)是程式碼初階重構作業中會使用到的流程之一。
主要內容是將一段程式碼,了解它的工作內容後,將這段程式碼取出來,放入一個函式,並為這個函式取一個有意義的名稱,讓人一看就知道這個函式主要在做什麼。
手術流程
- 建立一個新的函式,並且幫它取一個有意義的名字,讓人了解這個函式到在做什麼
- 將要擷取的程式碼複製到新建好的函式中
- 檢查一下這段程式碼,哪些區域變數會被上層的函式參考;那些區域變數只會在這段擷取的程式碼被參考
如果有被上層的函式參考的情況下,將該區域變數變成函式的參數
如果沒有,則將該區域變數挪到新的函式內 - 編譯
處理完擷取出來的程式碼中所有的變數後,就可以進行編譯,檢查還有哪些變數沒有被處理到的 - 用新的函式取代原本要解取的程式碼
- 測試
- 找看看有沒有其他地方類似或相同的程式碼,並用新的函式取代
範例
function printOwing(invoice) {
let outstanding = 0;
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
// 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()}`);
}
沒有範圍外的變數
以上面的程式碼為例,擷取沒有變數的程式碼很簡單,只要剪下、貼上,並且在程式碼中呼叫列印banner的函式
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()}`);
}
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
再來擷取列印細節的部分,可以這樣寫
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);
printDetails();
function printDetails(){
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
}
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
雖然這樣寫沒有問題,但如果今天寫的程式語言不支援巢狀函式的時候,就會有問題。
但把它拉到最上層的話,就要注意到有沒有使用到任何在上層函式中宣告的變數。
使用區域變數
以上面的程式碼為例,擷取函式的時候就順便帶兩個參數
擷取printDetails
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);
printDetails(invoice, outstanding);
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
擷取record due date
record due date 也是一樣的操作流程
function printOwing(invoice) {
let outstanding = 0;
printBanner();
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function recordDueDate(invoice){
// record due date
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
擷取 calculate outstanding
接下來將以手術流程來擷取
前置作業 (Slide statement)
function printOwing(invoice) {
printBanner();
let outstanding = 0;
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
calculateOutstanding
建立新函式function printOwing(invoice) {
printBanner();
let outstanding = 0;
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function calculateOutstanding(invoice) {
}
function recordDueDate(invoice){...}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {...}
將要擷取的程式碼複製到新建好的函式中
function printOwing(invoice) {
printBanner();
let outstanding = 0;
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function calculateOutstanding(invoice) {
let outstanding = 0;
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
return outstanding;
}
function recordDueDate(invoice){...}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {...}
用新的函式取代原本要解取的程式碼
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function calculateOutstanding(invoice) {
let outstanding = 0;
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
return outstanding;
}
function recordDueDate(invoice){...}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {...}
養成好習慣,整理一下變數名稱及變數型別(opt.)
function printOwing(invoice) {
printBanner();
const outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function calculateOutstanding(invoice) {
let result = 0;
// calculate outstanding
for (const o of invoice.orders) {
result += o.amount;
}
return result;
}
function recordDueDate(invoice){...}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {...}
作者表示,遇到函式需要回傳多個結果的時候,他的做法是將這需要回傳多個結果的函式拆開,例如:
let a = resultAFromFun();
let b = resultBFromFun();