專有名詞解釋:
1.POJO(plain ordinary java Object),專指有g(shù)et ,set等的簡(jiǎn)單類,如:DO/DTO/BO/VO
2.GAV(GroupId,ArtifactId,Version),maven坐標(biāo)
3.OOP(object Oriented programming):泛指類,對(duì)象的編程處理方式
4.NPE(NullPointerException)空指針異常
5.OOM(out of memory) 內(nèi)存溢出
6.一方庫(kù):本項(xiàng)目的內(nèi)部模塊
7.二方庫(kù):內(nèi)部程序模塊,其他組的
8.三方庫(kù):第三方開(kāi)源
命名風(fēng)格
- 代碼命名不能以下劃線或者美元符號(hào)開(kāi)頭或者結(jié)尾(程序員基本約定)
- 代碼命名不能以中文拼音或者中文拼音與英文混合方式(方便閱讀)
- 類名使用UpperCamCamelCase風(fēng)格,但DO、PO、DTO、VO、BO等除外(類名首字母大寫)
- 方法名、參數(shù)名、變量名統(tǒng)一使用lowerCamelCase,必須遵守駝峰命名(駝峰命名)
- 常量名全部大寫,單詞間用下劃線隔開(kāi)(常量全大寫)
- 抽象類必須以Abstract或者Base開(kāi)頭,異常類必須以Exception結(jié)尾,測(cè)試
類以測(cè)試的類的名稱開(kāi)頭Test結(jié)尾(方便看出類屬于什么) - 類型與中括號(hào)緊挨相連標(biāo)示數(shù)組
- POJO類中布爾類型變量不要加is前綴(Boolean不要is開(kāi)頭會(huì)導(dǎo)致部分解析錯(cuò)誤)
- 包名統(tǒng)一小寫,點(diǎn)分隔符有且有一個(gè)自然語(yǔ)義單詞
- 避免在父子類和不同代碼塊中采用相同變量名
- 避免不規(guī)范的縮寫命名
- 在對(duì)元素命名時(shí)用完整單詞組合表達(dá)其意
- 常量和變量命名時(shí),表示類型放在詞尾,如:idList、TERMINATED_TREAD_COUNT
- 接口、類、方法、模塊使用設(shè)計(jì)模式,命名時(shí)要體現(xiàn)具體模式
- 接口類中的方法和屬性不要加任何修飾符,并加上有效的javadoc。
- 接口和實(shí)現(xiàn)類的命名規(guī)則:
1、對(duì)于service和dao類,實(shí)現(xiàn)類必須用Impl結(jié)尾;
2、如果是形容能力的接口名稱,取對(duì)應(yīng)的形容詞為接口名 AbstractTranslator實(shí)現(xiàn) Translatable接口 - 枚舉類名加Enum后綴,枚舉成員名稱全大寫,單詞間用下劃線隔開(kāi)(枚舉是特殊的類,都是常量,所有大寫)
- 各層命名規(guī)范:(更加方便區(qū)分)
A) Service/DAO層命名規(guī)約
1.獲取單個(gè)對(duì)象的方法用get做前綴
2.獲取多個(gè)對(duì)象的方法用list做前綴,如:listObjects
3.獲取統(tǒng)計(jì)值的方法用count做前綴
4.插入方法用save/insert做前綴
5.刪除方法用delete/remove做前綴
6.修改方法用update做前綴
B)領(lǐng)域模型命名規(guī)范
1.數(shù)據(jù)對(duì)象:xxxDO, xxx為數(shù)據(jù)庫(kù)表名
2.數(shù)據(jù)傳輸對(duì)象:xxxDTO,xxx為業(yè)務(wù)模型相關(guān)名稱
3.展示對(duì)象:xxxVO,xxx一般為網(wǎng)頁(yè)名稱
4.POJO是對(duì)DO、DTO、VO、BO的統(tǒng)稱,禁止xxxPOJO
常量定義
- 代碼中禁止出現(xiàn)魔法值
- 在Long類型中賦值,數(shù)值后使用大寫L(小寫不好區(qū)分)
- 不要在一個(gè)常量類中維護(hù)所有常量,要根據(jù)功能分開(kāi)維護(hù)(更明確的命名)
- 常量的復(fù)用層次:
1.跨應(yīng)用:放在二方庫(kù)中,通常在constant目錄下
2.應(yīng)用內(nèi):放在一方庫(kù)中,通常在constant目錄下
3.子工程內(nèi):放在當(dāng)前子工程constant目錄下
4.包內(nèi)共享常量:當(dāng)前包下單獨(dú)的constant目錄下
5.類內(nèi)共享常量:直接在類內(nèi)部private static final定義 - 如果變量值只在固定的范圍內(nèi)變化,用enum類型定義
代碼格式
- 如果大括號(hào)代碼為空直接'{}',大括號(hào)內(nèi)有代碼則:左大括號(hào)左側(cè)不換行,右側(cè)換行;右大括號(hào)右側(cè)換行,左側(cè)如果不跟else等代碼換行,否則不換行
- 小括號(hào)和字符之間不能有空格,括號(hào)內(nèi)字符和運(yùn)算符之間有空格 如:if (a == b)
- if、for、while、do、switch與括號(hào)之間必須有空格
- 任何二目、三目運(yùn)算符前后必須有空格(=,&&,+-*/)
- 采用4個(gè)空格,禁止使用tab
- 注釋的雙斜線和內(nèi)容要有空格(注釋顯示更清楚)
- 強(qiáng)制類型轉(zhuǎn)換時(shí),右括號(hào)與強(qiáng)制轉(zhuǎn)換值之間不用空格
- 單行字符不超過(guò)120個(gè),超過(guò)要換行
- 方法在定義和傳參時(shí),必須要加空格
- IDE的text file encoding 設(shè)置為UTF-8;IDE中 文件的換行符使用Unix格式
- 單個(gè)方法盡量不超過(guò)80行
- 不同邏輯、不同語(yǔ)義、不同業(yè)務(wù)之間的代碼插入一個(gè)空行分隔符
OOP規(guī)約
- 不用一個(gè)類型的對(duì)象引用來(lái)訪問(wèn)靜態(tài)方法和靜態(tài)屬性,直接類名訪問(wèn)即可
- 所有覆寫方法,必須加@Override注解
- 相同業(yè)務(wù)含義,相同參數(shù)類型才能使用java可變參數(shù)
- 外部依賴或者二方庫(kù)依賴的接口,不能修改方法簽名。接口過(guò)時(shí)必須用@Deprecated 注解,并說(shuō)明新接口或者新服務(wù)是什么
- 不能使用過(guò)時(shí)的類或者方法
- Object的equals方法容易拋出空指針,應(yīng)使用常量或者確定值的對(duì)象來(lái)調(diào)用equals
- 所有整型包裝類之間的值比較都用equals 方法比較
- 浮點(diǎn)數(shù)之間的等值判斷,基本類型不能用==,包裝類不能用equals。
解決方案:(1) 指定一個(gè)誤差范圍,兩個(gè)浮點(diǎn)數(shù)的差值在此范圍之內(nèi),則認(rèn)為是相等的。
(2) 使用BigDecimal來(lái)定義值,再進(jìn)行浮點(diǎn)數(shù)的運(yùn)算操作。 - 定義DO類時(shí),屬性類型要數(shù)據(jù)庫(kù)字段類型相匹配
- 防止精度丟失,禁止使用BigDecimal(double)方式將double對(duì)象轉(zhuǎn)換成BigDecimal。建議使用BigDecimal的valueOf方法
- 基本類型和包裝類型的使用標(biāo)準(zhǔn)
1.所有POJO的屬性必須用包裝類型
2.RPC方法的參數(shù)和返回值必須使用包裝類型
3.所有局部變量使用基本變量 - 所有POJO 不要對(duì)其屬性設(shè)置默認(rèn)值
- 序列化類新增屬性時(shí)不要修改其serialVersionUID字段
- 構(gòu)造方法里禁止加任何業(yè)務(wù)處理邏輯,有要加在init()
- POJO類必須要寫toString方法
- 禁止在POJO類中對(duì)屬性xxx 同時(shí)存在isXxx()和getXxx()
- 使用索引訪問(wèn)用String的split方法得到數(shù)組時(shí),需要對(duì)最后一個(gè)分隔符有無(wú)內(nèi)容做檢查
- 一個(gè)類有多個(gè)構(gòu)造方法或者多個(gè)同名方法,要按照順序來(lái)。
- 類中的方法順序 :共有方法-> 私有方法 -> get/set
- setter方法中參數(shù)名稱和成員變量名稱一致,不要在getter和setter方法中加業(yè)務(wù)邏輯
- 循環(huán)體內(nèi)用StringBuilder的append方法進(jìn)行擴(kuò)展
- final可以修飾類,方法,變量。
- 慎用Object的clone方法
- 類成員與方法訪問(wèn)控制從嚴(yán)
1) 如果不允許外部直接通過(guò)new來(lái)創(chuàng)建對(duì)象,那么構(gòu)造方法必須是private。
2) 工具類不允許有public或default構(gòu)造方法。
3) 類非static成員變量并且與子類共享,必須是protected。
4) 類非static成員變量并且僅在本類使用,必須是private。
5) 類static成員變量如果僅在本類使用,必須是private。
6) 若是static成員變量,考慮是否為final。
7) 類成員方法只供類內(nèi)部調(diào)用,必須是private。
8) 類成員方法只對(duì)繼承類公開(kāi),那么限制為protected。
集合處理
- hashCode和equals 的處理遵循以下規(guī)則:
1)只要覆寫equals ,就必須要覆寫hashCode
2)因?yàn)镾et存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù)hashCode和equals進(jìn)行判斷,所以Set存儲(chǔ)的對(duì)象必須覆寫這兩個(gè)方法。
3)如果自定義對(duì)象作為Map的鍵,那么必須覆寫hashCode和equals。 - ArrayList的subList結(jié)果不能強(qiáng)轉(zhuǎn)ArrayList。
- 使用map的keySet()、values()、entrySet()方法返回對(duì)象后不可以對(duì)其進(jìn)行添加元素的操作
- Collections類返回的對(duì)象,如:emptyList()/singletonList()等都是immutablelist不可對(duì)其進(jìn)行添加或者刪除元素的操作
- 在subList場(chǎng)景中,高度注意對(duì)原集合元素的增加或刪除,均會(huì)導(dǎo)致子列表的遍歷、增加、刪除產(chǎn)生ConcurrentModificationException 異常
- 使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的toArray(T[] array),傳入的是類型完全一致、長(zhǎng)度為0的空數(shù)組
- 在使用Collection接口任何實(shí)現(xiàn)類的addAll()方法時(shí),一定要對(duì)輸入的集合做NEP判斷
- 使用工具類Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方法,它的add/remove/clear方法會(huì)拋出UnsupportedOperationException異常
說(shuō)明:asList的返回對(duì)象是一個(gè)Arrays內(nèi)部類,并沒(méi)有實(shí)現(xiàn)集合的修改方法。Arrays.asList體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺(tái)的數(shù)據(jù)仍是數(shù)組。 - 泛型通配符<? extends T>來(lái)接收返回的數(shù)據(jù),此寫法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作為接口調(diào)用賦值時(shí)易出錯(cuò)
- 在無(wú)泛型限制定義的集合賦值給泛型限制的集合時(shí),在使用集合元素時(shí),需要進(jìn)行instanceof判斷,避免拋出ClassCastException異常
- 不要在foreach循環(huán)里進(jìn)行元素的remove/add操作。remove元素請(qǐng)使用Iterator方式,如果并發(fā)操作,需要對(duì)Iterator對(duì)象加鎖
- 在JDK7 版本及以上,Comparator 實(shí)現(xiàn)類要滿足如下三個(gè)條件,不然Arrays.sort,Collections.sort 會(huì)拋IllegalArgumentException 異常。
說(shuō)明:三個(gè)條件如下
1) x,y 的比較結(jié)果和y,x 的比較結(jié)果相反。
2) x>y,y>z,則x>z。
3) x=y,則x,z 比較結(jié)果和y,z 比較結(jié)果相同。 - 集合泛型定義時(shí),在JDK7 及以上,使用diamond 語(yǔ)法或全省略。
- 集合初始化時(shí),指定集合初始值大小。
- 使用entrySet 遍歷Map 類集合KV,而不是keySet 方式進(jìn)行遍歷
- 高度注意Map類集合K/V能不能存儲(chǔ)null值的情況,如下表格:
- 合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無(wú)序性(unsort)和不穩(wěn)定性(unorder)帶來(lái)的負(fù)面影響。
- 利用Set元素唯一的特性,可以快速對(duì)一個(gè)集合進(jìn)行去重操作,避免使用List的contains方法進(jìn)行遍歷、對(duì)比、去重操作
并發(fā)處理
- 獲取單例對(duì)象需要保證線程安全,其中的方法也要保證線程安全。
- 創(chuàng)建線程或線程池時(shí)請(qǐng)指定有意義的線程名稱,方便出錯(cuò)時(shí)回溯。
- 線程資源必須通過(guò)線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程
- 線程池不允許使用Executors去創(chuàng)建,而是通過(guò)ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)
- SimpleDateFormat 是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用DateUtils工具類。
- 必須回收自定義的ThreadLocal變量,尤其在線程池場(chǎng)景下,線程經(jīng)常會(huì)被復(fù)用,如果不清理自定義的 ThreadLocal變量,可能會(huì)影響后續(xù)業(yè)務(wù)邏輯和造成內(nèi)存泄露等問(wèn)題。盡量在代理中使用try-finally塊進(jìn)行回收
- 高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能鎖區(qū)塊,就不要鎖整個(gè)方法體;能用對(duì)象鎖,就不要用類鎖
- 對(duì)多個(gè)資源、數(shù)據(jù)庫(kù)表、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序,否則可能會(huì)造成死鎖。
- 在使用阻塞等待獲取鎖的方式中,必須在try代碼塊之外,并且在加鎖方法與try代碼塊之間沒(méi)有任何可能拋出異常的方法調(diào)用,避免加鎖成功后,在finally中無(wú)法解鎖。
- 在使用嘗試機(jī)制來(lái)獲取鎖的方式中,進(jìn)入業(yè)務(wù)代碼塊之前,必須先判斷當(dāng)前線程是否持有鎖。鎖的釋放規(guī)則與鎖的阻塞等待方式相同
- 并發(fā)修改同一記錄時(shí),避免更新丟失,需要加鎖。要么在應(yīng)用層加鎖,要么在緩存加鎖,要么在數(shù)據(jù)庫(kù)層使用樂(lè)觀鎖,使用version作為更新依據(jù)
- 多線程并行處理定時(shí)任務(wù)時(shí),Timer運(yùn)行多個(gè)TimeTask時(shí),只要其中之一沒(méi)有捕獲拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,如果在處理定時(shí)任務(wù)時(shí)使用ScheduledExecutorService則沒(méi)有這個(gè)問(wèn)題
- 資金相關(guān)的金融敏感信息,使用悲觀鎖策略
- 使用CountDownLatch進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線程退出前必須調(diào)用countDown方法,線程執(zhí)行代碼注意catch異常,確保countDown方法被執(zhí)行到,避免主線程無(wú)法執(zhí)行至await方法,直到超時(shí)才返回結(jié)果
- 避免Random實(shí)例被多線程使用,雖然共享該實(shí)例是線程安全的,但會(huì)因競(jìng)爭(zhēng)同一seed 導(dǎo)致的性能下降
- 在并發(fā)場(chǎng)景下,通過(guò)雙重檢查鎖(double-checked locking)實(shí)現(xiàn)延遲初始化的優(yōu)化問(wèn)題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較為簡(jiǎn)單一種(適用于JDK5及以上版本),將目標(biāo)屬性聲明為 volatile型
- volatile解決多線程內(nèi)存不可見(jiàn)問(wèn)題。對(duì)于一寫多讀,是可以解決變量同步問(wèn)題,但是如果多寫,同樣無(wú)法解決線程安全問(wèn)題。
- HashMap在容量不夠進(jìn)行resize時(shí)由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致CPU飆升,在開(kāi)發(fā)過(guò)程中可以使用其它數(shù)據(jù)結(jié)構(gòu)或加鎖來(lái)規(guī)避此風(fēng)險(xiǎn)
- ThreadLocal對(duì)象使用static修飾,ThreadLocal無(wú)法解決共享對(duì)象的更新問(wèn)題
控制語(yǔ)句
- 在一個(gè)switch塊內(nèi),每個(gè)case要么通過(guò)continue/break/return等來(lái)終止,要么注釋說(shuō)明程序?qū)⒗^續(xù)執(zhí)行到哪一個(gè)case為止;在一個(gè)switch塊內(nèi),都必須包含一個(gè)default語(yǔ)句并且放在最后,即使它什么代碼也沒(méi)有
- 當(dāng)switch括號(hào)內(nèi)的變量類型為String并且此變量為外部參數(shù)時(shí),必須先進(jìn)行null判斷
- 在if/else/for/while/do語(yǔ)句中必須使用大括號(hào)
- 在高并發(fā)場(chǎng)景中,避免使用”等于”判斷作為中斷或退出的條件
- 表達(dá)異常的分支時(shí),少用if-else方式
- 除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執(zhí)行其它復(fù)雜的語(yǔ)句,將復(fù)雜邏輯判斷的結(jié)果賦值給一個(gè)有意義的布爾變量名,以提高可讀性
- 不要在其它表達(dá)式(尤其是條件表達(dá)式)中,插入賦值語(yǔ)句
- 循環(huán)體中的語(yǔ)句要考量性能,以下操作盡量移至循環(huán)體外處理,如定義對(duì)象、變量、獲取數(shù)據(jù)庫(kù)連接,進(jìn)行不必要的try-catch操作(這個(gè)try-catch是否可以移至循環(huán)體外)。
- 避免采用取反邏輯運(yùn)算符
- 接口入?yún)⒈Wo(hù),這種場(chǎng)景常見(jiàn)的是用作批量操作的接口
- 下列情形,需要進(jìn)行參數(shù)校驗(yàn):
1) 調(diào)用頻次低的方法。
2) 執(zhí)行時(shí)間開(kāi)銷很大的方法。此情形中,參數(shù)校驗(yàn)時(shí)間幾乎可以忽略不計(jì),但如果因?yàn)閰?shù)錯(cuò)誤導(dǎo)致 中間執(zhí)行回退,或者錯(cuò)誤,那得不償失。
3) 需要極高穩(wěn)定性和可用性的方法。
4) 對(duì)外提供的開(kāi)放接口,不管是RPC/API/HTTP接口。
5) 敏感權(quán)限入口。 - 下列情形,不需要進(jìn)行參數(shù)校驗(yàn):
1) 極有可能被循環(huán)調(diào)用的方法。但在方法說(shuō)明里必須注明外部參數(shù)檢查要求。
2) 底層調(diào)用頻度比較高的方法。畢竟是像純凈水過(guò)濾的最后一道,參數(shù)錯(cuò)誤不太可能到底層才會(huì)暴露問(wèn)題。一般DAO層與Service層都在同一個(gè)應(yīng)用中,部署在同一臺(tái)服務(wù)器中,所以DAO的參數(shù)校驗(yàn),可以省略。
3) 被聲明成private只會(huì)被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過(guò)檢查或者肯定不會(huì)有問(wèn)題,此時(shí)可以不校驗(yàn)參數(shù)。
注釋規(guī)范
- 類、類屬性、類方法的注釋必須使用Javadoc規(guī)范,使用/*內(nèi)容/格式,不得使用// xxx方式
- 所有的抽象方法(包括接口中的方法)必須要用Javadoc注釋、除了返回值、參數(shù)、異常說(shuō)明外,還必須指出該方法做什么事情,實(shí)現(xiàn)什么功能
- 所有的類都必須添加創(chuàng)建者和創(chuàng)建日期
- 方法內(nèi)部單行注釋,在被注釋語(yǔ)句上方另起一行,使用//注釋。方法內(nèi)部多行注釋使用/* */注釋,注意與代碼對(duì)齊
- 所有的枚舉類型字段必須要有注釋,說(shuō)明每個(gè)數(shù)據(jù)項(xiàng)的用途
- 與其“半吊子”英文來(lái)注釋,不如用中文注釋把問(wèn)題說(shuō)清楚。專有名詞與關(guān)鍵字保持英文原文即可。
- 代碼修改的同時(shí),注釋也要進(jìn)行相應(yīng)的修改,尤其是參數(shù)、返回值、異常、核心邏輯等的修改
- 謹(jǐn)慎注釋掉代碼。在上方詳細(xì)說(shuō)明,而不是簡(jiǎn)單地注釋掉。如果無(wú)用,則刪除。
- 對(duì)于注釋的要求:第一、能夠準(zhǔn)確反映設(shè)計(jì)思想和代碼邏輯;第二、能夠描述業(yè)務(wù)含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒(méi)有注釋的大段代碼對(duì)于閱讀者形同天書,注釋是給自己看的,即使隔很長(zhǎng)時(shí)間,也能清晰理解當(dāng)時(shí)的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。
- 好的命名、代碼結(jié)構(gòu)是自解釋的,注釋力求精簡(jiǎn)準(zhǔn)確、表達(dá)到位。避免出現(xiàn)注釋的一個(gè)極端:過(guò)多過(guò)濫的注釋,代碼的邏輯一旦修改,修改注釋是相當(dāng)大的負(fù)擔(dān)
- 特殊注釋標(biāo)記,請(qǐng)注明標(biāo)記人與標(biāo)記時(shí)間。注意及時(shí)處理這些標(biāo)記,通過(guò)標(biāo)記掃描,經(jīng)常清理此類標(biāo)記。線上故障有時(shí)候就是來(lái)源于這些標(biāo)記處的代碼
其他
- 在使用正則表達(dá)式時(shí),利用好其預(yù)編譯功能,可以有效加快正則匹配速度
- velocity調(diào)用POJO類的屬性時(shí),直接使用屬性名取值即可,模板引擎會(huì)自動(dòng)按規(guī)范調(diào)用POJO的getXxx(),如果是boolean基本數(shù)據(jù)類型變量(boolean命名不需要加is前綴),會(huì)自動(dòng)調(diào)用isXxx()方法
- 后臺(tái)輸送給頁(yè)面的變量必須加$!{var}——中間的感嘆號(hào)
- 注意 Math.random() 這個(gè)方法返回是double類型,注意取值的范圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數(shù)類型的隨機(jī)數(shù),不要將x放大10的若干倍然后取整,直接使用Random對(duì)象的nextInt或者nextLong方法
- 獲取當(dāng)前毫秒數(shù)System.currentTimeMillis(); 而不是new Date().getTime();
- 日期格式化時(shí),傳入pattern中表示年份統(tǒng)一使用小寫的y
- 不要在視圖模板中加入任何復(fù)雜的邏輯
- 任何數(shù)據(jù)結(jié)構(gòu)的構(gòu)造或初始化,都應(yīng)指定大小,避免數(shù)據(jù)結(jié)構(gòu)無(wú)限增長(zhǎng)吃光內(nèi)存
- 及時(shí)清理不再使用的代碼段或配置信息
異常日志
異常處理
- java類庫(kù)中定義的可以通過(guò)預(yù)檢查方式規(guī)避的RuntimeException異常不應(yīng)該通過(guò)catch方式處理。如NullPointException、IndexOutOfBoundsException。
- 異常不要用作流程控制、條件控制。
- catch是要分清是穩(wěn)定代碼和非穩(wěn)定代碼,對(duì)于非穩(wěn)定代碼catch盡可能的按照異常類型分類。
- 捕獲異常一定要做處理,如果不想處理就拋給上層調(diào)用者。
- 有try塊放在事務(wù)中,catch異常后如果需要回滾事務(wù),一定要注意手動(dòng)回滾事務(wù)。
- finally塊中必須對(duì)資源對(duì)象、流對(duì)象進(jìn)行關(guān)閉,有異常也要catch。
- 不要在finally塊中使用return
- 捕獲的異常要和拋的異常匹配或者捕獲的異常是拋異常的父類
- 在調(diào)用RPC、二方包、或動(dòng)態(tài)生成類的相關(guān)方法時(shí),捕獲異常一定要用Throwable類攔截
- 方法的返回值可以是null,但是必須要說(shuō)明什么情況返回null
- 防止NEP:
- 返回類型是基本類型 ,return包裝類型的對(duì)象。
- 數(shù)據(jù)庫(kù)查詢的結(jié)果可能是null
- 集合里的元素即時(shí)isNotEmpty,取出來(lái)的元素也可能是null
- 遠(yuǎn)程調(diào)用返回對(duì)象時(shí),必須要進(jìn)行判空處理
- 對(duì)于Session中的數(shù)據(jù)要進(jìn)行判空處理
- 級(jí)聯(lián)調(diào)用有可能產(chǎn)生空指針
- 定義時(shí)區(qū)分unchecked / checked 異常,避免直接拋出new RuntimeException(),更不允許拋出Exception或者Throwable,應(yīng)使用有業(yè)務(wù)含義的自定義異常。推薦業(yè)界已定義過(guò)的自定義異常,如:DAOException / ServiceException等
- 對(duì)于公司外的http/api開(kāi)放接口必須使用“錯(cuò)誤碼”;而應(yīng)用內(nèi)部推薦異常拋出;跨應(yīng)用間RPC調(diào)用優(yōu)先考慮使用Result方式,封裝isSuccess()方法、“錯(cuò)誤碼”、“錯(cuò)誤簡(jiǎn)短信息”
- 避免出現(xiàn)重復(fù)的代碼(Don't Repeat Yourself),即DRY原則
日志規(guī)約
- 應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)中的API,而應(yīng)依賴使用日志框架 SLF4J中的API,使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一
- 所有日志文件至少保存15天,因?yàn)橛行┊惓>邆湟浴爸堋睘轭l次發(fā)生的特點(diǎn)。網(wǎng)絡(luò)運(yùn)行狀態(tài)、安全相關(guān)信息、系統(tǒng)監(jiān)測(cè)、管理后臺(tái)操作、用戶敏感操作需要留存相關(guān)的網(wǎng)絡(luò)日志不少于6個(gè)月
- 應(yīng)用中的擴(kuò)展日志(如打點(diǎn)、臨時(shí)監(jiān)控、訪問(wèn)日志等)命名方式:appName_logType_logName.log。logType:日志類型,如stats/monitor/access等;logName:日志描述。這種命名的好處:通過(guò)文件名就可知道日志文件屬于什么應(yīng)用,什么類型,什么目的,也有利于歸類查找
- 在日志輸出時(shí),字符串變量之間的拼接使用占位符的方式
- 對(duì)于trace/debug/info級(jí)別的日志輸出,必須進(jìn)行日志級(jí)別的開(kāi)關(guān)判斷
- 避免重復(fù)打印日志,浪費(fèi)磁盤空間,務(wù)必在log4j.xml中設(shè)置additivity=false
- 異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場(chǎng)信息和異常堆棧信息。如果不處理,那么通過(guò)關(guān)鍵字throws往上拋出
- 謹(jǐn)慎地記錄日志。生產(chǎn)環(huán)境禁止輸出debug日志;有選擇地輸出info日志;如果使用warn來(lái)記錄剛上線時(shí)的業(yè)務(wù)行為信息,一定要注意日志輸出量的問(wèn)題,避免把服務(wù)器磁盤撐爆,并記得及時(shí)刪除這些觀察日志
- 可以使用warn日志級(jí)別來(lái)記錄用戶輸入?yún)?shù)錯(cuò)誤的情況,避免用戶投訴時(shí),無(wú)所適從。如非必要,請(qǐng)不要在此場(chǎng)景打出error級(jí)別,避免頻繁報(bào)警
- 盡量用英文來(lái)描述日志錯(cuò)誤信息,如果日志中的錯(cuò)誤信息用英文描述不清楚的話使用中文描述即可,否則容易產(chǎn)生歧義?!緩?qiáng)制】國(guó)際化團(tuán)隊(duì)或海外部署的服務(wù)器由于字符集問(wèn)題,使用全英文來(lái)注釋和描述日志錯(cuò)誤信息
單元測(cè)試
- 好的單元測(cè)試必須遵守AIR原則
- 單元測(cè)試應(yīng)該是全自動(dòng)執(zhí)行的,并且非交互式的。測(cè)試用例通常是被定期執(zhí)行的,執(zhí)行過(guò)程必須完全自動(dòng)化才有意義。輸出結(jié)果需要人工檢查的測(cè)試不是一個(gè)好的單元測(cè)試。單元測(cè)試中不準(zhǔn)使用System.out來(lái)進(jìn)行人肉驗(yàn)證,必須使用assert來(lái)驗(yàn)證
- 保持單元測(cè)試的獨(dú)立性。為了保證單元測(cè)試穩(wěn)定可靠且便于維護(hù),單元測(cè)試用例之間決不能互相調(diào)用,也不能依賴執(zhí)行的先后次序
- 單元測(cè)試是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響
- 對(duì)于單元測(cè)試,要保證測(cè)試粒度足夠小,有助于精確定位問(wèn)題。單測(cè)粒度至多是類級(jí)別,一般是方法級(jí)別
- 核心業(yè)務(wù)、核心應(yīng)用、核心模塊的增量代碼確保單元測(cè)試通過(guò)
- 單元測(cè)試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業(yè)務(wù)代碼目錄下
- 單元測(cè)試的基本目標(biāo):語(yǔ)句覆蓋率達(dá)到70%;核心模塊的語(yǔ)句覆蓋率和分支覆蓋率都要達(dá)到100%
- 編寫單元測(cè)試代碼遵守BCDE原則,以保證被測(cè)試模塊的交付質(zhì)量
- 對(duì)于數(shù)據(jù)庫(kù)相關(guān)的查詢,更新,刪除等操作,不能假設(shè)數(shù)據(jù)庫(kù)里的數(shù)據(jù)是存在的,或者直接操作數(shù)據(jù)庫(kù)把數(shù)據(jù)插入進(jìn)去,請(qǐng)使用程序插入或者導(dǎo)入數(shù)據(jù)的方式來(lái)準(zhǔn)備數(shù)據(jù)
- 和數(shù)據(jù)庫(kù)相關(guān)的單元測(cè)試,可以設(shè)定自動(dòng)回滾機(jī)制,不給數(shù)據(jù)庫(kù)造成臟數(shù)據(jù)?;蛘邔?duì)單元測(cè)試產(chǎn)生的數(shù)據(jù)有明確的前后綴標(biāo)識(shí)
- 對(duì)于不可測(cè)的代碼在適當(dāng)?shù)臅r(shí)機(jī)做必要的重構(gòu),使代碼變得可測(cè),避免為了達(dá)到測(cè)試要求而書寫不規(guī)范測(cè)試代碼
- 在設(shè)計(jì)評(píng)審階段,開(kāi)發(fā)人員需要和測(cè)試人員一起確定單元測(cè)試范圍,單元測(cè)試最好覆蓋所有測(cè)試用例
- 單元測(cè)試作為一種質(zhì)量保障手段,在項(xiàng)目提測(cè)前完成單元測(cè)試,不建議項(xiàng)目發(fā)布后補(bǔ)充單元測(cè)試用例
- 為了更方便地進(jìn)行單元測(cè)試,業(yè)務(wù)代碼應(yīng)避免以下情況: 1.構(gòu)造方法中做的事情過(guò)多。 2. 存在過(guò)多的全局變量和靜態(tài)方法。 3. 存在過(guò)多的外部依賴。 4. 存在過(guò)多的條件語(yǔ)句
- 那是測(cè)試同學(xué)干的事情。本文是開(kāi)發(fā)手冊(cè),凡是本文內(nèi)容都是與開(kāi)發(fā)同學(xué)強(qiáng)相關(guān)的。 1. 單元測(cè)試代碼是多余的。系統(tǒng)的整體功能與各單元部件的測(cè)試正常與否是強(qiáng)相關(guān)的。 2. 單元測(cè)試代碼不需要維護(hù)。一年半載后,那么單元測(cè)試幾乎處于廢棄狀態(tài)。 3. 單元測(cè)試與線上故障沒(méi)有辯證關(guān)系。好的單元測(cè)試能夠最大限度地規(guī)避線上故障
安全規(guī)約
- 隸屬于用戶個(gè)人的頁(yè)面或者功能必須進(jìn)行權(quán)限控制校驗(yàn)
- 用戶敏感數(shù)據(jù)禁止直接展示,必須對(duì)展示數(shù)據(jù)進(jìn)行脫敏
- 用戶輸入的SQL參數(shù)嚴(yán)格使用參數(shù)綁定或者M(jìn)ETADATA字段值限定,防止SQL注入,禁止字符串拼接SQL訪問(wèn)數(shù)據(jù)庫(kù)
- 用戶請(qǐng)求傳入的任何參數(shù)必須做有效性驗(yàn)證
- 禁止向HTML頁(yè)面輸出未經(jīng)安全過(guò)濾或未正確轉(zhuǎn)義的用戶數(shù)據(jù)
- 表單、AJAX提交必須執(zhí)行CSRF安全驗(yàn)證
- 在使用平臺(tái)資源,譬如短信、郵件、電話、下單、支付,必須實(shí)現(xiàn)正確的防重放的機(jī)制,如數(shù)量限制、疲勞度控制、驗(yàn)證碼校驗(yàn),避免被濫刷而導(dǎo)致資損
- 發(fā)貼、評(píng)論、發(fā)送即時(shí)消息等用戶生成內(nèi)容的場(chǎng)景必須實(shí)現(xiàn)防刷、文本內(nèi)容違禁詞過(guò)濾等風(fēng)控策略
MySQL數(shù)據(jù)庫(kù)
建表規(guī)約
- 表達(dá)是與否概念的字段,必須使用is_xxx的方式命名,數(shù)據(jù)類型是unsigned tinyint(1表示是,0表示否)
- 表名、字段名必須使用小寫字母或數(shù)字,禁止出現(xiàn)數(shù)字開(kāi)頭,禁止兩個(gè)下劃線中間只出現(xiàn)數(shù)字。數(shù)據(jù)庫(kù)字段名的修改代價(jià)很大,因?yàn)闊o(wú)法進(jìn)行預(yù)發(fā)布,所以字段名稱需要慎重考慮
- 表名不使用復(fù)數(shù)名詞
- 禁用保留字,如desc、range、match、delayed等,請(qǐng)參考MySQL官方保留字
- 主鍵索引名為pk_字段名;唯一索引名為uk_字段名;普通索引名則為idx_字段名
- 小數(shù)類型為decimal,禁止使用float和double
- 如果存儲(chǔ)的字符串長(zhǎng)度幾乎相等,使用char定長(zhǎng)字符串類型
- varchar是可變長(zhǎng)字符串,不預(yù)先分配存儲(chǔ)空間,長(zhǎng)度不要超過(guò)5000,如果存儲(chǔ)長(zhǎng)度大于此值,定義字段類型為text,獨(dú)立出來(lái)一張表,用主鍵來(lái)對(duì)應(yīng),避免影響其它字段索引效率
- 表必備三字段:id, create_time, update_time
- 表的命名最好是遵循“業(yè)務(wù)名稱_表的作用”
- 庫(kù)名與應(yīng)用名稱盡量一致
- 字段允許適當(dāng)冗余,以提高查詢性能,但必須考慮數(shù)據(jù)一致。冗余字段應(yīng)遵循: 1) 不是頻繁修改的字段。 2) 不是varchar超長(zhǎng)字段,更不能是text字段。3) 不是唯一索引的字段。
- 如果修改字段含義或?qū)ψ侄伪硎镜臓顟B(tài)追加時(shí),需要及時(shí)更新字段注釋
- 單表行數(shù)超過(guò)500萬(wàn)行或者單表容量超過(guò)2GB,才推薦進(jìn)行分庫(kù)分表
- 合適的字符存儲(chǔ)長(zhǎng)度,不但節(jié)約數(shù)據(jù)庫(kù)表空間、節(jié)約索引存儲(chǔ),更重要的是提升檢索速度
索引規(guī)約
- 業(yè)務(wù)上具有唯一特性的字段,即使是多個(gè)字段的組合,也必須建成唯一索引
- 超過(guò)三個(gè)表禁止join。需要join的字段,數(shù)據(jù)類型必須絕對(duì)一致;多表關(guān)聯(lián)查詢時(shí),保證被關(guān)聯(lián)的字段需要有索引
- 在varchar字段上建立索引時(shí),必須指定索引長(zhǎng)度,沒(méi)必要對(duì)全字段建立索引,根據(jù)實(shí)際文本區(qū)分度決定索引長(zhǎng)度即可
- 頁(yè)面搜索嚴(yán)禁左模糊或者全模糊,如果需要請(qǐng)走搜索引擎來(lái)解決
- 如果有order by的場(chǎng)景,請(qǐng)注意利用索引的有序性。order by 最后的字段是組合索引的一部分,并且放在索引組合順序的最后,避免出現(xiàn)file_sort的情況,影響查詢性能
- 利用覆蓋索引來(lái)進(jìn)行查詢操作,避免回表
- 利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁(yè)場(chǎng)景
- SQL性能優(yōu)化的目標(biāo):至少要達(dá)到 range 級(jí)別,要求是ref級(jí)別,如果可以是consts最好
- 建組合索引的時(shí)候,區(qū)分度最高的在最左邊
- 防止因字段類型不同造成的隱式轉(zhuǎn)換,導(dǎo)致索引失效
- 創(chuàng)建索引時(shí)避免有如下極端誤解: 1) 寧濫勿缺。認(rèn)為一個(gè)查詢就需要建一個(gè)索引。 2) 寧缺勿濫。認(rèn)為索引會(huì)消耗空間、嚴(yán)重拖慢記錄的更新以及行的新增速度。 3) 抵制惟一索引。認(rèn)為業(yè)務(wù)的惟一性一律需要在應(yīng)用層通過(guò)“先查后插”方式解決。
SQL語(yǔ)句
- 不要使用count(列名)或count(常量)來(lái)替代count(),count()是SQL92定義的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語(yǔ)法,跟數(shù)據(jù)庫(kù)無(wú)關(guān),跟NULL和非NULL無(wú)關(guān)
- count(distinct col) 計(jì)算該列除NULL之外的不重復(fù)行數(shù),注意 count(distinct col1, col2) 如果其中一列全為NULL,那么即使另一列有不同的值,也返回為0
- 當(dāng)某一列的值全是NULL時(shí),count(col)的返回結(jié)果為0,但sum(col)的返回結(jié)果為NULL,因此使用sum()時(shí)需注意NPE問(wèn)題
- 使用ISNULL()來(lái)判斷是否為NULL值
- 代碼中寫分頁(yè)查詢邏輯時(shí),若count為0應(yīng)直接返回,避免執(zhí)行后面的分頁(yè)語(yǔ)句
- 不得使用外鍵與級(jí)聯(lián),一切外鍵概念必須在應(yīng)用層解決
- 禁止使用存儲(chǔ)過(guò)程,存儲(chǔ)過(guò)程難以調(diào)試和擴(kuò)展,更沒(méi)有移植性
- 數(shù)據(jù)訂正(特別是刪除、修改記錄操作)時(shí),要先select,避免出現(xiàn)誤刪除,確認(rèn)無(wú)誤才能執(zhí)行更新語(yǔ)句
- in操作能避免則避免,若實(shí)在避免不了,需要仔細(xì)評(píng)估in后邊的集合元素?cái)?shù)量,控制在1000個(gè)之內(nèi)
- 如果有國(guó)際化需要,所有的字符存儲(chǔ)與表示,均以u(píng)tf-8編碼,注意字符統(tǒng)計(jì)函數(shù)的區(qū)別
- TRUNCATE TABLE 比 DELETE 速度快,且使用的系統(tǒng)和事務(wù)日志資源少,但TRUNCATE無(wú)事務(wù)且不觸發(fā)trigger,有可能造成事故,故不建議在開(kāi)發(fā)代碼中使用此語(yǔ)句
ORM映射
- 在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明
- POJO類的布爾屬性不能加is,而數(shù)據(jù)庫(kù)字段必須加is_,要求在resultMap中進(jìn)行字段與屬性之間的映射
- 不要用resultClass當(dāng)返回參數(shù),即使所有類屬性名與數(shù)據(jù)庫(kù)字段一一對(duì)應(yīng),也需要定義;反過(guò)來(lái),每一個(gè)表也必然有一個(gè)POJO類與之對(duì)應(yīng)
- sql.xml配置參數(shù)使用:#{},#param# 不要使用${} 此種方式容易出現(xiàn)SQL注入
- iBATIS自帶的queryForList(String statementName,int start,int size)不推薦使用
- 不允許直接拿HashMap與Hashtable作為查詢結(jié)果集的輸出
- 更新數(shù)據(jù)表記錄時(shí),必須同時(shí)更新記錄對(duì)應(yīng)的gmt_modified字段值為當(dāng)前時(shí)間
- 不要寫一個(gè)大而全的數(shù)據(jù)更新接口。傳入為POJO類,不管是不是自己的目標(biāo)更新字段,都進(jìn)行update table set c1=value1,c2=value2,c3=value3; 這是不對(duì)的。執(zhí)行SQL時(shí),不要更新無(wú)改動(dòng)的字段,一是易出錯(cuò);二是效率低;三是增加binlog存儲(chǔ)
- @Transactional事務(wù)不要濫用。事務(wù)會(huì)影響數(shù)據(jù)庫(kù)的QPS,另外使用事務(wù)的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補(bǔ)償、統(tǒng)計(jì)修正等
- <isEqual>中的compareValue是與屬性值對(duì)比的常量,一般是數(shù)字,表示相等時(shí)帶上此條件;<isNotEmpty>表示不為空且不為null時(shí)執(zhí)行;<isNotNull>表示不為null值時(shí)執(zhí)行
服務(wù)器
- 高并發(fā)服務(wù)器建議調(diào)小TCP協(xié)議的time_wait超時(shí)時(shí)間
- 調(diào)大服務(wù)器所支持的最大文件句柄數(shù)(File Descriptor,簡(jiǎn)寫為fd)
- 給JVM環(huán)境參數(shù)設(shè)置-XX:+HeapDumpOnOutOfMemoryError參數(shù),讓JVM碰到OOM場(chǎng)景時(shí)輸出dump信息
- 在線上生產(chǎn)環(huán)境,JVM的Xms和Xmx設(shè)置一樣大小的內(nèi)存容量,避免在GC 后調(diào)整堆大小帶來(lái)的壓力
- 服務(wù)器內(nèi)部重定向使用forward;外部重定向地址使用URL拼裝工具類來(lái)生成,否則會(huì)帶來(lái)URL維護(hù)不一致的問(wèn)題和潛在的安全風(fēng)險(xiǎn)
設(shè)計(jì)規(guī)約
- 存儲(chǔ)方案和底層數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)獲得評(píng)審一致通過(guò),并沉淀成為文檔
- 在需求分析階段,如果與系統(tǒng)交互的User超過(guò)一類并且相關(guān)的User Case超過(guò)5個(gè),使用用例圖來(lái)表達(dá)更加清晰的結(jié)構(gòu)化需求
- 如果某個(gè)業(yè)務(wù)對(duì)象的狀態(tài)超過(guò)3個(gè),使用狀態(tài)圖來(lái)表達(dá)并且明確狀態(tài)變化的各個(gè)觸發(fā)條件
- 如果系統(tǒng)中某個(gè)功能的調(diào)用鏈路上的涉及對(duì)象超過(guò)3個(gè),使用時(shí)序圖來(lái)表達(dá)并且明確各調(diào)用環(huán)節(jié)的輸入與輸出
- 如果系統(tǒng)中模型類超過(guò)5個(gè),并且存在復(fù)雜的依賴關(guān)系,使用類圖來(lái)表達(dá)并且明確類之間的關(guān)系
- 如果系統(tǒng)中超過(guò)2個(gè)對(duì)象之間存在協(xié)作關(guān)系,并且需要表示復(fù)雜的處理流程,使用活動(dòng)圖來(lái)表示
- 需求分析與系統(tǒng)設(shè)計(jì)在考慮主干功能的同時(shí),需要充分評(píng)估異常流程與業(yè)務(wù)邊界
- 類在設(shè)計(jì)與實(shí)現(xiàn)時(shí)要符合單一原則
- 謹(jǐn)慎使用繼承的方式來(lái)進(jìn)行擴(kuò)展,優(yōu)先使用聚合/組合的方式來(lái)實(shí)現(xiàn)
- 系統(tǒng)設(shè)計(jì)時(shí),根據(jù)依賴倒置原則,盡量依賴抽象類與接口,有利于擴(kuò)展與維護(hù)
- 系統(tǒng)設(shè)計(jì)時(shí),注意對(duì)擴(kuò)展開(kāi)放,對(duì)修改閉合
- 系統(tǒng)設(shè)計(jì)階段,共性業(yè)務(wù)或公共行為抽取出來(lái)公共模塊、公共配置、公共類、公共方法等,避免出現(xiàn)重復(fù)代碼或重復(fù)配置的情況
- 避免如下誤解:敏捷開(kāi)發(fā) = 講故事 + 編碼 + 發(fā)布
- 系統(tǒng)設(shè)計(jì)主要目的是明確需求、理順邏輯、后期維護(hù),次要目的用于指導(dǎo)編碼
- 設(shè)計(jì)的本質(zhì)就是識(shí)別和表達(dá)系統(tǒng)難點(diǎn),找到系統(tǒng)的變化點(diǎn),并隔離變化點(diǎn)
- 系統(tǒng)架構(gòu)設(shè)計(jì)的目的:
1.確定系統(tǒng)邊界。確定系統(tǒng)在技術(shù)層面上的做與不做。
2.確定系統(tǒng)內(nèi)模塊之間的關(guān)系。確定模塊之間的依賴關(guān)系及模塊的宏觀輸入與輸出。
3.確定指導(dǎo)后續(xù)設(shè)計(jì)與演化的原則。使后續(xù)的子系統(tǒng)或模塊設(shè)計(jì)在規(guī)定的框架內(nèi)繼續(xù)演化。
4.確定非功能性需求。非功能性需求是指安全性、可用性、可擴(kuò)展性等 - 在做無(wú)障礙產(chǎn)品設(shè)計(jì)時(shí),需要考慮到:
- 所有可交互的控件元素必須能被tab鍵聚焦,并且焦點(diǎn)順序需符合自然操作邏輯。
2.用于登陸校驗(yàn)和請(qǐng)求攔截的驗(yàn)證碼均需提供圖形驗(yàn)證以外的其它方式。
3.自定義的控件類型需明確交互方式。
- 所有可交互的控件元素必須能被tab鍵聚焦,并且焦點(diǎn)順序需符合自然操作邏輯。