自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

新聞
寫出整潔的代碼,是每個(gè)程序員的追求?!禼lean code》指出,要想寫出好的代碼,首先得知道什么是骯臟代碼、什么是整潔代碼;然后通過(guò)大量的刻意練習(xí),才能真正寫出整潔的代碼。

寫出整潔的代碼,是每個(gè)程序員的追求?!禼lean code》指出,要想寫出好的代碼,首先得知道什么是骯臟代碼、什么是整潔代碼;然后通過(guò)大量的刻意練習(xí),才能真正寫出整潔的代碼。

[[319259]]

圖片來(lái)自 Pexels

WTF/min 是衡量代碼質(zhì)量的唯一標(biāo)準(zhǔn),Uncle Bob 在書中稱糟糕的代碼為沼澤(wading),這只突出了我們是糟糕代碼的受害者。

國(guó)內(nèi)有一個(gè)更適合的詞匯:屎山,雖然不是很文雅但是更加客觀,程序員既是受害者也是加害者。

對(duì)于什么是整潔的代碼,書中給出了大師們的總結(jié):

  • Bjarne Stroustrup:優(yōu)雅且高效;直截了當(dāng);減少依賴;只做好一件事
  • Grady booch:簡(jiǎn)單直接
  • Dave thomas:可讀,可維護(hù),單元測(cè)試
  • Ron Jeffries:不要重復(fù)、單一職責(zé),表達(dá)力(Expressiveness)

其中,我最喜歡的是表達(dá)力(Expressiveness)這個(gè)描述,這個(gè)詞似乎道出了好代碼的真諦:用簡(jiǎn)單直接的方式描繪出代碼的功能,不多也不少。

命名的藝術(shù)

坦白的說(shuō),命名是一件困難的事情,要想出一個(gè)恰到好處的命名需要一番功夫,尤其我們的母語(yǔ)還不是編程語(yǔ)言所通用的英語(yǔ)。

不過(guò)這一切都是值得了,好的命名讓你的代碼更直觀,更有表達(dá)力。好的命名應(yīng)該有下面的特征:

①名副其實(shí)

好的變量名告訴你:是什么東西,為什么存在,該怎么使用,如果需要通過(guò)注釋來(lái)解釋變量,那么就先得不那么名副其實(shí)了。

下面是書中的一個(gè)示例代碼,展示了命名對(duì)代碼質(zhì)量的提升:

  1. # bad code 
  2. def getItem(theList): 
  3.    ret = [] 
  4.    for x in theList: 
  5.       if x[0] == 4: 
  6.          ret.append(x) 
  7.    return ret 
  8.  
  9. # good code 
  10. def getFlaggedCell(gameBoard): 
  11.    '''掃雷游戲,flagged: 翻轉(zhuǎn)''' 
  12.    flaggedCells = [] 
  13.    for cell in gameBoard: 
  14.       if cell.IsFlagged(): 
  15.          flaggedCells.append(cell) 
  16.    return flaggedCells 

②避免誤導(dǎo)

不要掛羊頭賣狗肉,不要覆蓋慣用縮略語(yǔ)!

這里不得不吐槽前兩天才看到的一份代碼,居然使用了 l 作為變量名;而且,user 居然是一個(gè) list(單復(fù)數(shù)都沒(méi)學(xué)好!!)

③有意義的區(qū)分

代碼是寫給機(jī)器執(zhí)行,也是給人閱讀的,所以概念一定要有區(qū)分度:

  1. # bad 
  2. def copy(a_list, b_list): 
  3. pass 
  4. # good 
  5. def copy(source, destination): 
  6. pass 

④使用讀的出來(lái)的單詞

如果名稱讀不出來(lái),那么討論的時(shí)候就會(huì)像個(gè)傻鳥。

⑤使用方便搜索的命名

名字長(zhǎng)短應(yīng)與其作用域大小相對(duì)應(yīng)!

⑥避免思維映射

比如在代碼中寫一個(gè) temp,那么讀者就得每次看到這個(gè)單詞的時(shí)候翻譯成其真正的意義。

注釋

有表達(dá)力的代碼是無(wú)需注釋的:

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

注釋的適當(dāng)作用在于彌補(bǔ)我們用代碼表達(dá)意圖時(shí)遇到的失敗,這聽起來(lái)讓人沮喪,但事實(shí)確實(shí)如此。

The truth is in the code,注釋只是二手信息,二者的不同步或者不等價(jià)是注釋的最大問(wèn)題。

書中給出了一個(gè)非常形象的例子來(lái)展示,用代碼來(lái)闡述,而非注釋:

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

因此,當(dāng)想要添加注釋的時(shí)候,可以想想是否可以通過(guò)修改命名,或者修改函數(shù)(代碼)的抽象層級(jí)來(lái)展示代碼的意圖。

當(dāng)然,也不能因噎廢食,書中指出了以下一些情況屬于好的注釋:

  • 法務(wù)信息
  • 對(duì)意圖的注釋,為什么要這么做
  • 警示
  • TODO 注釋
  • 放大看似不合理之物的重要性

