您的瀏覽器不支援JavaScript功能,若網頁功能無法正常使用時,請開啟瀏覽器JavaScript狀態
Antfire 的生活雜記
Skip
    • 首頁
    • Blog
    • 重構心法之函式擷取術(Extrac...

    重構心法之函式擷取術(Extract Function)

    函式擷取術 (Extract Function)

    函式擷取術 (Extract Function)是程式碼初階重構作業中會使用到的流程之一。
    主要內容是將一段程式碼,了解它的工作內容後,將這段程式碼取出來,放入一個函式,並為這個函式取一個有意義的名稱,讓人一看就知道這個函式主要在做什麼。

    手術流程

    1. 建立一個新的函式,並且幫它取一個有意義的名字,讓人了解這個函式到在做什麼
    2. 將要擷取的程式碼複製到新建好的函式中
    3. 檢查一下這段程式碼,哪些區域變數會被上層的函式參考;那些區域變數只會在這段擷取的程式碼被參考
      如果有被上層的函式參考的情況下,將該區域變數變成函式的參數
      如果沒有,則將該區域變數挪到新的函式內
    4. 編譯
      處理完擷取出來的程式碼中所有的變數後,就可以進行編譯,檢查還有哪些變數沒有被處理到的
    5. 用新的函式取代原本要解取的程式碼
    6. 測試
    7. 找看看有沒有其他地方類似或相同的程式碼,並用新的函式取代

    範例

    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();

     Comments