中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

CodeReview常見(jiàn)代碼問(wèn)題

2018-07-20    來(lái)源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

路線圖

常見(jiàn)代碼問(wèn)題

常見(jiàn)的潛在代碼問(wèn)題是當(dāng)前直接會(huì)導(dǎo)致BUG、故障或者產(chǎn)品功能不能正常工作的類(lèi)別。

空值

空值恐怕是最容易出現(xiàn)的地方之一。 常見(jiàn)錯(cuò)誤有: a. 值為NULL導(dǎo)致空指針異常; b. 參數(shù)字符串含有前導(dǎo)或后綴空格沒(méi)有Trim導(dǎo)致查詢(xún)?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è)總是存在的前提下, 可以?xún)?yōu)化空值檢測(cè)的方法和存在形式。 比如集中于一個(gè)類(lèi) NullChecker 中管理,并與系統(tǒng)的整體錯(cuò)誤處理設(shè)計(jì)保持一致。集中管理和處理一致性原則可以作為系統(tǒng)設(shè)計(jì)的一個(gè)準(zhǔn)則。 這樣主流程中只要增加一行調(diào)用即可, 既可以天網(wǎng)恢恢疏而不漏地檢測(cè)對(duì)象為空, 也不會(huì)讓代碼顯得難看。
class NullChecker {
       public static void checkNull(Object obj, Error error) {
               if (obj == null)  { throw new BizException(error); }
       }
}
  • 在參數(shù)入口處統(tǒng)一做 trim。 如果在業(yè)務(wù)邏輯里做 trim , 就會(huì)導(dǎo)致有的業(yè)務(wù)邏輯做了 trim , 有的沒(méi)做, 體現(xiàn)在產(chǎn)品上就會(huì)有令用戶(hù)困惑的事情發(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ì)詢(xún)問(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)銷(xiāo)大的)對(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è)模塊依賴(lài)的公共函數(shù)的修改,容易造成影響范圍超過(guò)當(dāng)前業(yè)務(wù)改動(dòng),無(wú)意識(shí)地破壞依賴(lài)于該公共函數(shù)的其他業(yè)務(wù)。要特別慎重?煽康姆绞绞牵合炔榭丛摴埠瘮(shù)的調(diào)用, 如果只有自己的業(yè)務(wù)用,可適當(dāng)大膽一些; 如果有多個(gè)地方依賴(lài),抽離一個(gè)新的函數(shù),抽離原函數(shù)里的可復(fù)用部分,然后基于可復(fù)用部分構(gòu)建新的函數(shù)。修改原則遵循“開(kāi)閉”原則,才能盡可能使改動(dòng)影響降低到最小化。

基類(lèi)及實(shí)例字段和方法也屬于公共函數(shù)的范疇。 盡量不要修改基類(lèi)的東西。

單測(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)有拋出異;蛘卟皇侵付ó惓#瑒t應(yīng)該 AssertFailed 而不是通過(guò)。

對(duì)于代碼變更和BUG修復(fù),如果當(dāng)時(shí)由于時(shí)間緊而沒(méi)有寫(xiě),后續(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)合查詢(xún)。

與原有業(yè)務(wù)不兼容, 一般出現(xiàn)在:

  1. 一對(duì)一與一對(duì)多的變化。 比如原來(lái)的關(guān)系是一個(gè)訂單對(duì)應(yīng)一個(gè)物流信息, 后來(lái)變化為一個(gè)訂單可能對(duì)應(yīng)多個(gè)物流信息; 原來(lái)的邏輯是一個(gè)訂單顯示多個(gè)物流信息可以更改,后來(lái)要求一個(gè)訂單只展示最近一次的物流信息可以修改。
  2. 多個(gè)業(yè)務(wù)組合。 業(yè)務(wù) A 與業(yè)務(wù) B 原來(lái)是分開(kāi)發(fā)展的, 后來(lái)開(kāi)展一種活動(dòng),將業(yè)務(wù)A與業(yè)務(wù)B進(jìn)行一種組合營(yíng)銷(xiāo)。 此時(shí),多半會(huì)出現(xiàn)很多 if-else 語(yǔ)句。

