近藤麻理惠:怦然心动的程式整理魔法
不知道有沒有人在看近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();
}
常見雜亂的程式:
- 重複的程式
- 超長的函數
- 巨Class
- 超多的參數
- 散彈程式:新功能修改需要更改很多部分的程式
情況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件事:
- 找出基本總價格:數量*單價
- 如果購買超過1000個商品就給你多一點折扣吧
- 最後找出折扣後的價格。
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,可以捐贈我一杯大熱美(大杯美式咖啡)支持我。