code complete — 建立高質量的代碼

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

變量

變量初始化原則

  • 聲明的時候初始化前端

  • 在靠近變量第一次使用的位置初始化,就近原則。java

  • 理想狀況下,在靠近第一次使用變量的位置聲明和定義該變量,可是在JS裏面卻習慣將變量聲明提早。編程

  • 注意計數器和累加器的修改。segmentfault

  • 在類的構造函數中初始化數據成員安全

  • 肯定是否須要從新初始化網絡

  • 把每一個變量用於惟一用途app

變量做用域優化

做用域指變量在程序內的可見和可引用範圍。介於同一變量多個引用點之間的代碼可稱爲」攻擊窗口(window of vulnerability)」,應把變量的引用點儘量集中在一塊兒,減少」攻擊窗口「的範圍。框架

  • 儘可能縮短變量的引用範圍編程語言

  • 儘可能縮短變量的存活時間

  • 把相關語句提取成單獨的子程序

  • 儘可能少使用全局變量。使用全局變量可讓程序寫起來很方便,由於全局變量能夠隨時訪問和使用,可是這樣很難維護和管理,若是換人來維護這些代碼他很難知道這些變量在哪裏在何時會被修改。

變量命名原則

  • 規範命名的目的是提升程序的可讀性同時易於調試

  • 變量名須要準確描述其表明的事物

  • 變量名的平均長度在10到16個字符時更易於調試。這並非說你要把全部的變量都控制在這個範圍,命名的最終目的提升可讀性和可維護性,當你檢查代碼時發現大部分變量名都很短或者含義不清時,那你的命名確定有問題

  • 長名變量適合全局變量,短名的適合局部變量

  • 將計算值限定詞做爲後綴。Total,Sum,Average,Max,Min,Str,Pointer等表示計算的限定詞通常放在後面。

  • 使用業界約定俗稱的變量。好比i,j,temp,flag這些,不用解釋都知道。

  • 使用團隊命名規範,不一樣團隊,不一樣語言的命名原則會有不一樣,優先服從規範。
    代碼閱讀的次數遠大於編寫的次數,確保你的名字更易於閱讀,而不是易於編寫。

變量縮寫原則

  • 使用標準的縮寫,如min,sub,str等

  • 去掉全部非前置元音,如computer->cmptr,screen->scrn,apple->appl

  • 去掉虛詞and,or,the等

  • 使用單詞的前幾個字母,統一在每一個單詞的第N個字母后截斷

  • 去除無用後綴,如ing,ed等

  • 保留每一個音節中最引人注意的發音

  • 確保不要由於縮寫而改變了變量的含義,或者縮寫後的變量名有歧義或者很難理解

語句

直線型代碼

組織直線型代碼最主要的原則就是按照依賴關係進行排列。所謂依賴關係就是下一行代碼是否會依賴上一行代碼的執行,是則爲順序相關依賴,不然爲順序無關依賴。能夠用好的子程序名,參數列表,註釋來讓順序相關依賴變得更明顯。若是代碼之間沒有順序依賴關係,那就設法使相關的語句儘量地接近。

條件語句

if語句使用原則
  • 先處理正常路徑,再處理不常見狀況

  • 考慮else語句。雖然5到8成的代碼都會有else語句,但有些狀況是在程序一開始就作一個if判斷,是則返回,不執行後面的代碼,這樣能夠避免將後面的代碼全都嵌套在else子語句中。但不管是否有else,請都將子句用大括號括起來。

  • 簡化複雜的條件檢測。在if/elseif語句中,常常會有很複雜的邏輯判斷,爲了提升可讀性,可將這些邏輯判斷封裝成布爾函數。

  • 考慮將if/elseif 替換成case.

case 語句

case語句適合處理簡單易分類的數據,若是你的數據並不簡單,請使用if/elseif語句。

  • 按字母/數字順序排列各類狀況

  • 優先處理正常狀況

  • 按執行效率排列case語句

  • 若是在某個case後面沒有break,請註釋說明。

  • 利用default子句來檢測錯誤

表驅動法

直接訪問表

在前端開發,針對後臺返回的錯誤碼,一般不會直接用if/else判斷錯誤碼來顯示相應地錯誤信息,而是將錯誤碼-錯誤提示存放在」表「對象中,經過傳入錯誤碼來返回錯誤提示,這就是最簡單的表驅動法——直接訪問表。