業(yè)務(wù)邏輯的兼容問(wèn)題一般體現(xiàn)在系統(tǒng)的復(fù)用性和可擴(kuò)展機(jī)制上。良好的系統(tǒng)可復(fù)用性和可擴(kuò)展性可以更容易地做到業(yè)務(wù)邏輯兼容。 主要有如下幾種級(jí)別:

  1. 自動(dòng)兼容。 增加一種類(lèi)型, 只是 biz_type 的值多了一種, 系統(tǒng)自動(dòng)將已有功能適配給新的 biz_type;
  2. 一點(diǎn)改動(dòng)。增加一個(gè)分支語(yǔ)句, 對(duì) biz_type 的某個(gè)特性進(jìn)行擴(kuò)展;
  3. 一些改動(dòng)。 需要見(jiàn)縫插針地增加一個(gè)單獨(dú)的分支判斷和邏輯處理模塊, 對(duì)整體可擴(kuò)展性沒(méi)有影響, 但會(huì)造成局部的復(fù)雜化;
  4. 一部分功能改動(dòng)。 只需要對(duì)其中一個(gè)功能模塊做個(gè)擴(kuò)展;
  5. 多處改動(dòng)。 需要對(duì)多個(gè)功能模塊做相應(yīng)的改造,不過(guò)更多是新增而不是修改;
  6. 難以改動(dòng)。 需要深入到功能模塊內(nèi)部做艱難的修改, 并要保證原有功能不受影響。

如何應(yīng)對(duì)呢?

  1. 針對(duì)關(guān)聯(lián)關(guān)系, 在項(xiàng)目之初, 可以詢(xún)問(wèn)清楚: 將來(lái)在產(chǎn)品上是否有可擴(kuò)展的變化? 及早預(yù)留空間, 或者確定產(chǎn)品上的對(duì)策; 在代碼實(shí)現(xiàn)上, 兼顧考慮一對(duì)一到一對(duì)多,或一對(duì)多到一對(duì)一的關(guān)聯(lián)變化。比如使用列表來(lái)表達(dá)單個(gè)信息, 使用索引從列表中獲取單個(gè)信息。
  2. 針對(duì)業(yè)務(wù)組合, 明確各業(yè)務(wù)的核心部分, 抽離出業(yè)務(wù)的可復(fù)用的部分,形成 API ; 考慮組合模式和裝飾器模式來(lái)進(jìn)行擴(kuò)展。

核心不變, 外圍定制化。

缺乏必要日志

對(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ī)范使用?梢允褂霉参臋n維護(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ù)的值類(lèi)型, 可以根據(jù)不同值類(lèi)型做基礎(chǔ)的檢測(cè); (2) 參數(shù)的業(yè)務(wù)類(lèi)型, 有基礎(chǔ)非業(yè)務(wù)參數(shù), 基礎(chǔ)業(yè)務(wù)參數(shù)和具體業(yè)務(wù)參數(shù)。 不同的參數(shù)業(yè)務(wù)類(lèi)型有不同的處理。 將參數(shù)值類(lèi)型與參數(shù)業(yè)務(wù)類(lèi)型結(jié)合起來(lái), 結(jié)合一致性的異常捕獲處理, 就可以實(shí)現(xiàn)一個(gè)可復(fù)用的參數(shù)檢測(cè)框架。參數(shù)檢測(cè)既可以采用普通的分支語(yǔ)句,也可以采用注解方式。采用注解方式更可讀,不過(guò)單測(cè)編寫(xiě)更具技巧。

引用錯(cuò)誤

對(duì)于動(dòng)態(tài)語(yǔ)言, 由于缺乏強(qiáng)大的靜態(tài)代碼檢測(cè),修改了類(lèi)引用的地方尤其要注意,很可能導(dǎo)致依賴(lài)的其他業(yè)務(wù)出錯(cuò); 尤其是修改重名引用時(shí)。有線上故障教訓(xùn)。PHP工程中含有兩個(gè) Format 類(lèi), 一個(gè)基礎(chǔ)的一個(gè)業(yè)務(wù)相關(guān)的, 被改動(dòng)的類(lèi)文件里開(kāi)始沒(méi)有指明引用,默認(rèn)采用了基礎(chǔ) Format 類(lèi)的實(shí)現(xiàn), 然后提交者在改動(dòng)文件頭增加了對(duì)業(yè)務(wù) Format 的引用, 導(dǎo)致依賴(lài)于基礎(chǔ)Format類(lèi)的其他業(yè)務(wù)不能正常工作。避免引用錯(cuò)誤的方法: 當(dāng)要在文件里增加新的類(lèi)引用時(shí), 先在文件里搜索是否有重名類(lèi)的引用。如果有, 就要格外小心了。

細(xì)節(jié)錯(cuò)誤

比如數(shù)組越界、JSON解析出錯(cuò)、函數(shù)參數(shù)傳遞出錯(cuò)、API 版本不對(duì)、使用網(wǎng)上拷貝的未經(jīng)測(cè)試的代碼、不成熟的算法、傳值與傳引用、相等性比較等。

