本文將從變量屈暗,語(yǔ)句,代碼塊脂男,子程序养叛,到類以及框架設(shè)計(jì),詳細(xì)描述了如何編寫高質(zhì)量的程序宰翅。盡管大部分原則你可能都知道了弃甥,但還是有些點(diǎn)會(huì)帶給你驚喜。
變量
變量初始化原則
- 聲明的時(shí)候初始化
- 在靠近變量第一次使用的位置初始化汁讼,就近原則淆攻。
- 理想情況下,在靠近第一次使用變量的位置聲明和定義該變量嘿架,但是在JS里面卻習(xí)慣將變量聲明提前瓶珊。
- 注意計(jì)數(shù)器和累加器的修改。
- 在類的構(gòu)造函數(shù)中初始化數(shù)據(jù)成員
- 確定是否需要重新初始化
- 把每個(gè)變量用于唯一用途
變量作用域優(yōu)化
作用域指變量在程序內(nèi)的可見(jiàn)和可引用范圍耸彪。介于同一變量多個(gè)引用點(diǎn)之間的代碼可稱為”攻擊窗口(window of vulnerability)”,應(yīng)把變量的引用點(diǎn)盡可能集中在一起伞芹,減小”攻擊窗口“的范圍。
- 盡量縮短變量的引用范圍
- 盡量縮短變量的存活時(shí)間
- 把相關(guān)語(yǔ)句提取成單獨(dú)的子程序
- 盡量少使用全局變量搜囱。使用全局變量可以讓程序?qū)懫饋?lái)很方便丑瞧,因?yàn)槿肿兞靠梢噪S時(shí)訪問(wèn)和使用柑土,但是這樣很難維護(hù)和管理蜀肘,如果換人來(lái)維護(hù)這些代碼他很難知道這些變量在哪里在什么時(shí)候會(huì)被修改绊汹。
變量命名原則
- 規(guī)范命名的目的是提高程序的可讀性同時(shí)易于調(diào)試
- 變量名需要準(zhǔn)確描述其代表的事物
- 變量名的平均長(zhǎng)度在10到16個(gè)字符時(shí)更易于調(diào)試。這并不是說(shuō)你要把所有的變量都控制在這個(gè)范圍扮宠,命名的最終目的提高可讀性和可維護(hù)性西乖,當(dāng)你檢查代碼時(shí)發(fā)現(xiàn)大部分變量名都很短或者含義不清時(shí),那你的命名肯定有問(wèn)題
- 長(zhǎng)名變量適合全局變量坛增,短名的適合局部變量
- 將計(jì)算值限定詞作為后綴获雕。Total,Sum,Average,Max,Min,Str,Pointer等表示計(jì)算的限定詞一般放在后面。
- 使用業(yè)界約定俗稱的變量收捣。比如i,j,temp,flag這些届案,不用解釋都知道。
- 使用團(tuán)隊(duì)命名規(guī)范罢艾,不同團(tuán)隊(duì)楣颠,不同語(yǔ)言的命名原則會(huì)有不同,優(yōu)先服從規(guī)范咐蚯。
代碼閱讀的次數(shù)遠(yuǎn)大于編寫的次數(shù)童漩,確保你的名字更易于閱讀,而不是易于編寫春锋。
變量縮寫原則
- 使用標(biāo)準(zhǔn)的縮寫矫膨,如min,sub,str等
- 去掉所有非前置元音,如computer->cmptr,screen->scrn,apple->appl
- 去掉虛詞and,or,the等
- 使用單詞的前幾個(gè)字母期奔,統(tǒng)一在每個(gè)單詞的第N個(gè)字母后截?cái)?/li>
- 去除無(wú)用后綴侧馅,如ing,ed等
- 保留每個(gè)音節(jié)中最引人注意的發(fā)音
- 確保不要因?yàn)榭s寫而改變了變量的含義,或者縮寫后的變量名有歧義或者很難理解
語(yǔ)句
直線型代碼
組織直線型代碼最主要的原則就是按照依賴關(guān)系進(jìn)行排列呐萌。所謂依賴關(guān)系就是下一行代碼是否會(huì)依賴上一行代碼的執(zhí)行馁痴,是則為順序相關(guān)依賴,否則為順序無(wú)關(guān)依賴搁胆∶指悖可以用好的子程序名,參數(shù)列表渠旁,注釋來(lái)讓順序相關(guān)依賴變得更明顯攀例。如果代碼之間沒(méi)有順序依賴關(guān)系,那就設(shè)法使相關(guān)的語(yǔ)句盡可能地接近顾腊。
條件語(yǔ)句
if語(yǔ)句使用原則
- 先處理正常路徑粤铭,再處理不常見(jiàn)情況
- 考慮else語(yǔ)句。雖然5到8成的代碼都會(huì)有else語(yǔ)句杂靶,但有些情況是在程序一開(kāi)始就做一個(gè)if判斷梆惯,是則返回酱鸭,不執(zhí)行后面的代碼,這樣可以避免將后面的代碼全都嵌套在else子語(yǔ)句中垛吗。但無(wú)論是否有else,請(qǐng)都將子句用大括號(hào)括起來(lái)凹髓。
- 簡(jiǎn)化復(fù)雜的條件檢測(cè)。在if/elseif語(yǔ)句中怯屉,經(jīng)常會(huì)有很復(fù)雜的邏輯判斷蔚舀,為了提高可讀性,可將這些邏輯判斷封裝成布爾函數(shù)锨络。
- 考慮將if/elseif 替換成case.
case 語(yǔ)句
case語(yǔ)句適合處理簡(jiǎn)單易分類的數(shù)據(jù)赌躺,如果你的數(shù)據(jù)并不簡(jiǎn)單,請(qǐng)使用if/elseif語(yǔ)句羡儿。
- 按字母/數(shù)字順序排列各種情況
- 優(yōu)先處理正常情況
- 按執(zhí)行效率排列case語(yǔ)句
- 如果在某個(gè)case后面沒(méi)有break,請(qǐng)注釋說(shuō)明礼患。
- 利用default子句來(lái)檢測(cè)錯(cuò)誤
表驅(qū)動(dòng)法
直接訪問(wèn)表
在前端開(kāi)發(fā),針對(duì)后臺(tái)返回的錯(cuò)誤碼掠归,通常不會(huì)直接用if/else判斷錯(cuò)誤碼來(lái)顯示相應(yīng)地錯(cuò)誤信息缅叠,而是將錯(cuò)誤碼-錯(cuò)誤提示存放在”表“對(duì)象中,通過(guò)傳入錯(cuò)誤碼來(lái)返回錯(cuò)誤提示拂到,這就是最簡(jiǎn)單的表驅(qū)動(dòng)法——直接訪問(wèn)表痪署。
當(dāng)然我們可能會(huì)遇到更加復(fù)雜的情況,比如某活動(dòng)要給1到100歲的人提供優(yōu)惠兄旬,不同年齡的人群優(yōu)惠可能相同也可能不同狼犯。如果將年齡作為key,優(yōu)惠作為value领铐,那么最笨得方法是存儲(chǔ)100個(gè)鍵值對(duì)悯森,當(dāng)然這里面的值會(huì)有重復(fù)的。
解決方法就是做鍵值轉(zhuǎn)換绪撵,將年齡轉(zhuǎn)化成另外一個(gè)鍵瓢姻,然后讓該鍵對(duì)應(yīng)到具體優(yōu)惠。
索引訪問(wèn)表
鍵值轉(zhuǎn)換提供了一個(gè)很好地思路音诈,那就是將表的”查詢條件“和”查詢記錄"分開(kāi)管理幻碱,建立索引。索引訪問(wèn)表適合處理表記錄占用空間比較大得情況细溅,操作索引中的記錄往往比操作主表本身的記錄更方便廉價(jià)褥傍,并且由于索引和主表是分開(kāi)的,同一個(gè)主表可以根據(jù)不同查詢條件建立不同索引喇聊,靈活性更強(qiáng)恍风,后期可維護(hù)性也更好。
階梯訪問(wèn)表
索引訪問(wèn)的一個(gè)問(wèn)題就是如果鍵的取值范圍很大的話,那建立的索引就會(huì)很長(zhǎng)很占空間朋贬,階梯訪問(wèn)表則是對(duì)某些情況下的一種優(yōu)化凯楔。
階梯訪問(wèn)的基本思想是:表中的記錄對(duì)于不同的數(shù)據(jù)范圍有效,而不是不同的數(shù)據(jù)點(diǎn)锦募。相對(duì)于索引訪問(wèn)摆屯,通常將輸入數(shù)據(jù)映射到指定數(shù)據(jù)范圍,飯后取得對(duì)于值的過(guò)程是比較耗時(shí)的御滩,這其實(shí)是一種用時(shí)間換空間的方式鸥拧。具體采用哪種表驅(qū)動(dòng)方法党远,就看時(shí)間和空間哪個(gè)對(duì)你更重要了削解。
高質(zhì)量的子程序
創(chuàng)建子程序最主要的目的是提高程序的可管理性,當(dāng)然也有其他一些好的理由沟娱。其中氛驮,節(jié)省代碼空間只是一個(gè)次要原因,更重要的是能提高可讀性济似、可靠性和可修改性矫废。
高質(zhì)量的子程序可以:
- 降低和隔離復(fù)雜度
- 引入中間層,易懂的代碼
- 提高可移植性
- 改善性能
- 隱藏實(shí)現(xiàn)細(xì)節(jié)砰蠢,隱藏全局?jǐn)?shù)據(jù)
- 限制變化帶來(lái)的影響
- 形成中央控制點(diǎn)
- 達(dá)到特定的重構(gòu)目的
高質(zhì)量的子程序應(yīng)該是功能上高內(nèi)聚的蓖扑,有著良好的命名。說(shuō)到命名台舱,一直很矛盾律杠,怎樣才能算是一個(gè)好的命名?按什么標(biāo)準(zhǔn)竞惋?書中給了參考:
- 描述子程序所做的所有事情柜去。要完整的描述一個(gè)子程序,名字可能會(huì)很長(zhǎng)拆宛,這個(gè)時(shí)候除了使用縮寫嗓奢,還應(yīng)該思考一下這樣的子程序本身是不是有問(wèn)題。
- 避免使用無(wú)意義或模糊的詞浑厚。計(jì)算機(jī)是明確的股耽,doSomething這樣的函數(shù)名只是用來(lái)教學(xué)。
- 不要通過(guò)數(shù)字來(lái)標(biāo)識(shí)钳幅∥矧看到handle1,handle2這樣的命名是不是很憤怒,哈哈贡这。
- 根據(jù)需要確定子程序名字的長(zhǎng)度茬末。研究表明,變量名的最佳長(zhǎng)度是9到15個(gè)字符。我不知道這個(gè)調(diào)查是針對(duì)特定編程語(yǔ)言還是所有編程語(yǔ)言丽惭,按理說(shuō)應(yīng)該是語(yǔ)言無(wú)關(guān)击奶,但我怎么有種感覺(jué),Java或者C++代碼的命名普遍比JS中的要長(zhǎng)责掏?
-給函數(shù)命名時(shí)要對(duì)返回值有所描述柜砾。就是說(shuō)看到函數(shù)名就知道它會(huì)返回什么。比如xxx.isReady()看名字就知道返回布爾型换衬,xxx.next()返回下一個(gè)與xxx相關(guān)的對(duì)象痰驱。 - 給過(guò)程起名時(shí)使用語(yǔ)氣強(qiáng)烈的動(dòng)賓形式。比如printDocument,checkOrderInfo瞳浦。但是在面向?qū)ο笳Z(yǔ)言中担映,比如JS,通常不用加賓語(yǔ)叫潦,因?yàn)橘e語(yǔ)就是對(duì)象本身蝇完,比如document.print(),orderInfo.check()。
- 準(zhǔn)確使用對(duì)仗詞矗蕊。比如add/remove,open/close短蜕。fileOpen對(duì)fileClose,fileOpen對(duì)fClose就會(huì)很奇怪傻咖。
- 為常用操作確定命名規(guī)則朋魔。
書中還說(shuō)了一個(gè)比較有趣的問(wèn)題,子程序可以寫多長(zhǎng)卿操?理論上認(rèn)為的子程序最佳長(zhǎng)度是一屏代碼或打印出來(lái)一到兩頁(yè)紙的長(zhǎng)度警检,約20200行(原書是50150行)。人們已經(jīng)在子程序長(zhǎng)度的問(wèn)題上做了大量統(tǒng)計(jì)和研究硬纤,但并非所有的這些統(tǒng)計(jì)都適合現(xiàn)代編程解滓。不過(guò)有一點(diǎn),如果你的子程序超過(guò)了200行筝家,那你就要小心了洼裤。
子程序通常會(huì)有參數(shù),如何組織這些參數(shù)也是門學(xué)問(wèn)溪王。下面是一些指導(dǎo)原則:
- 按照輸入-可修改-輸出的順序排列參數(shù)腮鞍,也可以考慮按照該排列規(guī)則對(duì)參數(shù)進(jìn)行規(guī)范命名。
- 讓所有子程序參數(shù)排列順序保持一致莹菱。
- 使用所有參數(shù)移国。很遺憾,這是JS的先天缺陷道伟,你需要更加小心迹缀。
- 把狀態(tài)或者出錯(cuò)變量放到最后使碾。
- 不要把子程序的參數(shù)用作工作變量,應(yīng)該在子程序中使用局部變量祝懂。
calcDemo(inputVal){
inputVal = inputVal + currentAdder(inputVal)
// do something with inputVal
...
return inputVal
}
這樣的代碼雖然沒(méi)有任何錯(cuò)誤票摇,但是容易造成誤解,因?yàn)樽詈蠓祷氐膇nputVal已經(jīng)不是最初傳入的inputVal了砚蓬,正確的做法是在函數(shù)內(nèi)部使用局部變量指向inputVal然后返回該局部變量矢门。這里是工程代碼,不是在競(jìng)賽網(wǎng)站上灰蛙,不能為了簡(jiǎn)潔而簡(jiǎn)潔祟剔,少寫一行代碼并不會(huì)給你加分。
- 在接口中對(duì)參數(shù)的假定加以說(shuō)明摩梧。
- 限制子程序的參數(shù)個(gè)數(shù)物延。7是個(gè)很神奇的數(shù)字,讓你的參數(shù)保持在七個(gè)以內(nèi)障本。
- 為子程序傳遞用以維持其接口抽象的變量或?qū)ο蠼探臁N以诤芏啻a中發(fā)現(xiàn),函數(shù)參數(shù)并不是一個(gè)個(gè)變量驾霜,而是一個(gè)對(duì)象,通過(guò)該對(duì)象來(lái)傳遞參數(shù)买置。
這是一個(gè)富有爭(zhēng)議的問(wèn)題粪糙。假如一個(gè)對(duì)象有10個(gè)屬性,但是處理方法只用到了3個(gè)屬性忿项,那么直接傳遞對(duì)象就暴露了其他屬性蓉冈,這破壞了封裝原則,增加了代碼耦合轩触。另一種觀點(diǎn)則認(rèn)為傳遞整個(gè)對(duì)象能使子程序更加靈活寞酿,使接口更加穩(wěn)定易于擴(kuò)展。
那到底何時(shí)傳變量脱柱,何時(shí)傳對(duì)象呢伐弹?作者認(rèn)為關(guān)鍵在于子程序的接口想要表達(dá)何種抽象。如果要表達(dá)的抽象是子程序期望的特定數(shù)據(jù)榨为,那么應(yīng)該直接傳數(shù)據(jù)惨好,如果要表達(dá)的抽象是想擁有某個(gè)特定對(duì)象,就應(yīng)該傳對(duì)象随闺。
比如日川,你發(fā)現(xiàn)在調(diào)用子程序之前都要先創(chuàng)建一個(gè)對(duì)象,調(diào)用完后又從對(duì)象中取出這些數(shù)據(jù)矩乐,那說(shuō)明你需要的是數(shù)據(jù)而非對(duì)象龄句。如果你發(fā)現(xiàn)自己經(jīng)常需要修改子程序的參數(shù)表,而每次修改的參數(shù)都來(lái)自同一個(gè)對(duì)象,那說(shuō)明你需要的是整個(gè)對(duì)象分歇。
說(shuō)完參數(shù)透葛,最后來(lái)說(shuō)說(shuō)返回值。如果把函數(shù)按語(yǔ)義劃分卿樱,可以分為“函數(shù)”和“過(guò)程”僚害,”函數(shù)”有返回值,而“過(guò)程”返回void或者沒(méi)有返回值繁调。什么時(shí)候使用”函數(shù)“,什么時(shí)候使用”過(guò)程”萨蚕,其實(shí)通過(guò)函數(shù)名就應(yīng)該能確定下來(lái)。比如xxx.next()和xxx.fire()蹄胰,前者一看就是”函數(shù)“岳遥,而后者是”過(guò)程“。
如果你使用”函數(shù)“裕寨,肯定會(huì)存在返回錯(cuò)誤返回值的風(fēng)險(xiǎn)浩蓉,尤其是當(dāng)函數(shù)內(nèi)有多條分支時(shí)。為減小這一風(fēng)險(xiǎn)宾袜,請(qǐng)確保:
- 檢查所有可能的返回路徑
- 不要返回指向局部數(shù)據(jù)的引用或者指針
防御式編程
防御式編程的核心其實(shí)就是容錯(cuò)捻艳。當(dāng)子程序遭遇到各種非法輸入數(shù)據(jù)時(shí)也能工作。對(duì)于這些非法數(shù)據(jù)庆猫,通常有三種方式來(lái)處理:
- 檢查所有來(lái)源于外部的數(shù)據(jù)认轨。文件,用戶月培,網(wǎng)絡(luò)等接口的數(shù)據(jù)都屬于外部數(shù)據(jù)嘁字,這些都是不安全的。
- 檢查子程序所有的輸入?yún)?shù)杉畜。子程序的輸入數(shù)據(jù)來(lái)源于其它子程序纪蜒,這里做檢查是為了防止程序內(nèi)部產(chǎn)生了非預(yù)期的數(shù)據(jù)。
- 決定如何處理錯(cuò)誤的輸入數(shù)據(jù)此叠。根據(jù)項(xiàng)目需求纯续,你可以返回錯(cuò)誤碼,記錄日志拌蜘,返回一個(gè)默認(rèn)的合法值或返回與前次相同的數(shù)據(jù)杆烁,具體方案視需求而定。
第一點(diǎn)和第二點(diǎn)都是數(shù)據(jù)校驗(yàn)简卧,第三點(diǎn)是對(duì)校驗(yàn)結(jié)果的處理方式兔魂。一切錯(cuò)誤都來(lái)自于輸入輸出。理論上對(duì)于所有外部數(shù)據(jù)都要進(jìn)行校驗(yàn)举娩,因?yàn)檫@些數(shù)據(jù)都是不可靠不確定的析校,需要通過(guò)一個(gè)”過(guò)濾系統(tǒng)”將其過(guò)濾成確定類型的數(shù)據(jù)构罗。這個(gè)”過(guò)濾系統(tǒng)”就是隔欄(barricade)。在隔欄的外面應(yīng)該使用錯(cuò)誤處理技術(shù)智玻,在內(nèi)部應(yīng)該使用斷言遂唧。因?yàn)楦魴趦?nèi)部的數(shù)據(jù)都是被清理過(guò)的,如果在內(nèi)部出錯(cuò)那應(yīng)該是程序的錯(cuò)誤而非數(shù)據(jù)的錯(cuò)誤吊奢。
還有一種容錯(cuò)方式叫異常盖彭。異常是把代碼中得錯(cuò)誤或異常事件傳遞給調(diào)用方代碼的一種特殊手段。異常跟斷言的使用情景相似页滚,都是用來(lái)處理那些罕見(jiàn)或者永遠(yuǎn)不應(yīng)該發(fā)生得情況召边。書中給出了使用異常的一些建議:
- 用異常通知程序的其他部分,進(jìn)行錯(cuò)誤消息傳遞裹驰。
- 只有在其他編碼方式無(wú)法解決的情況下才使用異常隧熙。
- 不要把本可在局部處理的錯(cuò)誤當(dāng)成一個(gè)未捕獲的異常拋出去。
- 避免使用空得catch語(yǔ)句幻林,這是一種不負(fù)責(zé)任的寫法贞盯。
- 了解所有函數(shù)庫(kù)可能拋出的異常。
- 建立一套幾種的異常處理機(jī)制沪饺。
- 考慮異常的替換方案躏敢,確保你的程序是真的需要處理異常。
過(guò)度的防御式編程會(huì)使程序變得臃腫緩慢随闽,增加軟件的復(fù)雜度父丰,變得難以維護(hù)。所以在進(jìn)行編碼時(shí)呀考慮好什么地方需要防御掘宪,然后調(diào)整優(yōu)先級(jí),因地制宜攘烛。