Code Complete — 創(chuàng)建高質(zhì)量的代碼

本文將從變量,語句,代碼塊,子程序,到類以及框架設(shè)計,詳細(xì)描述了如何編寫高質(zhì)量的程序。盡管大部分原則你可能都知道了,但還是有些點會帶給你驚喜。

變量

變量初始化原則

  • 聲明的時候初始化
  • 在靠近變量第一次使用的位置初始化,就近原則。
  • 理想情況下,在靠近第一次使用變量的位置聲明和定義該變量,但是在JS里面卻習(xí)慣將變量聲明提前。
  • 注意計數(shù)器和累加器的修改。
  • 在類的構(gòu)造函數(shù)中初始化數(shù)據(jù)成員
  • 確定是否需要重新初始化
  • 把每個變量用于唯一用途

變量作用域優(yōu)化

作用域指變量在程序內(nèi)的可見和可引用范圍。介于同一變量多個引用點之間的代碼可稱為”攻擊窗口(window of vulnerability)”,應(yīng)把變量的引用點盡可能集中在一起,減小”攻擊窗口“的范圍。

  • 盡量縮短變量的引用范圍
  • 盡量縮短變量的存活時間
  • 把相關(guān)語句提取成單獨的子程序
  • 盡量少使用全局變量。使用全局變量可以讓程序?qū)懫饋砗芊奖悖驗槿肿兞靠梢噪S時訪問和使用,但是這樣很難維護(hù)和管理,如果換人來維護(hù)這些代碼他很難知道這些變量在哪里在什么時候會被修改。

變量命名原則

  • 規(guī)范命名的目的是提高程序的可讀性同時易于調(diào)試
  • 變量名需要準(zhǔn)確描述其代表的事物
  • 變量名的平均長度在10到16個字符時更易于調(diào)試。這并不是說你要把所有的變量都控制在這個范圍,命名的最終目的提高可讀性和可維護(hù)性,當(dāng)你檢查代碼時發(fā)現(xiàn)大部分變量名都很短或者含義不清時,那你的命名肯定有問題
  • 長名變量適合全局變量,短名的適合局部變量
  • 將計算值限定詞作為后綴。Total,Sum,Average,Max,Min,Str,Pointer等表示計算的限定詞一般放在后面。
  • 使用業(yè)界約定俗稱的變量。比如i,j,temp,flag這些,不用解釋都知道。
  • 使用團(tuán)隊命名規(guī)范,不同團(tuán)隊,不同語言的命名原則會有不同,優(yōu)先服從規(guī)范。
    代碼閱讀的次數(shù)遠(yuǎn)大于編寫的次數(shù),確保你的名字更易于閱讀,而不是易于編寫。

變量縮寫原則

  • 使用標(biāo)準(zhǔn)的縮寫,如min,sub,str等
  • 去掉所有非前置元音,如computer->cmptr,screen->scrn,apple->appl
  • 去掉虛詞and,or,the等
  • 使用單詞的前幾個字母,統(tǒng)一在每個單詞的第N個字母后截斷
  • 去除無用后綴,如ing,ed等
  • 保留每個音節(jié)中最引人注意的發(fā)音
  • 確保不要因為縮寫而改變了變量的含義,或者縮寫后的變量名有歧義或者很難理解

語句

直線型代碼

組織直線型代碼最主要的原則就是按照依賴關(guān)系進(jìn)行排列。所謂依賴關(guān)系就是下一行代碼是否會依賴上一行代碼的執(zhí)行,是則為順序相關(guān)依賴,否則為順序無關(guān)依賴。可以用好的子程序名,參數(shù)列表,注釋來讓順序相關(guān)依賴變得更明顯。如果代碼之間沒有順序依賴關(guān)系,那就設(shè)法使相關(guān)的語句盡可能地接近。

條件語句