對(duì)于數(shù)組越界錯(cuò)誤, 通常要對(duì)空數(shù)組、針對(duì)數(shù)組大小的邊界值+1和-1寫(xiě)單測(cè)來(lái)避免; 使用網(wǎng)上拷貝的代碼,誠(chéng)然可節(jié)省時(shí)間,也一定要加工一下并用單測(cè)覆蓋; 傳值和傳引用可通過(guò)單測(cè)來(lái)避免錯(cuò)誤; 對(duì)象的相等性比較切忌使用等號(hào)=。

多重條件

類(lèi)似 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)類(lèi)型語(yǔ)言與靜態(tài)類(lèi)型語(yǔ)言交互時(shí),會(huì)有一些問(wèn)題產(chǎn)生。比如PHP的對(duì)象通常是一個(gè)Map, 如果是空對(duì)象就會(huì)寫(xiě)成 [], 然而 [] 會(huì)被 Java 解析成列表。這樣, 如果數(shù)據(jù)庫(kù)的值是通過(guò) PHP 寫(xiě)入,那么這個(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ǔ)言交互的邊界處,要特別注意這些類(lèi)型轉(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} ?缦到y(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ù)成本大幅增加”的類(lèi)別。

硬編碼

硬編碼主要有三種情況: a. “魔數(shù)”; b. 寫(xiě)死的配置; c. 臨時(shí)加的邏輯和文案。

“魔數(shù)”與重復(fù)代碼類(lèi)似,當(dāng)前或許不會(huì)引發(fā)問(wèn)題,時(shí)間一長(zhǎng),為了弄清楚其代表的含義,增加很多溝通維護(hù)成本,且分散在各處很容易導(dǎo)致修改的時(shí)候遺漏不一致。務(wù)必清清除。方法也比較簡(jiǎn)單:定義含義明顯的枚舉或常量,代表這個(gè)魔數(shù)在代碼中發(fā)言。

“寫(xiě)死的配置”不會(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ā)生涯中遇到的最?lèi)盒牡氖虑橹涣恕Mㄓ眠壿嬇c具體的各種業(yè)務(wù)邏輯混雜交錯(cuò),想插根針都難。遇到這種情況,只能先祈福,然后抽離一個(gè)新的函數(shù),嚴(yán)格判斷相應(yīng)條件滿(mǎn)足后去調(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ù)可以覆寫(xiě)同樣命名的函數(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ù)依賴(lài)關(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ù)代碼。比如獲取操作人和操作記錄,如果寫(xiě)在類(lèi) XController 里, 那么類(lèi) YController 就面臨尷尬局面: 如果寫(xiě)在 YController , 就會(huì)導(dǎo)致重復(fù)代碼; 如果跨層去調(diào)用 XController 方法,又是非常不推薦的做法。因此, 獲取操作人和操作記錄,最好寫(xiě)在 Service 層, Controller 層只負(fù)責(zé)參數(shù)傳入、檢測(cè)和結(jié)果轉(zhuǎn)譯、返回。

不用多余的代碼

工程中常常會(huì)有一些不用的代碼;蛘呤且恍⿻簳r(shí)未用到的Util工具或庫(kù)函數(shù),或者是由于業(yè)務(wù)變更導(dǎo)致已經(jīng)廢棄不用的代碼,或者是由于一時(shí)寫(xiě)出后來(lái)又重寫(xiě)的代碼。盡量清除掉不用多余的代碼,對(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)的代碼缺乏必要的注釋?zhuān)褂玫降闹匾惴ㄈ狈Ρ匾囊贸鎏帲瑢?duì)特別的處理缺乏必要的說(shuō)明。

原則上, 每個(gè)方法至少要用一個(gè)簡(jiǎn)短的單行注釋?zhuā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 類(lèi)中的實(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ā)類(lèi)、同步工具而不是線程對(duì)象、并發(fā)原語(yǔ)。在復(fù)雜并發(fā)場(chǎng)景下,還需注意多個(gè)同步對(duì)象上的鎖是否按合適的順序獲得和釋放以避免死鎖,相應(yīng)的錯(cuò)誤處理代碼是否合理。

事務(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ǔ)句編寫(xiě)要非常小心。查詢(xún)SQL需要添加必要索引,添加合適的查詢(xún)條件和查詢(xún)順序,加快查詢(xún)效率, 避免慢查; 盡量避免使用 Join, 子查詢(xún);避免SQL注入。

SQL優(yōu)秀書(shū)籍推薦:?SQL語(yǔ)言藝術(shù)

安全問(wèn)題