其中個(gè)人最贊同的是第 2 點(diǎn)和第 5 點(diǎn),做什么很容易通過(guò)命名表達(dá),但為什么要這么做則并不直觀,特別涉及到專業(yè)知識(shí)、算法的時(shí)候。

另外,有些第一感覺(jué)“不那么優(yōu)雅”的代碼,也許有其特殊愿意,那么這樣的代碼就應(yīng)該加上注釋,說(shuō)明為什么要這樣,比如為了提升關(guān)鍵路徑的性能,可能會(huì)犧牲部分代碼的可讀性。

最壞的注釋就是過(guò)時(shí)或者錯(cuò)誤的注釋,這對(duì)于代碼的維護(hù)者(也許就是幾個(gè)月后的自己)是巨大的傷害,可惜除了 code review,并沒(méi)有簡(jiǎn)單易行的方法來(lái)保證代碼與注釋的同步。

函數(shù)

①函數(shù)的單一職責(zé)

一個(gè)函數(shù)應(yīng)該只做一件事,這件事應(yīng)該能通過(guò)函數(shù)名就能清晰的展示。判斷方法很簡(jiǎn)單:看看函數(shù)是否還能再拆出一個(gè)函數(shù)。

函數(shù)要么做什么 do_sth,要么查詢什么 query_sth。最惡心的就是函數(shù)名表示只會(huì) query_sth,但事實(shí)上卻會(huì) do_sth,這使得函數(shù)產(chǎn)生了副作用。

比如書中的例子:

  1. public class UserValidator { 
  2. private Cryptographer cryptographer; 
  3. public boolean checkPassword(String userName, String password) {  
  4.         User user = UserGateway.findByName(userName); 
  5. if (user != User.NULL) { 
  6. String codedPhrase = user.getPhraseEncodedByPassword();  
  7. String phrase = cryptographer.decrypt(codedPhrase, password);  
  8. if ("Valid Password".equals(phrase)) { 
  9.                 Session.initialize(); 
  10. return true;  
  11.             } 
  12.         } 
  13. return false;  
  14.     } 

②函數(shù)的抽象層級(jí)

每個(gè)函數(shù)一個(gè)抽象層次,函數(shù)中的語(yǔ)句都要在同一個(gè)抽象層級(jí),不同的抽象層級(jí)不能放在一起。

比如我們想把大象放進(jìn)冰箱,應(yīng)該是這個(gè)樣子的:

  1. def pushElephantIntoRefrige(): 
  2.     openRefrige() 
  3.     pushElephant() 
  4.     closeRefrige() 

函數(shù)里面的三句代碼在同一個(gè)層級(jí)(高度)描述了要完成把大象放進(jìn)冰箱這件事順序相關(guān)的三個(gè)步驟。

顯然,pushElephant 這個(gè)步驟又可能包含很多子步驟,但是在 pushElephantIntoRefrige 這個(gè)層級(jí),是無(wú)需知道太多細(xì)節(jié)的。

當(dāng)我們想通過(guò)閱讀代碼的方式來(lái)了解一個(gè)新的項(xiàng)目時(shí),一般都是采取廣度優(yōu)先的策略,自上而下的閱讀代碼,先了解整體結(jié)構(gòu),然后再深入感興趣的細(xì)節(jié)。

如果沒(méi)有對(duì)實(shí)現(xiàn)細(xì)節(jié)進(jìn)行良好的抽象(并凝練出一個(gè)名副其實(shí)的函數(shù)),那么閱讀者就容易迷失在細(xì)節(jié)的汪洋里。

某種程度看來(lái),這個(gè)跟金字塔原理也很像:

每一個(gè)層級(jí)都是為了論證其上一層級(jí)的觀點(diǎn),同時(shí)也需要下一層級(jí)的支持;同一層級(jí)之間的多個(gè)論點(diǎn)又需要以某種邏輯關(guān)系排序。

pushElephantIntoRefrige 就是中心論點(diǎn),需要多個(gè)子步驟的支持,同時(shí)這些子步驟之間也有邏輯先后順序。

③函數(shù)參數(shù)

函數(shù)的參數(shù)越多,組合出的輸入情況就愈多,需要的測(cè)試用例也就越多,也就越容易出問(wèn)題。

輸出參數(shù)相比返回值難以理解,這點(diǎn)深有同感,輸出參數(shù)實(shí)在是很不直觀。從函數(shù)調(diào)用者的角度,一眼就能看出返回值,而很難識(shí)別輸出參數(shù)。輸出參數(shù)通常逼迫調(diào)用者去檢查函數(shù)簽名,這個(gè)實(shí)在不友好。

向函數(shù)傳入Boolean(書中稱之為 Flag Argument)通常不是好主意。尤其是傳入True or False后的行為并不是一件事情的兩面,而是兩件不同的事情時(shí)。

這很明顯違背了函數(shù)的單一職責(zé)約束,解決辦法很簡(jiǎn)單,那就是用兩個(gè)函數(shù)。Dont repear yourself。

在函數(shù)這個(gè)層級(jí),是最容易、最直觀實(shí)現(xiàn)復(fù)用的,很多 IDE 也難幫助我們講一段代碼重構(gòu)出一個(gè)函數(shù)。

不過(guò)在實(shí)踐中,也會(huì)出現(xiàn)這樣一種情況:一段代碼在多個(gè)方法中都有使用,但是又不完全一樣,如果抽象成一個(gè)通用函數(shù),那么就需要加參數(shù)、加 if else 區(qū)別。這樣就有點(diǎn)尷尬,貌似可以重構(gòu),但又不是很完美。

造成上述問(wèn)題的某種情況是因?yàn)椋@段代碼也違背了單一職責(zé)原則,做了不只一件事情,這才導(dǎo)致不好復(fù)用,解決辦法是進(jìn)行方法的細(xì)分,才能更好復(fù)用。