if語句使用原則
  • 先處理正常路徑,再處理不常見情況
  • 考慮else語句。雖然5到8成的代碼都會有else語句,但有些情況是在程序一開始就做一個if判斷,是則返回,不執(zhí)行后面的代碼,這樣可以避免將后面的代碼全都嵌套在else子語句中。但無論是否有else,請都將子句用大括號括起來。
  • 簡化復(fù)雜的條件檢測。在if/elseif語句中,經(jīng)常會有很復(fù)雜的邏輯判斷,為了提高可讀性,可將這些邏輯判斷封裝成布爾函數(shù)。
  • 考慮將if/elseif 替換成case.
case 語句

case語句適合處理簡單易分類的數(shù)據(jù),如果你的數(shù)據(jù)并不簡單,請使用if/elseif語句。

  • 按字母/數(shù)字順序排列各種情況
  • 優(yōu)先處理正常情況
  • 按執(zhí)行效率排列case語句
  • 如果在某個case后面沒有break,請注釋說明。
  • 利用default子句來檢測錯誤

表驅(qū)動法

直接訪問表

在前端開發(fā),針對后臺返回的錯誤碼,通常不會直接用if/else判斷錯誤碼來顯示相應(yīng)地錯誤信息,而是將錯誤碼-錯誤提示存放在”表“對象中,通過傳入錯誤碼來返回錯誤提示,這就是最簡單的表驅(qū)動法——直接訪問表。

當(dāng)然我們可能會遇到更加復(fù)雜的情況,比如某活動要給1到100歲的人提供優(yōu)惠,不同年齡的人群優(yōu)惠可能相同也可能不同。如果將年齡作為key,優(yōu)惠作為value,那么最笨得方法是存儲100個鍵值對,當(dāng)然這里面的值會有重復(fù)的。

解決方法就是做鍵值轉(zhuǎn)換,將年齡轉(zhuǎn)化成另外一個鍵,然后讓該鍵對應(yīng)到具體優(yōu)惠。

索引訪問表

鍵值轉(zhuǎn)換提供了一個很好地思路,那就是將表的”查詢條件“和”查詢記錄"分開管理,建立索引。索引訪問表適合處理表記錄占用空間比較大得情況,操作索引中的記錄往往比操作主表本身的記錄更方便廉價,并且由于索引和主表是分開的,同一個主表可以根據(jù)不同查詢條件建立不同索引,靈活性更強(qiáng),后期可維護(hù)性也更好。

階梯訪問表

索引訪問的一個問題就是如果鍵的取值范圍很大的話,那建立的索引就會很長很占空間,階梯訪問表則是對某些情況下的一種優(yōu)化。
階梯訪問的基本思想是:表中的記錄對于不同的數(shù)據(jù)范圍有效,而不是不同的數(shù)據(jù)點。相對于索引訪問,通常將輸入數(shù)據(jù)映射到指定數(shù)據(jù)范圍,飯后取得對于值的過程是比較耗時的,這其實是一種用時間換空間的方式。具體采用哪種表驅(qū)動方法,就看時間和空間哪個對你更重要了。

高質(zhì)量的子程序

創(chuàng)建子程序最主要的目的是提高程序的可管理性,當(dāng)然也有其他一些好的理由。其中,節(jié)省代碼空間只是一個次要原因,更重要的是能提高可讀性、可靠性和可修改性。
高質(zhì)量的子程序可以:

  • 降低和隔離復(fù)雜度
  • 引入中間層,易懂的代碼
  • 提高可移植性
  • 改善性能
  • 隱藏實現(xiàn)細(xì)節(jié),隱藏全局?jǐn)?shù)據(jù)
  • 限制變化帶來的影響
  • 形成中央控制點
  • 達(dá)到特定的重構(gòu)目的