固然咱們可能會遇到更加複雜的狀況,好比某活動要給1到100歲的人提供優惠,不一樣年齡的人羣優惠可能相同也可能不一樣。若是將年齡做爲key,優惠做爲value,那麼最笨得方法是存儲100個鍵值對,固然這裏面的值會有重複的。

解決方法就是作鍵值轉換,將年齡轉化成另一個鍵,而後讓該鍵對應到具體優惠。

索引訪問表

鍵值轉換提供了一個很好地思路,那就是將表的」查詢條件「和」查詢記錄"分開管理,創建索引。索引訪問表適合處理表記錄佔用空間比較大得狀況,操做索引中的記錄每每比操做主表自己的記錄更方便廉價,而且因爲索引和主表是分開的,同一個主表能夠根據不一樣查詢條件創建不一樣索引,靈活性更強,後期可維護性也更好。

階梯訪問表

索引訪問的一個問題就是若是鍵的取值範圍很大的話,那創建的索引就會很長很佔空間,階梯訪問表則是對某些狀況下的一種優化。
階梯訪問的基本思想是:表中的記錄對於不一樣的數據範圍有效,而不是不一樣的數據點。相對於索引訪問,一般將輸入數據映射到指定數據範圍,飯後取得對於值的過程是比較耗時的,這實際上是一種用時間換空間的方式。具體採用哪一種表驅動方法,就看時間和空間哪一個對你更重要了。

高質量的子程序

建立子程序最主要的目的是提升程序的可管理性,固然也有其餘一些好的理由。其中,節省代碼空間只是一個次要緣由,更重要的是能提升可讀性、可靠性和可修改性。
高質量的子程序能夠:

  • 下降和隔離複雜度

  • 引入中間層,易懂的代碼

  • 提升可移植性

  • 改善性能

  • 隱藏實現細節,隱藏全局數據

  • 限制變化帶來的影響

  • 造成中央控制點

  • 達到特定的重構目的

高質量的子程序應該是功能上高內聚的,有着良好的命名。說到命名,一直很矛盾,怎樣才能算是一個好的命名?按什麼標準?書中給了參考:

  • 描述子程序所作的全部事情。要完整的描述一個子程序,名字可能會很長,這個時候除了使用縮寫,還應該思考一下這樣的子程序自己是否是有問題。

  • 避免使用無心義或模糊的詞。計算機是明確的,doSomething這樣的函數名只是用來教學。

  • 不要經過數字來標識。看到handle1,handle2這樣的命名是否是很憤怒,哈哈。

  • 根據須要肯定子程序名字的長度。研究代表,變量名的最佳長度是9到15個字符。我不知道這個調查是針對特定編程語言仍是全部編程語言,按理說應該是語言無關,但我怎麼有種感受,Java或者C++代碼的命名廣泛比JS中的要長?
    -給函數命名時要對返回值有所描述。就是說看到函數名就知道它會返回什麼。好比xxx.isReady()看名字就知道返回布爾型,xxx.next()返回下一個與xxx相關的對象。

  • 給過程起名時使用語氣強烈的動賓形式。好比printDocument,checkOrderInfo。可是在面嚮對象語言中,好比JS,一般不用加賓語,由於賓語就是對象自己,好比document.print(),orderInfo.check()。

  • 準確使用對仗詞。好比add/remove,open/close。fileOpen對fileClose,fileOpen對fClose就會很奇怪。

  • 爲經常使用操做肯定命名規則。

書中還說了一個比較有趣的問題,子程序能夠寫多長?理論上認爲的子程序最佳長度是一屏代碼或打印出來一到兩頁紙的長度,約20~200行(原書是50~150行)。人們已經在子程序長度的問題上作了大量統計和研究,但並不是全部的這些統計都適合現代編程。不過有一點,若是你的子程序超過了200行,那你就要當心了。

子程序一般會有參數,如何組織這些參數也是門學問。下面是一些指導原則:

  • 按照輸入-可修改-輸出的順序排列參數,也能夠考慮按照該排列規則對參數進行規範命名。

  • 讓全部子程序參數排列順序保持一致。

  • 使用全部參數。很遺憾,這是JS的先天缺陷,你須要更加當心。

  • 把狀態或者出錯變量放到最後。

  • 不要把子程序的參數用做工做變量,應該在子程序中使用局部變量。

calcDemo(inputVal){
  inputVal = inputVal + currentAdder(inputVal)
  // do something with inputVal
  ...
  return inputVal
}

