快哭了!我被同事寫的代碼坑慘了

寫出整潔的代碼,是每一個程序員的追求。 《clean code》指出,要想寫出好的代碼,首先得知道什麼是骯髒代碼、什麼是整潔代碼; 而後經過大量的刻意練習,才能真正寫出整潔的代碼。

圖片來自 Pexels

WTF/min 是衡量代碼質量的惟一標準,Uncle Bob 在書中稱糟糕的代碼爲沼澤(wading),這隻突出了咱們是糟糕代碼的受害者。html


國內有一個更適合的詞彙:屎山,雖然不是很文雅可是更加客觀,程序員既是受害者也是加害者。前端


對於什麼是整潔的代碼,書中給出了大師們的總結:程序員

  • Bjarne Stroustrup:優雅且高效;直截了當;減小依賴;只作好一件事web

  • Grady booch:簡單直接算法

  • Dave thomas:可讀,可維護,單元測試express

  • Ron Jeffries:不要重複、單一職責,表達力(Expressiveness)編程


其中,我最喜歡的是表達力(Expressiveness)這個描述,這個詞彷佛道出了好代碼的真諦:用簡單直接的方式描繪出代碼的功能,很少也很多。

命名的藝術後端


坦白的說,命名是一件困難的事情,要想出一個恰到好處的命名須要一番功夫,尤爲咱們的母語還不是編程語言所通用的英語。


不過這一切都是值得了,好的命名讓你的代碼更直觀,更有表達力。好的命名應該有下面的特徵:


①名副其實

好的變量名告訴你:是什麼東西,爲何存在,該怎麼使用, 若是須要經過註釋來解釋變量,那麼就先得不那麼名副其實了。

下面是書中的一個示例代碼,展現了命名對代碼質量的提高:微信

# bad code
def getItem(theList):
   ret = []
   for x in theList:
      if x[0] == 4:
         ret.append(x)
   return ret

# good code
def getFlaggedCell(gameBoard):
   '''掃雷遊戲,flagged: 翻轉'''
   flaggedCells = []
   for cell in gameBoard:
      if cell.IsFlagged():
         flaggedCells.append(cell)
   return flaggedCells

②避免誤導

不要掛羊頭賣狗肉,不要覆蓋慣用縮略語!架構


這裏不得不吐槽前兩天才看到的一份代碼,竟然使用了 l 做爲變量名;並且,user 竟然是一個 list(單複數都沒學好!!)

③有意義的區分

代碼是寫給機器執行,也是給人閱讀的,因此概念必定要有區分度:

# bad
def copy(a_list, b_list):
pass
# good
def copy(source, destination):
pass

④使用讀的出來的單詞


若是名稱讀不出來,那麼討論的時候就會像個傻鳥。

⑤使用方便搜索的命名


名字長短應與其做用域大小相對應!

⑥避免思惟映射


好比在代碼中寫一個 temp,那麼讀者就得每次看到這個單詞的時候翻譯成其真正的意義。

註釋


有表達力的代碼是無需註釋的:

The proper use of comments is to compensate for our failure to express ourself in code.


註釋的適看成用在於彌補咱們用代碼表達意圖時遇到的失敗,這聽起來讓人沮喪,但事實確實如此。


The truth is in the code,註釋只是二手信息,兩者的不一樣步或者不等價是註釋的最大問題。

書中給出了一個很是形象的例子來展現,用代碼來闡述,而非註釋:

bad
// check to see if the employee is eligible for full benefit
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
good
if (employee.isEligibleForFullBenefits())


所以,當想要添加註釋的時候,能夠想一想是否能夠經過修改命名,或者修改函數(代碼)的抽象層級來展現代碼的意圖。

固然,也不能因噎廢食,書中指出瞭如下一些狀況屬於好的註釋:
  • 法務信息

  • 對意圖的註釋,爲何要這麼作

  • 警示

  • TODO 註釋

  • 放大看似不合理之物的重要性


其中我的最贊同的是第 2 點和第 5 點,作什麼很容易經過命名錶達,但爲何要這麼作則並不直觀,特別涉及到專業知識、算法的時候。


另外,有些第一感受「不那麼優雅」的代碼,也許有其特殊願意,那麼這樣的代碼就應該加上註釋,說明爲何要這樣,好比爲了提高關鍵路徑的性能,可能會犧牲部分代碼的可讀性。

最壞的註釋就是過期或者錯誤的註釋,這對於代碼的維護者(也許就是幾個月後的本身)是巨大的傷害,惋惜除了 code review,並無簡單易行的方法來保證代碼與註釋的同步。

函數


①函數的單一職責

一個函數應該只作一件事,這件事應該能經過函數名就能清晰的展現。判斷方法很簡單:看看函數是否還能再拆出一個函數。

函數要麼作什麼 do_sth,要麼查詢什麼 query_sth。最噁心的就是函數名錶示只會 query_sth,但事實上卻會 do_sth,這使得函數產生了反作用。

好比書中的例子:

public class UserValidator {
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password) 
        User user = UserGateway.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword(); 
String phrase = cryptographer.decrypt(codedPhrase, password); 
if ("Valid Password".equals(phrase)) {
                Session.initialize();
return true
            }
        }
return false
    }
}

②函數的抽象層級

每一個函數一個抽象層次,函數中的語句都要在同一個抽象層級,不一樣的抽象層級不能放在一塊兒。


好比咱們想把大象放進冰箱,應該是這個樣子的:

def pushElephantIntoRefrige():
    openRefrige()
    pushElephant()
    closeRefrige()


函數裏面的三句代碼在同一個層級(高度)描述了要完成把大象放進冰箱這件事順序相關的三個步驟。

顯然,pushElephant 這個步驟又可能包含不少子步驟,可是在 pushElephantIntoRefrige 這個層級,是無需知道太多細節的。

當咱們想經過閱讀代碼的方式來了解一個新的項目時,通常都是採起廣度優先的策略,自上而下的閱讀代碼,先了解總體結構,而後再深刻感興趣的細節。


若是沒有對實現細節進行良好的抽象(並凝練出一個名副其實的函數),那麼閱讀者就容易迷失在細節的汪洋裏。

某種程度看來,這個跟金字塔原理也很像:

每個層級都是爲了論證其上一層級的觀點,同時也須要下一層級的支持;同一層級之間的多個論點又須要以某種邏輯關係排序。


pushElephantIntoRefrige 就是中心論點,須要多個子步驟的支持,同時這些子步驟之間也有邏輯前後順序。

③函數參數


函數的參數越多,組合出的輸入狀況就愈多,須要的測試用例也就越多,也就越容易出問題。

輸出參數相比返回值難以理解,這點深有同感,輸出參數實在是很不直觀。從函數調用者的角度,一眼就能看出返回值,而很難識別輸出參數。輸出參數一般逼迫調用者去檢查函數簽名,這個實在不友好。

向函數傳入Boolean(書中稱之爲 Flag Argument)一般不是好主意。尤爲是傳入True or False後的行爲並非一件事情的兩面,而是兩件不一樣的事情時。


這很明顯違背了函數的單一職責約束,解決辦法很簡單,那就是用兩個函數。 Dont repear yourself。

在函數這個層級,是最容易、最直觀實現複用的,不少 IDE 也難幫助咱們講一段代碼重構出一個函數。

不過在實踐中,也會出現這樣一種狀況: 一段代碼在多個方法中都有使用,可是又不徹底同樣,若是抽象成一個通用函數,那麼就須要加參數、加 if else 區別。這樣就有點尷尬,貌似能夠重構,但又不是很完美。

形成上述問題的某種狀況是由於,這段代碼也違背了單一職責原則,作了不僅一件事情,這才致使很差複用,解決辦法是進行方法的細分,才能更好複用。


也能夠考慮 template method 來處理差別的部分。

測試


很是慚愧的是,在我經歷的項目中,測試(尤爲是單元測試)一直都沒有獲得足夠的重視,也沒有試行過 TDD。正由於缺失,才更感良好測試的珍貴。

咱們常說,好的代碼須要有可讀性、可維護性、可擴展性,好的代碼、架構須要不停的重構、迭代,但自動化測試是保證這一切的基礎,沒有高覆蓋率的、自動化的單元測試、迴歸測試,誰都不敢去修改代碼,只能任其腐爛。

即便針對核心模塊寫了單元測試,通常也很隨意,認爲這只是測試代碼,配不上生產代碼的地位,覺得只要能跑通就好了。


這就致使測試代碼的可讀性、可維護性很是差,而後致使測試代碼很難跟隨生產代碼一塊兒更新、演化,最後致使測試代碼失效。因此說,髒測試等同於沒測試。

所以,測試代碼的三要素:
  • 可讀性

  • 可讀性

  • 可讀性


對於測試的原則、準則以下:

  • 沒有測試以前不要寫任何功能代碼

  • 只編寫剛好可以體現一個失敗狀況的測試代碼

  • 只編寫剛好能經過測試的功能代碼


測試的 FIRST 準則:
  • 快速(Fast)測試應該夠快,儘可能自動化。

  • 獨立(Independent)測試應該應該獨立。不要相互依賴

  • 可重複(Repeatable)測試應該在任何環境上都能重複經過。

  • 自我驗證(Self-Validating)測試應該有 bool 輸出。不要經過查看日誌這種低效率方式來判斷測試是否經過。

  • 及時(Timely)測試應該及時編寫,在其對應的生產代碼以前編寫。


做者:xybaby

出處:https://www.cnblogs.com/xybaby/p/11335829.html


文末彩蛋

掃碼回覆,"  2020    " 得到最新前端,後端,大數據,人工智能,PHP等 視頻教程資料雲盤連接。
   
   
編程·思惟·職場
歡迎掃碼關注


本文分享自微信公衆號 - 前端迷社區(gh_c8466b051727)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索