高質(zhì)量的子程序應(yīng)該是功能上高內(nèi)聚的,有著良好的命名。說到命名,一直很矛盾,怎樣才能算是一個好的命名?按什么標(biāo)準(zhǔn)?書中給了參考:

  • 描述子程序所做的所有事情。要完整的描述一個子程序,名字可能會很長,這個時候除了使用縮寫,還應(yīng)該思考一下這樣的子程序本身是不是有問題。
  • 避免使用無意義或模糊的詞。計算機(jī)是明確的,doSomething這樣的函數(shù)名只是用來教學(xué)。
  • 不要通過數(shù)字來標(biāo)識。看到handle1,handle2這樣的命名是不是很憤怒,哈哈。
  • 根據(jù)需要確定子程序名字的長度。研究表明,變量名的最佳長度是9到15個字符。我不知道這個調(diào)查是針對特定編程語言還是所有編程語言,按理說應(yīng)該是語言無關(guān),但我怎么有種感覺,Java或者C++代碼的命名普遍比JS中的要長?
    -給函數(shù)命名時要對返回值有所描述。就是說看到函數(shù)名就知道它會返回什么。比如xxx.isReady()看名字就知道返回布爾型,xxx.next()返回下一個與xxx相關(guān)的對象。
  • 給過程起名時使用語氣強(qiáng)烈的動賓形式。比如printDocument,checkOrderInfo。但是在面向?qū)ο笳Z言中,比如JS,通常不用加賓語,因為賓語就是對象本身,比如document.print(),orderInfo.check()。
  • 準(zhǔn)確使用對仗詞。比如add/remove,open/close。fileOpen對fileClose,fileOpen對fClose就會很奇怪。
  • 為常用操作確定命名規(guī)則。

書中還說了一個比較有趣的問題,子程序可以寫多長?理論上認(rèn)為的子程序最佳長度是一屏代碼或打印出來一到兩頁紙的長度,約20200行(原書是50150行)。人們已經(jīng)在子程序長度的問題上做了大量統(tǒng)計和研究,但并非所有的這些統(tǒng)計都適合現(xiàn)代編程。不過有一點,如果你的子程序超過了200行,那你就要小心了。

子程序通常會有參數(shù),如何組織這些參數(shù)也是門學(xué)問。下面是一些指導(dǎo)原則:

  • 按照輸入-可修改-輸出的順序排列參數(shù),也可以考慮按照該排列規(guī)則對參數(shù)進(jìn)行規(guī)范命名。
  • 讓所有子程序參數(shù)排列順序保持一致。
  • 使用所有參數(shù)。很遺憾,這是JS的先天缺陷,你需要更加小心。
  • 把狀態(tài)或者出錯變量放到最后。
  • 不要把子程序的參數(shù)用作工作變量,應(yīng)該在子程序中使用局部變量。
calcDemo(inputVal){
  inputVal = inputVal + currentAdder(inputVal)
  // do something with inputVal
  ...
  return inputVal
}

這樣的代碼雖然沒有任何錯誤,但是容易造成誤解,因為最后返回的inputVal已經(jīng)不是最初傳入的inputVal了,正確的做法是在函數(shù)內(nèi)部使用局部變量指向inputVal然后返回該局部變量。這里是工程代碼,不是在競賽網(wǎng)站上,不能為了簡潔而簡潔,少寫一行代碼并不會給你加分。

  • 在接口中對參數(shù)的假定加以說明。
  • 限制子程序的參數(shù)個數(shù)。7是個很神奇的數(shù)字,讓你的參數(shù)保持在七個以內(nèi)。
  • 為子程序傳遞用以維持其接口抽象的變量或?qū)ο蟆N以诤芏啻a中發(fā)現(xiàn),函數(shù)參數(shù)并不是一個個變量,而是一個對象,通過該對象來傳遞參數(shù)。

這是一個富有爭議的問題。假如一個對象有10個屬性,但是處理方法只用到了3個屬性,那么直接傳遞對象就暴露了其他屬性,這破壞了封裝原則,增加了代碼耦合。另一種觀點則認(rèn)為傳遞整個對象能使子程序更加靈活,使接口更加穩(wěn)定易于擴(kuò)展。

那到底何時傳變量,何時傳對象呢?作者認(rèn)為關(guān)鍵在于子程序的接口想要表達(dá)何種抽象。如果要表達(dá)的抽象是子程序期望的特定數(shù)據(jù),那么應(yīng)該直接傳數(shù)據(jù),如果要表達(dá)的抽象是想擁有某個特定對象,就應(yīng)該傳對象。