這樣的代碼雖然沒有任何錯誤,可是容易形成誤解,由於最後返回的inputVal已經不是最初傳入的inputVal了,正確的作法是在函數內部使用局部變量指向inputVal而後返回該局部變量。這裏是工程代碼,不是在競賽網站上,不能爲了簡潔而簡潔,少寫一行代碼並不會給你加分。

  • 在接口中對參數的假定加以說明。

  • 限制子程序的參數個數。7是個很神奇的數字,讓你的參數保持在七個之內。

  • 爲子程序傳遞用以維持其接口抽象的變量或對象。我在不少代碼中發現,函數參數並非一個個變量,而是一個對象,經過該對象來傳遞參數。

這是一個富有爭議的問題。假如一個對象有10個屬性,可是處理方法只用到了3個屬性,那麼直接傳遞對象就暴露了其餘屬性,這破壞了封裝原則,增長了代碼耦合。另外一種觀點則認爲傳遞整個對象能使子程序更加靈活,使接口更加穩定易於擴展。

那到底什麼時候傳變量,什麼時候傳對象呢?做者認爲關鍵在於子程序的接口想要表達何種抽象。若是要表達的抽象是子程序指望的特定數據,那麼應該直接傳數據,若是要表達的抽象是想擁有某個特定對象,就應該傳對象。

好比,你發如今調用子程序以前都要先建立一個對象,調用完後又從對象中取出這些數據,那說明你須要的是數據而非對象。若是你發現本身常常須要修改子程序的參數表,而每次修改的參數都來自同一個對象,那說明你須要的是整個對象。

說完參數,最後來講說返回值。若是把函數按語義劃分,能夠分爲「函數」和「過程」,」函數」有返回值,而「過程」返回void或者沒有返回值。何時使用」函數「,何時使用」過程」,其實經過函數名就應該能肯定下來。好比xxx.next()和xxx.fire(),前者一看就是」函數「,然後者是」過程「。
若是你使用」函數「,確定會存在返回錯誤返回值的風險,尤爲是當函數內有多條分支時。爲減少這一風險,請確保:

  • 檢查全部可能的返回路徑

  • 不要返回指向局部數據的引用或者指針

防護式編程

防護式編程的核心其實就是容錯。當子程序遭遇到各類非法輸入數據時也能工做。對於這些非法數據,一般有三種方式來處理:

  1. 檢查全部來源於外部的數據。文件,用戶,網絡等接口的數據都屬於外部數據,這些都是不安全的。

  2. 檢查子程序全部的輸入參數。子程序的輸入數據來源於其它子程序,這裏作檢查是爲了防止程序內部產生了非預期的數據。

  3. 決定如何處理錯誤的輸入數據。根據項目需求,你能夠返回錯誤碼,記錄日誌,返回一個默認的合法值或返回與前次相同的數據,具體方案視需求而定。

第一點和第二點都是數據校驗,第三點是對校驗結果的處理方式。一切錯誤都來自於輸入輸出。理論上對於全部外部數據都要進行校驗,由於這些數據都是不可靠不肯定的,須要經過一個」過濾系統」將其過濾成肯定類型的數據。這個」過濾系統」就是隔欄(barricade)。在隔欄的外面應該使用錯誤處理技術,在內部應該使用斷言。由於隔欄內部的數據都是被清理過的,若是在內部出錯那應該是程序的錯誤而非數據的錯誤。

還有一種容錯方式叫異常。異常是把代碼中得錯誤或異常事件傳遞給調用方代碼的一種特殊手段。異常跟斷言的使用情景類似,都是用來處理那些罕見或者永遠不該該發生得狀況。書中給出了使用異常的一些建議:

  • 用異常通知程序的其餘部分,進行錯誤消息傳遞。

  • 只有在其餘編碼方式沒法解決的狀況下才使用異常。

  • 不要把本可在局部處理的錯誤當成一個未捕獲的異常拋出去。

  • 避免使用空得catch語句,這是一種不負責任的寫法。

  • 瞭解全部函數庫可能拋出的異常。

  • 創建一套幾種的異常處理機制。

  • 考慮異常的替換方案,確保你的程序是真的須要處理異常。

過分的防護式編程會使程序變得臃腫緩慢,增長軟件的複雜度,變得難以維護。因此在進行編碼時呀考慮好什麼地方須要防護,而後調整優先級,因地制宜。

系列文章

相關文章
相關標籤/搜索