也可以考慮 template method 來(lái)處理差異的部分。

測(cè)試

非常慚愧的是,在我經(jīng)歷的項(xiàng)目中,測(cè)試(尤其是單元測(cè)試)一直都沒(méi)有得到足夠的重視,也沒(méi)有試行過(guò) TDD。正因?yàn)槿笔В鸥辛己脺y(cè)試的珍貴。

我們常說(shuō),好的代碼需要有可讀性、可維護(hù)性、可擴(kuò)展性,好的代碼、架構(gòu)需要不停的重構(gòu)、迭代,但自動(dòng)化測(cè)試是保證這一切的基礎(chǔ),沒(méi)有高覆蓋率的、自動(dòng)化的單元測(cè)試、回歸測(cè)試,誰(shuí)都不敢去修改代碼,只能任其腐爛。

即使針對(duì)核心模塊寫了單元測(cè)試,一般也很隨意,認(rèn)為這只是測(cè)試代碼,配不上生產(chǎn)代碼的地位,以為只要能跑通就行了。

這就導(dǎo)致測(cè)試代碼的可讀性、可維護(hù)性非常差,然后導(dǎo)致測(cè)試代碼很難跟隨生產(chǎn)代碼一起更新、演化,最后導(dǎo)致測(cè)試代碼失效。所以說(shuō),臟測(cè)試等同于沒(méi)測(cè)試。

因此,測(cè)試代碼的三要素:

  • 可讀性
  • 可讀性
  • 可讀性

對(duì)于測(cè)試的原則、準(zhǔn)則如下:

  • 沒(méi)有測(cè)試之前不要寫任何功能代碼
  • 只編寫恰好能夠體現(xiàn)一個(gè)失敗情況的測(cè)試代碼
  • 只編寫恰好能通過(guò)測(cè)試的功能代碼

測(cè)試的 FIRST 準(zhǔn)則:

  • 快速(Fast)測(cè)試應(yīng)該夠快,盡量自動(dòng)化。
  • 獨(dú)立(Independent)測(cè)試應(yīng)該應(yīng)該獨(dú)立。不要相互依賴
  • 可重復(fù)(Repeatable)測(cè)試應(yīng)該在任何環(huán)境上都能重復(fù)通過(guò)。
  • 自我驗(yàn)證(Self-Validating)測(cè)試應(yīng)該有 bool 輸出。不要通過(guò)查看日志這種低效率方式來(lái)判斷測(cè)試是否通過(guò)。
  • 及時(shí)(Timely)測(cè)試應(yīng)該及時(shí)編寫,在其對(duì)應(yīng)的生產(chǎn)代碼之前編寫。

 

責(zé)任編輯:武曉燕 來(lái)源: 51CTO技術(shù)棧
相關(guān)推薦

2024-08-21 08:22:33

2024-03-14 10:30:05

緩存場(chǎng)景DEMO

2021-07-16 07:57:35

SpringBootOpenFeign微服務(wù)

2022-03-23 08:01:04

Python語(yǔ)言代碼

2019-06-18 11:09:54

2025-04-22 03:00:00

模型SpringAI

2021-09-29 09:07:22

Docker 日志容器

2021-12-27 07:25:13

項(xiàng)目軟件開發(fā)

2020-09-27 10:55:10

代碼Java字符串

2020-04-07 08:00:02

Redis緩存數(shù)據(jù)

2021-01-18 11:27:03

Istio架構(gòu)云環(huán)境

2020-10-21 12:10:30

訂單號(hào)Java代碼

2021-12-03 11:57:27

代碼##語(yǔ)言

2020-05-15 09:30:12

代碼函數(shù)語(yǔ)言

2019-08-12 15:51:26

SSD存儲(chǔ)產(chǎn)品

2020-02-20 10:45:57

代碼JS開發(fā)

2020-09-25 08:58:43

推薦系統(tǒng)業(yè)務(wù)

2021-04-13 05:40:01

抓包藍(lán)屏Linux

2021-09-07 15:41:35

Bug誘因代碼

2019-02-12 15:00:32

Javascript命令式編程前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)