比如,你發(fā)現(xiàn)在調(diào)用子程序之前都要先創(chuàng)建一個對象,調(diào)用完后又從對象中取出這些數(shù)據(jù),那說明你需要的是數(shù)據(jù)而非對象。如果你發(fā)現(xiàn)自己經(jīng)常需要修改子程序的參數(shù)表,而每次修改的參數(shù)都來自同一個對象,那說明你需要的是整個對象。

說完參數(shù),最后來說說返回值。如果把函數(shù)按語義劃分,可以分為“函數(shù)”和“過程”,”函數(shù)”有返回值,而“過程”返回void或者沒有返回值。什么時候使用”函數(shù)“,什么時候使用”過程”,其實通過函數(shù)名就應(yīng)該能確定下來。比如xxx.next()和xxx.fire(),前者一看就是”函數(shù)“,而后者是”過程“。
如果你使用”函數(shù)“,肯定會存在返回錯誤返回值的風(fēng)險,尤其是當(dāng)函數(shù)內(nèi)有多條分支時。為減小這一風(fēng)險,請確保:

  • 檢查所有可能的返回路徑
  • 不要返回指向局部數(shù)據(jù)的引用或者指針

防御式編程

防御式編程的核心其實就是容錯。當(dāng)子程序遭遇到各種非法輸入數(shù)據(jù)時也能工作。對于這些非法數(shù)據(jù),通常有三種方式來處理:

  1. 檢查所有來源于外部的數(shù)據(jù)。文件,用戶,網(wǎng)絡(luò)等接口的數(shù)據(jù)都屬于外部數(shù)據(jù),這些都是不安全的。
  2. 檢查子程序所有的輸入?yún)?shù)。子程序的輸入數(shù)據(jù)來源于其它子程序,這里做檢查是為了防止程序內(nèi)部產(chǎn)生了非預(yù)期的數(shù)據(jù)。
  3. 決定如何處理錯誤的輸入數(shù)據(jù)。根據(jù)項目需求,你可以返回錯誤碼,記錄日志,返回一個默認(rèn)的合法值或返回與前次相同的數(shù)據(jù),具體方案視需求而定。

第一點和第二點都是數(shù)據(jù)校驗,第三點是對校驗結(jié)果的處理方式。一切錯誤都來自于輸入輸出。理論上對于所有外部數(shù)據(jù)都要進(jìn)行校驗,因為這些數(shù)據(jù)都是不可靠不確定的,需要通過一個”過濾系統(tǒng)”將其過濾成確定類型的數(shù)據(jù)。這個”過濾系統(tǒng)”就是隔欄(barricade)。在隔欄的外面應(yīng)該使用錯誤處理技術(shù),在內(nèi)部應(yīng)該使用斷言。因為隔欄內(nèi)部的數(shù)據(jù)都是被清理過的,如果在內(nèi)部出錯那應(yīng)該是程序的錯誤而非數(shù)據(jù)的錯誤。

還有一種容錯方式叫異常。異常是把代碼中得錯誤或異常事件傳遞給調(diào)用方代碼的一種特殊手段。異常跟斷言的使用情景相似,都是用來處理那些罕見或者永遠(yuǎn)不應(yīng)該發(fā)生得情況。書中給出了使用異常的一些建議:

  • 用異常通知程序的其他部分,進(jìn)行錯誤消息傳遞。
  • 只有在其他編碼方式無法解決的情況下才使用異常。
  • 不要把本可在局部處理的錯誤當(dāng)成一個未捕獲的異常拋出去。
  • 避免使用空得catch語句,這是一種不負(fù)責(zé)任的寫法。
  • 了解所有函數(shù)庫可能拋出的異常。
  • 建立一套幾種的異常處理機(jī)制。
  • 考慮異常的替換方案,確保你的程序是真的需要處理異常。

過度的防御式編程會使程序變得臃腫緩慢,增加軟件的復(fù)雜度,變得難以維護(hù)。所以在進(jìn)行編碼時呀考慮好什么地方需要防御,然后調(diào)整優(yōu)先級,因地制宜。

系列文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容