近藤麻理惠:怦然心动的程式整理魔法

不知道有沒有人在看近Netflix 的藤麻理惠:怦然心動的人生整理魔法。我覺得近藤麻理惠的語氣超療癒的。

圖片來自Netflix 節目, 藤麻理惠:怦然心動的人生整理魔法

然後某天我被Youtube 演算法推薦了一部影片講解如何手把手帶你清理雜亂不堪的程式。網址 我想起以前在實習時,講者與實習公司中的大大教我的差不多。所以決定寫一個中文版本來記錄一下。影片中的講者使用PHP作為範例語言,而我會用程式( Pyhthon / Javascript ) 來講解每一塊的問題。

Python 程式大概長這樣:self 是一個指向物件指標

    def update(self, age):
      student = Student.select().get(Student.name==self.name)
      student.age = age
      student.save()

JavaScript 程式大概會長這樣:this 是函數所屬的物件指標。

    function updateAge(age) {
      const self = this,
       student = students.find((s)=> {
        return self.age == s.age;
      });
      student.age = age;
      student.save();
    }

常見雜亂的程式:

  1. 重複的程式
  2. 超長的函數
  3. 巨Class
  4. 超多的參數
  5. 散彈程式:新功能修改需要更改很多部分的程式

情況1:

在函數中有部分程式塊與程式函數無關。有時候為了列印或呼叫,常常會重複呼叫同一個函數。這時這塊程式可能會增加其他人理解該函數的意義。

    def updateUserStats(stats):
      print("**************")
      print("***   Hi    ***")
      print("**************")
      requests.post('http://yourprofile/posts',body=stats)

將重複的部分趕出來,變成一個新的函數再呼叫回去就好了喔。

    def printHeader(text):
      print("**************")
      print("***  "+text+"    ***")
      print("**************")
    
    def updateUserStats(stats):
      printHeader("Hi")
      requests.post('http://yourprofile/posts',body=stats)

情況2

Inline parameter。 減少一次性參數使用,因為變數命名不好(而你多數都不會命名好的)會帶來混淆。

    def showPerson(self):
      isEmployable = self.canThisBeEmployed(self.age);
      if (isEmployable):
        print("This user is employable!")

拿掉它就好了嘛

    def showPerson(self):
      if (self.canThisBeEmployed(self.age)):
        print("This user is employable!")

情況3 :

避免magic number, 直接以 const, 全局變數來取代。因為Python 並沒有所謂的固定常數,這裡我們就換成以 JavaScript 為例子。我們可以看到某求職網站說合法薪金在 22000 到 1百萬之間。

    function isLegalSalary(salary) {
      if (salary >= 22000 & age < 1000000) {
        return true;
      }
      return false;
    }

如果某天政策更改了,最低薪已經上調了,老闆命令你修改合法薪金範圍。這時你要開啟文字搜尋還是直接修改第一行的最低薪常數呢?

const minLegalSalary=22000, maxLegalSalary = 1000000;//定義在第一行吧,直接修改就好了

function isLegalSalary(salary) {
  if (salary >= minLegalSalary && salary < maxLegalSalary) {
    return true;
  }
  return false;
}

情況4:

以拆分形式,降低函數邏輯的複雜度。可以看到以下的程式做了3件事:

  1. 找出基本總價格:數量*單價
  2. 如果購買超過1000個商品就給你多一點折扣吧
  3. 最後找出折扣後的價格。
    getBulkPrice(self, quality=1):
      basePrice = quatity*self.price;
      if (basePrice > 1000):
        discountFactor = 0.95;
       else:
        discountFactor = 0.98;  
      return basePrice*discountFactor;

從以上的函數中可以發現 getBulkPrice 最重要是求出大筆交易的價格,所以最重要的是給出折扣後的總價。所以我們可以將程式非核心功能( 找出基本總價格、折扣率 )拆出來。

    getBulkPrice(quantity=1):
      return self.basePrice(quantity)*self.discountFactor(self.basePrice(quantity))
    
    basePrice(self, quantity):
      return quantity*self.price;
    
    discountFactor(amt):
      return amt > 1000 ? 0.95: 0.98;

情況6: 用 break 來做簡單的 flag 控制。這樣可以省去定義flag參數。 情況7: 用 modifier 分開 query

    function checkSecurity() {
      # 查看有沒有黑名單
      let found=false;
      for(let i=0; i<this.customerList;i++) {
        if (!found) {
          if (this.customerList == "Don"){
            found=true;
          } 
          if (this.customerList == "John"){
            found=true;
          } 
        }
      }
      return found;
    }

將 flag 清除以後程式是不是看起來讓人怦然心動了呢?

    function checkSecurity() {
      # 清理掉
      for(let i=0; i<this.customerList;i++) {
          if (matchBlackList(this.customerList[i])){
            return true;
          }
      }
      return false;
    }
    
    function matchBlackList(name) {
      if (name == "Don" || name == "Jon"){
            alert(this.customerList[i]);
          return true;
      }
      return false;
    }

但是這裡有個小問題 matchBlackList 裡面做了一件叫 alert 的事情,這與函數名稱不太相符耶 。所以把alert 移回 checkSecurity 比較妥當一點。

    function checkSecurity() {
      # 清理掉
      for(let i=0; i<this.customerList;i++) {
          if (matchBlackList(this.customerList[i])){
            alert(this.customerList[i]);
            return true;
          }
      }
      return false;
    }
    
    function matchBlackList(name) {
      if (name == "Don" || name == "Jon"){
          return true;
      }
      return false;
    }

matchBlackList 可以進一步再縮短

    function matchBlackList(name) {
      return (name == "Don" || name == "Jon");
    }

重新 refactor 程式最重要的理念是:不更改程式行為下,更改程式的架構。所以為了迅速驗證 refactor 的正確性,完善的unit test 是非常值得花時間寫的。

如果你喜歡我的文章或side projects,可以捐贈我一杯大熱美(大杯美式咖啡)支持我。

Written on March 5, 2018