安全問(wèn)題一向是互聯(lián)網(wǎng)產(chǎn)品研發(fā)中極容易被忽視、而在爆發(fā)后又極引發(fā)熱議的議題。安全和隱私是用戶(hù)的心理紅線之一。應(yīng)用、數(shù)據(jù)、資金的安全性應(yīng)當(dāng)僅次于產(chǎn)品功能的準(zhǔn)確性和使用體驗(yàn)。

安全問(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)。

較輕微的問(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ī)范》等。

類(lèi)型轉(zhuǎn)換錯(cuò)誤

編程語(yǔ)言的類(lèi)型系統(tǒng)是非常重要的。如何在不同類(lèi)型之間可靠地互轉(zhuǎn),尤其是在父子類(lèi)型之間相互賦值,也是一個(gè)微技能。濫用類(lèi)型轉(zhuǎn)換,也會(huì)導(dǎo)致BUG 。

Java 中容易出現(xiàn)的錯(cuò)誤是:a. 字符串轉(zhuǎn)數(shù)值,字符串含有非數(shù)字部分;b. JSON字符串轉(zhuǎn)對(duì)象,某個(gè)字段含有不兼容的值類(lèi)型導(dǎo)致解析出錯(cuò);c. 子類(lèi)型轉(zhuǎn)不兼容的父類(lèi)型,滋生運(yùn)行時(shí)異常 ClassCastException;d. 相同特質(zhì)的類(lèi)型不兼容。比如 Long 與 Integer 都是數(shù)值型,卻不能互轉(zhuǎn)。

類(lèi)型轉(zhuǎn)換中最容易出BUG的地方是非布爾類(lèi)型取反。受C語(yǔ)言的影響,很多高級(jí)語(yǔ)言支持各種數(shù)據(jù)類(lèi)型轉(zhuǎn)布爾類(lèi)型,比如 PHP 字符串、數(shù)組、數(shù)字等都可以轉(zhuǎn)布爾類(lèi)型,相應(yīng)的就喜歡寫(xiě) 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í)候,可能不得不寫(xiě) if (!isNoExpress) , 雙重否定足夠弄暈?zāi)恪?/p>

容器遍歷的結(jié)構(gòu)變更

絕大多數(shù)語(yǔ)言都承襲了 C 語(yǔ)言的 for(int i=0;i<N;i++) 循環(huán)形式。不過(guò),現(xiàn)代編程語(yǔ)言通常都提供了迭代器遍歷、或 foreach 遍歷。 foreach 遍歷通;诘鞅闅v實(shí)現(xiàn)。 只要對(duì)容器結(jié)構(gòu)不做變更,推薦使用 foreach ; 若要遍歷的同時(shí)做修改或更新,推薦迭代器模式。 遍歷容器的時(shí)候同時(shí)做刪除元素操作,要特別留意,很可能導(dǎo)致越界錯(cuò)誤。更可靠的方式時(shí),直接生成新的容器,如果不涉及空間效率的話。

API參數(shù)傳遞錯(cuò)誤

如果API參數(shù)有多個(gè),而且相鄰參數(shù)的類(lèi)型相同,那么要特別留意是否參數(shù)順序是正確的,而不會(huì)張冠李戴。

當(dāng)然,在設(shè)計(jì)API參數(shù)的時(shí)候,就可以仔細(xì)用更精準(zhǔn)類(lèi)型進(jìn)行區(qū)分,并將相同類(lèi)型的參數(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ì)寫(xiě)出 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è)的影響,并增大理解的成本?煽康姆椒ㄊ牵瑑H當(dāng)在使用時(shí)才定義,并盡快返回結(jié)果。

另一種情況是,暴露的訪問(wèn)域過(guò)大,比如 public 字段。 盡可能地縮小可訪問(wèn)的范圍,可以增大變更和重構(gòu)的空間; 減少可變性,則可以自然地獲得并發(fā)安全性,降低CodeReview的理解成本。

比如,不可變的類(lèi)和字段定義成 final , 最小化包,類(lèi),接口,方法和域的可訪問(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), 編寫(xiě)合理的結(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)確性。

標(biāo)簽: Google 安全 大數(shù)據(jù) 代碼 服務(wù)器 服務(wù)器空間 互聯(lián)網(wǎng) 互聯(lián)網(wǎng)產(chǎn)品 權(quán)限 數(shù)據(jù)庫(kù) 搜索 通信 網(wǎng)絡(luò) 信息安全

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:細(xì)說(shuō) Java 中的字符和字符串( 二 )

下一篇:ActionView 1.5.0 發(fā)布,更好用的問(wèn)題需求跟蹤工具