CR常见代码问题
http://www.cnblogs.com/lovesqcc/p/9271781.html
路線圖
常見(jiàn)代碼問(wèn)題
空值
未捕獲潛在的異常
低性能
影響范圍過(guò)大
單測(cè)問(wèn)題
與原有業(yè)務(wù)邏輯不兼容
缺乏必要日志
錯(cuò)誤碼不符合規(guī)范
參數(shù)檢測(cè)缺乏或不足
引用錯(cuò)誤
名字沖突
細(xì)節(jié)錯(cuò)誤
多重條件
文不符實(shí)
跨語(yǔ)言或跨系統(tǒng)交互
可維護(hù)性問(wèn)題
硬編碼
重復(fù)代碼
通用邏輯與定制業(yè)務(wù)邏輯耦合
直接在原方法里加邏輯
多業(yè)務(wù)耦合
代碼層次不合理
不用多余的代碼
使用全局變量
缺乏必要的注釋
更難發(fā)現(xiàn)的錯(cuò)誤
并發(fā)
資源泄露
事務(wù)
SQL問(wèn)題
安全問(wèn)題
設(shè)計(jì)問(wèn)題
較輕微的問(wèn)題
命名不貼切
聲明時(shí)未初始化
風(fēng)格與整體有不一致
類型轉(zhuǎn)換錯(cuò)誤
否定式風(fēng)格
容器遍歷的結(jié)構(gòu)變更
API參數(shù)傳遞錯(cuò)誤
單行調(diào)用括號(hào)過(guò)多
修改方法簽名
打印日志太多
多級(jí)數(shù)據(jù)結(jié)構(gòu)
作用域過(guò)大
分支與循環(huán)
殘留的無(wú)用代碼
代碼與文檔不一致
使用冷僻用法或奇淫巧技
路線圖
常見(jiàn)代碼問(wèn)題
常見(jiàn)的潛在代碼問(wèn)題是當(dāng)前直接會(huì)導(dǎo)致BUG、故障或者產(chǎn)品功能不能正常工作的類別。
空值
空值恐怕是最容易出現(xiàn)的地方之一。 常見(jiàn)錯(cuò)誤有: a. 值為NULL導(dǎo)致空指針異常; b. 參數(shù)字符串含有前導(dǎo)或后綴空格沒(méi)有Trim導(dǎo)致查詢?yōu)榭铡?導(dǎo)致以上結(jié)果的原因主要有: 無(wú)此記錄、有此記錄但由于SQL訪問(wèn)異常而沒(méi)查到、網(wǎng)絡(luò)調(diào)用失敗、記錄中有臟數(shù)據(jù)、參數(shù)沒(méi)傳。
原則上,對(duì)于任何異常, 希望能夠打印出具體的錯(cuò)誤信息,根據(jù)錯(cuò)誤信息很快明白是什么原因, 而不是一個(gè) null ,還要在代碼里去推敲為什么為空。這樣我們必須識(shí)別出程序中可能的null, 并及時(shí)檢測(cè)、捕獲和拋出異常。
對(duì)于空值,最好的防護(hù)是“防御式編程”。當(dāng)獲取到對(duì)象之后, 使用之前總是判斷是否為空,并適當(dāng)拋出異常、打錯(cuò)誤日志或做其它處理。 有的人嫌檢測(cè)為空的 if 語(yǔ)句充斥在代碼里會(huì)破壞代碼的可維護(hù)性, 對(duì)此我的建議是:
- 空值檢測(cè)一定要有, 有勝于無(wú)。
- 在空值檢測(cè)總是存在的前提下, 可以優(yōu)化空值檢測(cè)的方法和存在形式。 比如集中于一個(gè)類 NullChecker 中管理,并與系統(tǒng)的整體錯(cuò)誤處理設(shè)計(jì)保持一致。集中管理和處理一致性原則可以作為系統(tǒng)設(shè)計(jì)的一個(gè)準(zhǔn)則。 這樣主流程中只要增加一行調(diào)用即可, 既可以天網(wǎng)恢恢疏而不漏地檢測(cè)對(duì)象為空, 也不會(huì)讓代碼顯得難看。
- 在參數(shù)入口處統(tǒng)一做 trim。 如果在業(yè)務(wù)邏輯里做 trim , 就會(huì)導(dǎo)致有的業(yè)務(wù)邏輯做了 trim , 有的沒(méi)做, 體現(xiàn)在產(chǎn)品上就會(huì)有令用戶困惑的事情發(fā)生。 比如搜索和導(dǎo)出業(yè)務(wù), 搜索能搜索出來(lái), 導(dǎo)出卻沒(méi)有。
未捕獲潛在的異常
第二個(gè)容易出錯(cuò)的地方是未捕獲潛在的異常。調(diào)用API接口、庫(kù)函數(shù)或系統(tǒng)服務(wù)等,只顧著享受便利卻不做防護(hù),常導(dǎo)致因?yàn)榫植渴《绊懻w的功能。最好的防護(hù)依然是“防御式編程”。 要么在當(dāng)前方法捕獲異常并返回合適的空值或空對(duì)象,要么拋給高層處理。
切不可默默"吞掉錯(cuò)誤和異常"。 如果這樣做了, 出問(wèn)題了等著加班和耗費(fèi)大量腦細(xì)胞吧!
在CodeReview的時(shí)候一定要仔細(xì)詢問(wèn):這里是否可能會(huì)拋出異常?如果拋異常會(huì)怎么處理?是否會(huì)影響整體服務(wù)和返回結(jié)果?
低性能
低性能會(huì)導(dǎo)致產(chǎn)品功能不好用、不可用,甚至導(dǎo)致產(chǎn)品失敗。
常見(jiàn)情況有:a. 循環(huán)地逐個(gè)調(diào)用單個(gè)接口獲取數(shù)據(jù)或訪問(wèn)數(shù)據(jù)庫(kù); b. 重復(fù)創(chuàng)建幾乎完全相同的(開(kāi)銷大的)對(duì)象;c. 數(shù)據(jù)庫(kù)訪問(wèn)、網(wǎng)絡(luò)調(diào)用等服務(wù)未處理超時(shí)的情況; d. 多重循環(huán)對(duì)于大數(shù)據(jù)量處理的算法性能低;e. 大量字符串拼接時(shí)使用了String而非StringBuilder.
對(duì)于 a,最好提供批量接口或批量并發(fā)獲取數(shù)據(jù); 對(duì)于 b, 將可復(fù)用對(duì)象抽離出循環(huán),一次創(chuàng)建多次使用; 對(duì)于 c,設(shè)置合理的超時(shí)時(shí)間并捕獲超時(shí)異常處理; 對(duì)于 d,使用預(yù)排序或預(yù)處理, 構(gòu)造合適的數(shù)據(jù)結(jié)構(gòu), 使得算法平均性能在 O(n) 或 O(nlogn) ; 對(duì)于 e, 記住: 少量字符串拼接使用String, 大量字符串拼接使用 StringBuilder, 通常不會(huì)使用到 StringBuffer.
影響范圍過(guò)大
對(duì)多個(gè)模塊依賴的公共函數(shù)的修改,容易造成影響范圍超過(guò)當(dāng)前業(yè)務(wù)改動(dòng),無(wú)意識(shí)地破壞依賴于該公共函數(shù)的其他業(yè)務(wù)。要特別慎重。可靠的方式是:先查看該公共函數(shù)的調(diào)用, 如果只有自己的業(yè)務(wù)用,可適當(dāng)大膽一些; 如果有多個(gè)地方依賴,抽離一個(gè)新的函數(shù),抽離原函數(shù)里的可復(fù)用部分,然后基于可復(fù)用部分構(gòu)建新的函數(shù)。修改原則遵循“開(kāi)閉”原則,才能盡可能使改動(dòng)影響降低到最小化。
基類及實(shí)例字段和方法也屬于公共函數(shù)的范疇。 盡量不要修改基類的東西。
單測(cè)問(wèn)題
單測(cè)是保證工程質(zhì)量的第一道重要防線。單測(cè)問(wèn)題一般包括: a. 單測(cè)未全部通過(guò); b. 重要業(yè)務(wù)邏輯缺乏單測(cè); c. 缺乏異常單測(cè); d. 代碼變更或BUG修復(fù)缺乏單測(cè)。
單測(cè)全部通過(guò)應(yīng)當(dāng)是提交代碼到代碼庫(kù)以及代碼Review的前提條件。代碼提交者應(yīng)當(dāng)保證單測(cè)全部通過(guò)。沒(méi)有捷徑可走。僅當(dāng)單測(cè)全部通過(guò)才提交到代碼庫(kù), 可以通過(guò)工具自動(dòng)化實(shí)現(xiàn)。 對(duì)于 maven 管理的工程, 只需一個(gè)命令: mvn test && git push origin branch_name 。 單測(cè)應(yīng)當(dāng)更注重質(zhì),而非單純追求覆蓋率。
缺乏單測(cè)的重要業(yè)務(wù)邏輯就像裸露在空氣中的電線一樣,雖然能跑起來(lái),卻是很容易“觸電”的。 方法: 增加覆蓋比較全面的單測(cè)。
缺乏異常單測(cè)也是代碼提交者常忽略的問(wèn)題。 異常也是一種實(shí)際的業(yè)務(wù)場(chǎng)景,反映系統(tǒng)的健壯性和友好性。異常應(yīng)該有相應(yīng)的單元測(cè)試覆蓋。創(chuàng)建條件使之拋出異常,并判斷異常是否是指定異常;若沒(méi)有拋出異常或者不是指定異常,則應(yīng)該 AssertFailed 而不是通過(guò)。
對(duì)于代碼變更和BUG修復(fù),如果當(dāng)時(shí)由于時(shí)間緊而沒(méi)有寫,后續(xù)應(yīng)當(dāng)補(bǔ)上。對(duì)于每個(gè)代碼變更和BUG,都可以抽離出相應(yīng)的代碼部分, 并有相應(yīng)單測(cè)覆蓋,并注明原因。
與原有業(yè)務(wù)邏輯不兼容
改動(dòng)針對(duì)當(dāng)前需求是合理的,卻與原有業(yè)務(wù)邏輯不兼容,也是常見(jiàn)的問(wèn)題。比如增加一個(gè)搜索條件, 卻不能與原有條件聯(lián)合查詢。
與原有業(yè)務(wù)不兼容, 一般出現(xiàn)在:
業(yè)務(wù)邏輯的兼容問(wèn)題一般體現(xiàn)在系統(tǒng)的復(fù)用性和可擴(kuò)展機(jī)制上。良好的系統(tǒng)可復(fù)用性和可擴(kuò)展性可以更容易地做到業(yè)務(wù)邏輯兼容。 主要有如下幾種級(jí)別:
如何應(yīng)對(duì)呢?
核心不變, 外圍定制化。
缺乏必要日志
對(duì)于重要而關(guān)鍵的實(shí)例狀態(tài)、代碼路徑及API調(diào)用,應(yīng)當(dāng)添加適當(dāng)?shù)腎NFO日志;對(duì)于異常,應(yīng)當(dāng)捕獲并添加Error日志。缺乏日志并不會(huì)影響業(yè)務(wù)功能,但出現(xiàn)問(wèn)題排查時(shí),就會(huì)非常不方便,甚至錯(cuò)失極寶貴的機(jī)會(huì)(不易重現(xiàn)的情況尤其如此)。此外,缺乏日志也會(huì)導(dǎo)致可控性差,難以做數(shù)據(jù)統(tǒng)計(jì)和分析。
錯(cuò)誤碼不符合規(guī)范
錯(cuò)誤碼本身不算是代碼問(wèn)題,不過(guò)基于整個(gè)組織和工程的可維護(hù)性來(lái)說(shuō),可以將錯(cuò)誤碼不符合規(guī)范作為一種錯(cuò)誤加以避免。方法: 對(duì)錯(cuò)誤碼進(jìn)行可控的管理和遵循規(guī)范使用。可以使用公共文檔維護(hù), 也可以開(kāi)發(fā)錯(cuò)誤碼管理系統(tǒng)來(lái)避免相同的錯(cuò)誤碼。
參數(shù)檢測(cè)缺乏或不足
參數(shù)檢測(cè)是對(duì)業(yè)務(wù)處理的第一層重要過(guò)濾。如果參數(shù)檢測(cè)不足夠,就會(huì)導(dǎo)致臟數(shù)據(jù)進(jìn)入服務(wù)處理,輕則導(dǎo)致異常,重則插入臟數(shù)據(jù)到數(shù)據(jù)庫(kù),對(duì)后續(xù)維護(hù)都會(huì)造成很多維護(hù)成本。方法: 采用“契約式編程”,規(guī)定前置條件,并使用單測(cè)進(jìn)行覆蓋。
對(duì)于復(fù)雜的業(yè)務(wù)應(yīng)用, 優(yōu)雅的參數(shù)檢測(cè)處理尤為重要。 根據(jù) “集中管理和處理一致性原則”, 可以建立一個(gè) paramchecker 包, 設(shè)計(jì)一個(gè)可復(fù)用的微框架來(lái)對(duì)應(yīng)用中所有的參數(shù)進(jìn)行統(tǒng)一集中化檢測(cè)。參數(shù)檢測(cè)主要包括: (1) 參數(shù)的值類型, 可以根據(jù)不同值類型做基礎(chǔ)的檢測(cè); (2) 參數(shù)的業(yè)務(wù)類型, 有基礎(chǔ)非業(yè)務(wù)參數(shù), 基礎(chǔ)業(yè)務(wù)參數(shù)和具體業(yè)務(wù)參數(shù)。 不同的參數(shù)業(yè)務(wù)類型有不同的處理。 將參數(shù)值類型與參數(shù)業(yè)務(wù)類型結(jié)合起來(lái), 結(jié)合一致性的異常捕獲處理, 就可以實(shí)現(xiàn)一個(gè)可復(fù)用的參數(shù)檢測(cè)框架。參數(shù)檢測(cè)既可以采用普通的分支語(yǔ)句,也可以采用注解方式。采用注解方式更可讀,不過(guò)單測(cè)編寫更具技巧。
引用錯(cuò)誤
對(duì)于動(dòng)態(tài)語(yǔ)言, 由于缺乏強(qiáng)大的靜態(tài)代碼檢測(cè),修改了類引用的地方尤其要注意,很可能導(dǎo)致依賴的其他業(yè)務(wù)出錯(cuò); 尤其是修改重名引用時(shí)。有線上故障教訓(xùn)。PHP工程中含有兩個(gè) Format 類, 一個(gè)基礎(chǔ)的一個(gè)業(yè)務(wù)相關(guān)的, 被改動(dòng)的類文件里開(kāi)始沒(méi)有指明引用,默認(rèn)采用了基礎(chǔ) Format 類的實(shí)現(xiàn), 然后提交者在改動(dòng)文件頭增加了對(duì)業(yè)務(wù) Format 的引用, 導(dǎo)致依賴于基礎(chǔ)Format類的其他業(yè)務(wù)不能正常工作。避免引用錯(cuò)誤的方法: 當(dāng)要在文件里增加新的類引用時(shí), 先在文件里搜索是否有重名類的引用。如果有, 就要格外小心了。
名字沖突
引用錯(cuò)誤實(shí)際上是名字沖突的一種情形。名字沖突常常出現(xiàn)在自定義函數(shù)命名跟庫(kù)函數(shù)名字一樣的情況下。此時(shí),自定義函數(shù)的定義會(huì)覆蓋庫(kù)函數(shù),導(dǎo)致在某一處正常,而其他地方出問(wèn)題。因此,在命名時(shí)要足夠有意識(shí),避免和庫(kù)函數(shù)命名沖突。
細(xì)節(jié)錯(cuò)誤
比如邏輯運(yùn)算符誤寫、優(yōu)先級(jí)錯(cuò)誤、長(zhǎng)整型截?cái)唷⒁绯觥?shù)組越界、JSON解析出錯(cuò)、函數(shù)參數(shù)傳遞出錯(cuò)、API 版本不對(duì)、使用網(wǎng)上拷貝的未經(jīng)測(cè)試的代碼、不成熟的算法、傳值與傳引用、相等性比較等。
對(duì)于數(shù)組越界錯(cuò)誤, 通常要對(duì)空數(shù)組、針對(duì)數(shù)組大小的邊界值+1和-1寫單測(cè)來(lái)避免; 使用網(wǎng)上拷貝的代碼,誠(chéng)然可節(jié)省時(shí)間,也一定要加工一下并用單測(cè)覆蓋; 傳值和傳引用可通過(guò)單測(cè)來(lái)避免錯(cuò)誤; 對(duì)象的相等性比較切忌使用等號(hào)=。
多重條件
類似 if ((!A || !B) && C || (D && E)) 的多重條件要仔細(xì)推敲。方法: 最好拆分成多個(gè)有含義變量。 isNotDelay = !A || !B ; isNormal = C ; isAllow = D && E ; cond = isNotDelay && isNormal || isAllow 。
文不符實(shí)
文不符實(shí)是一種可能導(dǎo)致線上故障的錯(cuò)誤。比如一個(gè) getXXX 的函數(shù),結(jié)果里面還做了 add, update 的操作。對(duì)問(wèn)題排查、產(chǎn)品運(yùn)維等都有非常大的殺傷力。因此命名一定要用實(shí)質(zhì)內(nèi)容相符,除非是故意搞破壞。
跨語(yǔ)言或跨系統(tǒng)交互
稍具規(guī)模的互聯(lián)網(wǎng)創(chuàng)業(yè)公司通常會(huì)采用多語(yǔ)言開(kāi)發(fā),比如PHP作為前端,Java作為后臺(tái)服務(wù)。當(dāng)動(dòng)態(tài)類型語(yǔ)言與靜態(tài)類型語(yǔ)言交互時(shí),會(huì)有一些問(wèn)題產(chǎn)生。比如PHP的對(duì)象通常是一個(gè)Map, 如果是空對(duì)象就會(huì)寫成 [], 然而 [] 會(huì)被 Java 解析成列表。這樣, 如果數(shù)據(jù)庫(kù)的值是通過(guò) PHP 寫入,那么這個(gè)值既有可能是JSON對(duì)象字符串,也可能是空數(shù)組字符串, Java 來(lái)解析就有點(diǎn)尷尬了。 同樣,當(dāng) Java 調(diào)用 PHP 接口時(shí), 不規(guī)范的PHP接口既可能返回列表,也可能返回 true or false , Java 解析返回結(jié)果也會(huì)比較尷尬。 因此, 在跨語(yǔ)言交互的邊界處,要特別注意這些類型轉(zhuǎn)換的差異。
跨系統(tǒng)交互則主要是接口設(shè)計(jì)與約定的問(wèn)題。同一個(gè)項(xiàng)目里不同業(yè)務(wù)團(tuán)隊(duì)之間的業(yè)務(wù)接口設(shè)計(jì)與約定, 不同企業(yè)里開(kāi)放接口的設(shè)計(jì)與約定, 要在最初深思熟慮,一旦開(kāi)放,在后期很少有接口設(shè)計(jì)改動(dòng)的空間。開(kāi)放接口設(shè)計(jì)要符合小而美、正交的特性, 命名要貼切一致, 參數(shù)取值要指明約束,枚舉參數(shù)要給出列表, 結(jié)果返回要規(guī)范一致,可以采用通用的 {"code":200, "msg": "success", "data": xxx} 。跨系統(tǒng)交互也要統(tǒng)一對(duì)術(shù)語(yǔ)和接口的理解的一致。
可維護(hù)性問(wèn)題
可維護(hù)性問(wèn)題是“在當(dāng)前業(yè)務(wù)變更的范圍內(nèi)通常不會(huì)導(dǎo)致BUG、故障,卻會(huì)在日后埋下地雷,引發(fā)BUG、故障、維護(hù)成本大幅增加”的類別。
硬編碼
硬編碼主要有三種情況: a. “魔數(shù)”; b. 寫死的配置; c. 臨時(shí)加的邏輯和文案。
“魔數(shù)”與重復(fù)代碼類似,當(dāng)前或許不會(huì)引發(fā)問(wèn)題,時(shí)間一長(zhǎng),為了弄清楚其代表的含義,增加很多溝通維護(hù)成本,且分散在各處很容易導(dǎo)致修改的時(shí)候遺漏不一致。務(wù)必清清除。方法也比較簡(jiǎn)單:定義含義明顯的枚舉或常量,代表這個(gè)魔數(shù)在代碼中發(fā)言。
“寫死的配置”不會(huì)影響業(yè)務(wù)功能, 不過(guò)在環(huán)境變更或系統(tǒng)調(diào)優(yōu)的時(shí)候,就顯得很不方便了。 方法: 盡量將配置抽離出來(lái)做成配置項(xiàng)放到配置文件里。
“臨時(shí)加的邏輯和文案”也是一種破壞系統(tǒng)可維護(hù)性的做法。方法: 抽離出來(lái)放在單獨(dú)的函數(shù)或方法里,并特別加以注釋。
重復(fù)代碼
重復(fù)代碼在當(dāng)前可能不會(huì)造成 BUG,但上線后,需要維護(hù)多處的事實(shí)一致性;時(shí)間一長(zhǎng),后續(xù)修改的時(shí)候就特別容易遺漏或處理不一致導(dǎo)致 BUG;重復(fù)代碼是公認(rèn)的“代碼壞味”,必當(dāng)盡力清除。方法: 抽離通用的部分,定制差異。重復(fù)代碼還有一種情況出現(xiàn),即創(chuàng)造新函數(shù)時(shí),先看看是否既有方法已經(jīng)實(shí)現(xiàn)過(guò)。
通用邏輯與定制業(yè)務(wù)邏輯耦合
這大概是每個(gè)媛猿們?cè)陂_(kāi)發(fā)生涯中遇到的最惡心的事情之一了。通用邏輯與具體的各種業(yè)務(wù)邏輯混雜交錯(cuò),想插根針都難。遇到這種情況,只能先祈福,然后抽離一個(gè)新的函數(shù),嚴(yán)格判斷相應(yīng)條件滿足后去調(diào)用它。
如果是新創(chuàng)建邏輯,可以使用函數(shù)式編程或基于接口的編程,將通用處理流程抽離出來(lái),而將具體業(yè)務(wù)邏輯以回調(diào)函數(shù)的形式傳入處理。
不要讓不同的業(yè)務(wù)共用相同的函數(shù),然后在函數(shù)里一堆 if-else plus switch , 而是每個(gè)業(yè)務(wù)都有各自的函數(shù), 并可復(fù)用相同的通用邏輯和流程處理; 或者各個(gè)業(yè)務(wù)可以覆寫同樣命名的函數(shù)。
復(fù)用,而非混雜。
直接在原方法里加邏輯
有業(yè)務(wù)改動(dòng)時(shí),猿媛們圖方便傾向于直接在原方法里加判斷和邏輯。這樣做是很不好的習(xí)慣。一方面,增加了原方法的長(zhǎng)度,破壞了其可維護(hù)性;另一方面,有可能對(duì)原方法的既有邏輯造成破壞。 可靠的方式是: 新增一個(gè)函數(shù),然后在原方法中調(diào)用并說(shuō)明原因。
多業(yè)務(wù)耦合
在業(yè)務(wù)邊界未仔細(xì)劃分清晰的情況下出現(xiàn),一個(gè)業(yè)務(wù)過(guò)多深入和摻雜另一個(gè)非相關(guān)業(yè)務(wù)的實(shí)現(xiàn)細(xì)節(jié)。在項(xiàng)目和系統(tǒng)設(shè)計(jì)之初,特別要注意先劃分業(yè)務(wù)邊界,定義好接口設(shè)計(jì)和服務(wù)依賴關(guān)系,再著手開(kāi)發(fā);否則,延遲到后期做這些工作,很可能會(huì)導(dǎo)致重復(fù)的工作量,含糊復(fù)雜的交互、增加后期系統(tǒng)維護(hù)和問(wèn)題排查的許多成本。磨刀不誤砍柴工。劃分清晰的業(yè)務(wù)、服務(wù)、接口邊界就屬于磨刀的功夫。
代碼層次不合理
代碼改動(dòng)邏輯是正確的,然而代碼的放置位置不符合當(dāng)前架構(gòu)設(shè)計(jì)約定,導(dǎo)致后續(xù)維護(hù)成本增加。
代碼層次不合理可能導(dǎo)致重復(fù)代碼。比如獲取操作人和操作記錄,如果寫在類 XController 里, 那么類 YController 就面臨尷尬局面: 如果寫在 YController , 就會(huì)導(dǎo)致重復(fù)代碼; 如果跨層去調(diào)用 XController 方法,又是非常不推薦的做法。因此, 獲取操作人和操作記錄,最好寫在 Service 層, Controller 層只負(fù)責(zé)參數(shù)傳入、檢測(cè)和結(jié)果轉(zhuǎn)譯、返回。
不用多余的代碼
工程中常常會(huì)有一些不用的代碼。或者是一些暫時(shí)未用到的Util工具或庫(kù)函數(shù),或者是由于業(yè)務(wù)變更導(dǎo)致已經(jīng)廢棄不用的代碼,或者是由于一時(shí)寫出后來(lái)又重寫的代碼。盡量清除掉不用多余的代碼,對(duì)系統(tǒng)可維護(hù)性是一種很好的改善,同時(shí)也有利于CodeReview。
使用全局變量
使用全局變量并沒(méi)有“錯(cuò)”,錯(cuò)的是,一旦出現(xiàn)問(wèn)題,排查和調(diào)試問(wèn)題起來(lái),真的會(huì)讓人“一夜之間白了頭”,耗費(fèi)數(shù)個(gè)小時(shí)是輕微懲罰。此外,全局變量還能“順手牽羊”地破壞函數(shù)的通用性,導(dǎo)致可維護(hù)性變差。務(wù)必消除全局變量的使用。當(dāng)然,全局常量是可以的。
缺乏必要的注釋
對(duì)重要和關(guān)鍵點(diǎn)的代碼缺乏必要的注釋,使用到的重要算法缺乏必要的引用出處,對(duì)特別的處理缺乏必要的說(shuō)明。
原則上, 每個(gè)方法至少要用一個(gè)簡(jiǎn)短的單行注釋, 適宜地描述了方法的用途、業(yè)務(wù)邏輯、作者及日期。對(duì)于特殊甚至奇葩的需求的特別實(shí)現(xiàn),要加一些注釋。 這樣后續(xù)維護(hù)時(shí)有個(gè)基礎(chǔ)。
更難發(fā)現(xiàn)的錯(cuò)誤
更難發(fā)現(xiàn)的錯(cuò)誤是指“復(fù)雜并發(fā)場(chǎng)景下的有一定技術(shù)難度的、需要豐富開(kāi)發(fā)與設(shè)計(jì)經(jīng)驗(yàn)才能看出來(lái)的錯(cuò)誤”。
并發(fā)
并發(fā)的問(wèn)題更難檢測(cè)、復(fù)現(xiàn)和調(diào)試。常見(jiàn)的問(wèn)題有:a. 在可能由多線程并發(fā)訪問(wèn)的對(duì)象中含有共享變量卻沒(méi)有同步保護(hù);b. 在代碼中手動(dòng)創(chuàng)建缺乏控制的線程或線程池;c. 并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí)沒(méi)有做任何同步措施;d. 多個(gè)線程對(duì)同一對(duì)象的互斥操作沒(méi)有同步保護(hù)。
對(duì)于 a, 在大部分Java應(yīng)用中,通常由Spring框架來(lái)控制和創(chuàng)建請(qǐng)求和服務(wù)實(shí)例,因此,保證“Controller, Service 類中的實(shí)例變量只允許 Service, DAO 的單例,不允許業(yè)務(wù)變量實(shí)例”基本確保沒(méi)有并發(fā)不正確更新的問(wèn)題;不過(guò),包含緩存策略的對(duì)象要特別注意多線程并發(fā)訪問(wèn)的問(wèn)題,出于性能考量, 盡量只對(duì)共享實(shí)例部分加鎖。
對(duì)于 b, 禁止在應(yīng)用中手動(dòng)創(chuàng)建線程或線程池,失控的線程池很容易導(dǎo)致應(yīng)用崩潰(有線上應(yīng)用崩潰的教訓(xùn))。
對(duì)于 c, 并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),要特別注意時(shí)序和狀態(tài)同步。如果時(shí)序控制不對(duì),會(huì)導(dǎo)致?tīng)顟B(tài)同步和更新出錯(cuò)。
對(duì)于 d, 對(duì)同一對(duì)象的互斥操作需要加分布式鎖同步。
使用線程池、并發(fā)庫(kù)、并發(fā)類、同步工具而不是線程對(duì)象、并發(fā)原語(yǔ)。在復(fù)雜并發(fā)場(chǎng)景下,還需注意多個(gè)同步對(duì)象上的鎖是否按合適的順序獲得和釋放以避免死鎖,相應(yīng)的錯(cuò)誤處理代碼是否合理。
資源泄露
- 打開(kāi)文件卻沒(méi)有關(guān)閉;
- 連接池的連接未回收;
- 重復(fù)創(chuàng)建的腳本引用沒(méi)有置空,無(wú)法被回收;
- 已使用完的集合元素始終被引用,無(wú)法被回收;
事務(wù)
事務(wù)方面常出現(xiàn)的問(wèn)題是:多個(gè)緊密關(guān)聯(lián)的業(yè)務(wù)操作和 SQL 語(yǔ)句沒(méi)有事務(wù)保證。 在資金業(yè)務(wù)操作或數(shù)據(jù)強(qiáng)一致性要求的業(yè)務(wù)操作中,要注意使用事務(wù),保證數(shù)據(jù)更新的一致性和完整性。
SQL問(wèn)題
SQL的正確性通常可以通過(guò) DAO 測(cè)試來(lái)保證。 SQL問(wèn)題主要是指潛在的性能問(wèn)題和安全問(wèn)題。
要避免SQL性能問(wèn)題, 在表設(shè)計(jì)的時(shí)候就要做好索引工作。在表數(shù)據(jù)量非常大的情況下,SQL語(yǔ)句編寫要非常小心。查詢SQL需要添加必要索引,添加合適的查詢條件和查詢順序,加快查詢效率, 避免慢查; 盡量避免使用 Join, 子查詢;避免SQL注入。
尤其避免在 update 語(yǔ)句中使用 where-if ! 很容易導(dǎo)致全表更新和嚴(yán)重的數(shù)據(jù)丟失,造成嚴(yán)重的線上故障 !!!
SQL優(yōu)秀書籍推薦:?SQL語(yǔ)言藝術(shù)
安全問(wèn)題
安全問(wèn)題一向是互聯(lián)網(wǎng)產(chǎn)品研發(fā)中極容易被忽視、而在爆發(fā)后又極引發(fā)熱議的議題。安全和隱私是用戶的心理紅線之一。應(yīng)用、數(shù)據(jù)、資金的安全性應(yīng)當(dāng)僅次于產(chǎn)品功能的準(zhǔn)確性和使用體驗(yàn)。
比如:緩沖區(qū)溢出; 惡意代碼注入;權(quán)限賦予不當(dāng); 應(yīng)用目錄泄露等。
安全問(wèn)題的CodeReview可參見(jiàn)檢查點(diǎn)清單:信息安全?。主要是如下措施: a. 嚴(yán)格檢查和屏蔽非法輸入; b. 對(duì)含敏感信息的請(qǐng)求加密通信; c. 業(yè)務(wù)處理后消除任何敏感私密信息的任何痕跡; d. 結(jié)果返回前在反序列化中清除敏感私密信息; e. 敏感私密信息在數(shù)據(jù)存儲(chǔ)設(shè)備中應(yīng)當(dāng)加密存儲(chǔ); f. 應(yīng)用有嚴(yán)格的角色、權(quán)限、操作、數(shù)據(jù)訪問(wèn)分級(jí)和控制; g. 切忌暴露服務(wù)器的重要的安全性信息,防止服務(wù)器被攻擊影響正常服務(wù)運(yùn)行。
設(shè)計(jì)問(wèn)題
設(shè)計(jì)問(wèn)題通常體現(xiàn)在: a. 是否有潛在的性能問(wèn)題; b. 是否有安全問(wèn)題; c. 業(yè)務(wù)變化時(shí)是否容易擴(kuò)展; d. 是否有遺漏的點(diǎn); e. 持續(xù)高負(fù)荷壓力下是否會(huì)崩潰。
較輕微的問(wèn)題
較輕微問(wèn)題是指“沒(méi)有技術(shù)難度、通過(guò)良好習(xí)慣即可避免的問(wèn)題”。
較輕微問(wèn)題一般不會(huì)造成負(fù)面影響的BUG或故障,不過(guò)建立一些好的習(xí)慣,主動(dòng)使用代碼檢測(cè)工具,消除這些較輕微錯(cuò)誤,也是一種修行。
命名不貼切
命名不貼切不會(huì)影響功能實(shí)現(xiàn),卻會(huì)誤導(dǎo)理解或增加理解難度。
方法:先查查字典,找個(gè)通俗易懂而且比較貼近的名字。可以參考 jdk 的命名、通用詞匯和行業(yè)詞匯; 作用域小的采用短命名,作用域大的采用長(zhǎng)命名。取名字是一種重要技能,—— 多少父母為此愁灰了頭!
聲明時(shí)未初始化
聲明時(shí)未初始化通常情況下都不會(huì)是問(wèn)題,因?yàn)楹竺鏁?huì)進(jìn)行賦值。不過(guò),如果賦值的過(guò)程中出現(xiàn)異常,那么可能會(huì)返回空值,從而導(dǎo)致空值異常。通常,變量聲明時(shí)賦予默認(rèn)初始值是個(gè)好習(xí)慣。
風(fēng)格與整體有不一致
工程通常求穩(wěn),一致性能更好地維護(hù)。在工程項(xiàng)目中,最好能夠遵循工程約定的風(fēng)格,在個(gè)人項(xiàng)目中可以凸顯個(gè)性風(fēng)格。Java編程一般要遵循《Java編程規(guī)范》,有追求的程序猿媛還會(huì)追求更高層次的,比如《Google Java 規(guī)范》等。
類型轉(zhuǎn)換錯(cuò)誤
編程語(yǔ)言的類型系統(tǒng)是非常重要的。如何在不同類型之間可靠地互轉(zhuǎn),尤其是在父子類型之間相互賦值,也是一個(gè)微技能。濫用類型轉(zhuǎn)換,也會(huì)導(dǎo)致BUG 。
Java 中容易出現(xiàn)的錯(cuò)誤是:a. 字符串轉(zhuǎn)數(shù)值,字符串含有非數(shù)字部分;b. JSON字符串轉(zhuǎn)對(duì)象,某個(gè)字段含有不兼容的值類型導(dǎo)致解析出錯(cuò);c. 子類型轉(zhuǎn)不兼容的父類型,滋生運(yùn)行時(shí)異常 ClassCastException;d. 相同特質(zhì)的類型不兼容。比如 Long 與 Integer 都是數(shù)值型,卻不能互轉(zhuǎn)。
類型轉(zhuǎn)換中最容易出BUG的地方是非布爾類型取反。受C語(yǔ)言的影響,很多高級(jí)語(yǔ)言支持各種數(shù)據(jù)類型轉(zhuǎn)布爾類型,比如 PHP 字符串、數(shù)組、數(shù)字等都可以轉(zhuǎn)布爾類型,相應(yīng)的就喜歡寫 if (!notBoolVar) 這種表達(dá)式, 容易隱藏看不出的BUG甚至錯(cuò)誤。
否定式風(fēng)格
變量含義、表達(dá)式語(yǔ)句傾向于使用否定式風(fēng)格,可能不知不覺(jué)耗費(fèi)大量腦細(xì)胞,因?yàn)槊看卫斫獾臅r(shí)候都要繞個(gè)彎子。 比如 isNoExpress 是否無(wú)需物流, 就有點(diǎn)繞。 為什么呢? 無(wú)需物流是針對(duì)快遞發(fā)貨的, 如果快遞發(fā)貨占發(fā)貨的90%, 無(wú)需物流只占10%,那么, isNoExpress = false 幾乎總為真。 涉及到判斷的時(shí)候,可能不得不寫 if (!isNoExpress) , 雙重否定足夠弄暈?zāi)恪?br />
容器遍歷的結(jié)構(gòu)變更
絕大多數(shù)語(yǔ)言都承襲了 C 語(yǔ)言的 for(int i=0;i<N;i++) 循環(huán)形式。不過(guò),現(xiàn)代編程語(yǔ)言通常都提供了迭代器遍歷、或 foreach 遍歷。 foreach 遍歷通常基于迭代器遍歷實(shí)現(xiàn)。 只要對(duì)容器結(jié)構(gòu)不做變更,推薦使用 foreach ; 若要遍歷的同時(shí)做修改或更新,推薦迭代器模式。 遍歷容器的時(shí)候同時(shí)做刪除元素操作,要特別留意,很可能導(dǎo)致越界錯(cuò)誤。更可靠的方式時(shí),直接生成新的容器,如果不涉及空間效率的話。
API參數(shù)傳遞錯(cuò)誤
如果API參數(shù)有多個(gè),而且相鄰參數(shù)的類型相同,那么要特別留意是否參數(shù)順序是正確的,而不會(huì)張冠李戴。
當(dāng)然,在設(shè)計(jì)API參數(shù)的時(shí)候,就可以仔細(xì)用更精準(zhǔn)類型進(jìn)行區(qū)分,并將相同類型的參數(shù)錯(cuò)開(kāi)。比如 calc(int accountNo, int pay, int timestamp) , 就容易傳錯(cuò),比較可靠的是 calc(int accountNo, Currency pay, Timestamp now) ,這樣是不可能將參數(shù)傳遞錯(cuò)誤的。
單行調(diào)用括號(hào)過(guò)多
為了簡(jiǎn)便,常常會(huì)寫出 wapper(calc(now, String.format("%s\n", new BufferedFileReader(filename, "UTF-8").readLines() ))) 的語(yǔ)句 , 嗯,你得好好瞧瞧和算算右邊的括號(hào)數(shù)量是否正確了。更糟糕的時(shí)候,結(jié)合API參數(shù)傳遞錯(cuò)誤,IDE 可能沒(méi)有報(bào)錯(cuò), 而你很可能沒(méi)有意識(shí)到自己的參數(shù)傳遞錯(cuò)誤了。 可靠的方式是, 拆出一部分變量,并將調(diào)用之間的括號(hào)用空格隔開(kāi),顯示出層次感。
String fileContent = new BufferedFileReader(filename, "UTF-8").readLines(); wapper( calc( now, String.format("%s\n", fileContent) ) )?
修改方法簽名
對(duì)某個(gè)方法有業(yè)務(wù)改動(dòng)時(shí),程序猿媛們傾向直接修改原方法的簽名。這時(shí),要特別注意:a. 不要修改原方法的參數(shù)順序; b. 在最后面增加可選參數(shù)。 從另一個(gè)角度來(lái)看,復(fù)雜的業(yè)務(wù)方法應(yīng)當(dāng)分兩層: 最外層負(fù)責(zé)調(diào)度,方法參數(shù)具有包容性,里面包含的字段比較多 ; 內(nèi)層方法負(fù)責(zé)特定業(yè)務(wù)邏輯的實(shí)現(xiàn),方法參數(shù)少而精。
修改原方法簽名本身就是容易產(chǎn)生問(wèn)題的習(xí)慣, 篡改原方法的參數(shù)順序更是大忌。 最好的方法是新建一個(gè)方法去復(fù)用原方法, 然后調(diào)用新的方法。代碼變更始終銘記“開(kāi)閉”原則。
打印日志太多
打印過(guò)多的日志并不好。一方面遮掩真正需要的信息,導(dǎo)致排查耗費(fèi)時(shí)間, 另一方面造成服務(wù)器空間浪費(fèi)、影響性能。生產(chǎn)環(huán)境日志一般只開(kāi)放 INFO及以上級(jí)別的日志; Debug 日志只在調(diào)試或排錯(cuò)的時(shí)候使用,生產(chǎn)環(huán)境可以禁止debug日志。
多級(jí)數(shù)據(jù)結(jié)構(gòu)
使用多級(jí)數(shù)據(jù)結(jié)構(gòu)時(shí),要確定父級(jí)數(shù)據(jù)一定有值,或者進(jìn)行檢測(cè)。比如 $order['baole']['ump']['money'],必須確保 $order['baole'], $order['baole']['money'] 一定有值或做非空檢測(cè)。
作用域過(guò)大
由于C語(yǔ)言的影響,猿媛們會(huì)在開(kāi)頭就定義好一些變量或要返回的對(duì)象,在很靠后的地方才使用到。不必要的過(guò)大的作用域?qū)ψ兞亢蛯?duì)象的變化產(chǎn)生不可測(cè)的影響,并增大理解的成本。可靠的方法是,僅當(dāng)在使用時(shí)才定義,并盡快返回結(jié)果。
另一種情況是,暴露的訪問(wèn)域過(guò)大,比如 public 字段。 盡可能地縮小可訪問(wèn)的范圍,可以增大變更和重構(gòu)的空間; 減少可變性,則可以自然地獲得并發(fā)安全性,降低CodeReview的理解成本。
比如,不可變的類和字段定義成 final , 最小化包,類,接口,方法和域的可訪問(wèn)性,默認(rèn)為 private , 若需要繼承,可定義為 protected , 僅當(dāng)需要作為 API 服務(wù)暴露出去時(shí),使用 public.
分支與循環(huán)
條件與循環(huán)偶爾也會(huì)導(dǎo)致錯(cuò)誤, 不過(guò)通常錯(cuò)誤可以在發(fā)布前解決掉。
對(duì)于 if-else 嵌套條件, 需要仔細(xì)檢查是否符合業(yè)務(wù)邏輯; 如果嵌套太深,是否可以使用另一種方式“解結(jié)” ; 對(duì)于 switch 語(yǔ)句, 大多數(shù)語(yǔ)言的 case 有 fall through 問(wèn)題, 要注意加上 break ; 最好加上 default 的處理。
對(duì)于 for 循環(huán), 編寫合理的結(jié)束條件避免死循環(huán); 對(duì)于循環(huán)變量的控制, 避免出現(xiàn) -1或 +1 錯(cuò)誤, 消除越界錯(cuò)誤; for 循環(huán)也要特別注意對(duì)空值和空容器的處理,避免拋出空值異常。可以通過(guò)單測(cè)來(lái)確保 for 循環(huán)的準(zhǔn)確性。
殘留的無(wú)用代碼
殘留的無(wú)用代碼,會(huì)成為系統(tǒng)的垃圾,增加系統(tǒng)的理解和維護(hù)成本。需要及時(shí)清理掉。
代碼與文檔不一致
文檔是理解代碼的第一扇窗口。優(yōu)秀的文檔,可以極大地降低理解代碼的成本。但是大多數(shù)開(kāi)發(fā)者還并不習(xí)慣編寫友好的文檔。常常出現(xiàn)無(wú)文檔、失效文檔、誤導(dǎo)性文檔等,影響人們的理解和判斷。
使用冷僻用法或奇淫巧技
使用冷僻用法或奇淫巧技會(huì)增大系統(tǒng)的理解成本,徒然消耗人的腦細(xì)胞。思路可借鑒,但不宜用于生產(chǎn)環(huán)境中。樸實(shí)最宜。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/9435309.html
總結(jié)
- 上一篇: 微服务接口限流的设计与思考(附GitHu
- 下一篇: 干货|一文读懂中国7大支付体系(附27页