RuntimeException
異常不該該經過catch
的方式來處理,好比:NullPointerException
,IndexOutOfBoundsException
等等【說明:沒法經過預檢查的異常除外,好比,在解析字符串形式的數字時,可能存在數字格式錯誤,不得不經過catch NumberFormatException
來實現】正例:
if (obj != null) {...}
反例:try { obj.method(); } catch (NullPointerException e) {…}java
catch
時請分清穩定代碼和非穩定代碼,穩定代碼指的是不管如何不會出錯的代碼。對於非穩定代碼的catch
儘量進行區分異常類型,再作對應的異常處理【說明:對大段代碼進行try-catch
,使程序沒法根據不一樣的異常作出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。】try
塊放到了事務代碼中,catch
異常後,若是須要回滾事務,必定要注意手動回滾事務finally
塊必須對資源對象、流對象進行關閉,有異常也要作try-catch
。【若是JDK7及以上,可使用try-with-resources
方式】finally
塊中使用return
【說明:try
塊中的return
語句執行成功後,並不立刻返回,而是繼續執行finally
塊中的語句,若是此處存在return
語句,則在此直接返回,無情丟棄掉try
塊中的返回點。】// 反例 private int x = 0; public int checkReturn() { try { // x 等於 1,此處不返回 return ++x; } finally { // 返回的結果是 2 return ++x; } }
RPC、二方包、或動態生成類
的相關方法時,捕捉異常必須使用Throwable
類來進行攔截。【說明:經過反射機制來調用方法,若是找不到方法,拋出NoSuchMethodException
。什麼狀況會拋出NoSuchMethodError
呢?二方包在類衝突時,仲裁機制可能致使引入非預期的版本使類的方法簽名不匹配,或者在字節碼修改框架(好比:ASM)動態建立或修改類時,修改了相應的方法簽名。這些狀況,即便代碼編譯期是正確的,但在代碼運行期時,會拋出NoSuchMethodError
。】null
,不強制返回空集合,或者空對象等,必須添加註釋充分說明什麼狀況下會返回null
值【說明:本手冊明確防止NPE
是調用者的責任
。即便被調用方法返回空集合或者空對象,對調用者來講,也並不是高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回null
的狀況。】NPE
,是程序員的基本修養,注意NPE
產生的場景1)返回類型爲基本數據類型,
return
包裝數據類型的對象時,自動拆箱有可能產生 NPE。【反例:public int f() { return Integer 對象}
,若是爲null
,自動解箱拋NPE
】
2)數據庫的查詢結果可能爲null
3)集合裏的元素即便isNotEmpty
,取出的數據元素也可能爲null
。
4)遠程調用返回對象時,一概要求進行空指針判斷,防止NPE
5)對於Session
中獲取的數據,建議進行NPE
檢查,避免空指針。
6)級聯調用obj.getA().getB().getC();
一連串調用,易產生NPE
。正例:使用JDK8的Optional
類來防止NPE問題。mysql
unchecked / checked
異常,避免直接拋出new RuntimeException()
,更不容許拋出Exception
或者Throwable
,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如:DAOException / ServiceException
等。http/api
開放接口必須使用「錯誤碼」;而應用內部推薦異常拋出;跨應用間RPC
調用優先考慮使用Result
方式,封裝isSuccess()
方法、「錯誤碼」、「錯誤簡短信息」【說明:關於RPC
方法返回方式使用Result
方式的理由:1)使用拋異常返回方式,調用方若是沒有捕獲到就會產生運行時錯誤。2)若是不加棧信息,只是new自定義異常,加入本身的理解的error message
,對於調用端解決問題的幫助不會太多。若是加了棧信息,在頻繁調用出錯的狀況下,數據序列化和傳輸的性能損耗也是問題。】Don't Repeat Yourself
),即DRY原則【說明:隨意複製和粘貼代碼,必然會致使代碼的重複,在之後須要修改時,須要修改全部的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化】Log4j、Logback
)中的API,而應依賴使用日誌框架SLF4J
中的API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Test.class);
appName_logType_logName.log
。logType:日誌類型,如stats/monitor/access
等;logName:日誌描述。這種命名的好處:經過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找【說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於經過日誌對系統進行及時監控。正例:force-web
應用中單獨監控時區轉換異常,如:force_web_timeZoneConvert.log
】String
字符串的拼接會使用StringBuilder
的append()
方式,有必定的性能損耗。使用佔位符僅是替換動做,能夠有效提高性能。正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
】trace/debug/info
級別的日誌輸出,必須進行日誌級別的開關判斷【說明:雖然在debug(參數)的方法體內第一行代碼isDisabled(Level.DEBUG_INT)
爲真時(Slf4j的常見實現Log4j
和Logback
),就直接return
,可是參數可能會進行字符串拼接運算。此外,若是debug(getName())
這種參數內有getName()
方法調用,無謂浪費方法調用的開銷。】// 正例 // 若是判斷爲真,那麼能夠輸出trace和debug級別的日誌 if (logger.isDebugEnabled()) { logger.debug("Current ID is: {} and name is: {}", id, getName()); }
log4j.xml
中設置additivity=false
【正例:<logger name="com.taobao.dubbo.config" additivity="false">
】案發現場信息
和異常堆棧信息
。若是不處理,那麼經過關鍵字throws
往上拋出【正例:logger.error(各種參數或者對象 toString() + "_" + e.getMessage(), e);
】debug
日誌;有選擇地輸出info
日誌;若是使用warn
來記錄剛上線時的業務行爲信息,必定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌【說明:大量地輸出無效日誌,不利於系統性能提高,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能作什麼?能不能給問題排查帶來好處?
】warn
日誌級別來記錄用戶輸入參數錯誤的狀況,避免用戶投訴時,無所適從。如非必要,請不要在此場景打出error
級別,避免頻繁報警【說明:注意日誌輸出的級別,error
級別只記錄系統邏輯出錯、異常或者重要的錯誤
信息】AIR
原則【說明:單元測試在線上運行時,感受像空氣(AIR)同樣並不存在,但在測試質量的保障上,倒是很是關鍵的。好的單元測試宏觀上來講,具備自動化、獨立性、可重複執行的特色。A:Automatic
(自動化)I:Independent
(獨立性)R:Repeatable
(可重複)】System.out
來進行人肉驗證,必須使用assert
來驗證method2
須要依賴method1
的執行,將執行結果做爲method2
的輸入】check in
時單元測試都會被執行。若是單測對外部環境(網絡、服務、中間件等)有依賴,容易致使持續集成機制的不可用。正例:爲了避免受外界環境影響,要求設計代碼時就把SUT的依賴改爲注入,在測試時用spring
這樣的DI
框架注入一個本地(內存)實現或者Mock
實現】src/test/java
,不容許寫在業務代碼目錄下【說明:源碼編譯時會跳過此目錄,而單元測試框架默認是掃描此目錄】DAO
層,Manager
層,可重用度高的Service
,都應該進行單元測試。】BCDE
原則,以保證被測試模塊的交付質量【B:Border
,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等; C:Correct
,正確的輸入,並獲得預期的結果; D:Design
,與設計文檔相結合,來編寫單元測試; E:Error
,強制錯誤信息輸入(如:非法數據、異常流程、業務容許外等),並獲得預期的結果】權限控制校驗
【說明:防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單】脫敏
【說明:中國大陸我的手機號碼顯示爲:137****0969,隱藏中間4位,防止隱私泄露】SQL 注
入,禁止字符串拼接SQL訪問數據庫page size
過大致使內存溢出;2.惡意order by
致使數據庫慢查詢;3.任意重定向;4.SQL注入;5.反序列化注入;6.正則輸入源串拒絕服務ReDoS;說明:Java代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,可是若是攻擊人員使用的是特殊構造的字符串來驗證,有可能致使死循環的結果。】CSRF(Cross-site request forgery)
跨站請求僞造是一類常見編程漏洞。對於存在CSRF漏洞的應用/網站,攻擊者能夠事先構造好URL,只要受害者用戶一訪問,後臺便在用戶不知情的狀況下對數據庫中用戶參數進行相應修改。】is_xxx
的方式命名,數據類型是unsigned tinyint
(1表示是,0表示否)【說明:任何字段若是爲非負數,必須是unsigned。注意:POJO類中的任何布爾類型的變量,都不要加is
前綴,因此,須要在<resultMap>
設置從is_xxx
到Xxx
的映射關係。數據庫表示是與否的值,使用tinyint
類型,堅持is_xxx
的命名方式是爲了明確其取值含義與取值範圍】正例:表達
邏輯刪除
的字段名is_deleted
,1表示刪除,0表示未刪除linux
正例:
aliyun_admin
,rdc_config
,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name程序員
DO
類名也是單數形式,符合表達習慣】desc、range、match、delayed
等,請參考MySQL
官方保留字pk_字段名
;惟一索引名爲uk_字段名
;普通索引名則爲idx_字段名
【說明:pk_
即primary key
;uk_
即unique key
;idx_
即index
的簡稱】decimal
,禁止使用float
和double
【說明:在存儲的時候,float
和double
都存在精度損失的問題,極可能在比較值的時候,獲得不正確的結果。若是存儲的數據範圍超過decimal
的範圍,建議將數據拆成整數和小數並分開存儲】char
定長字符串類型varchar
是可變長字符串,不預先分配存儲空間,長度不要超過5000
,若是存儲長度大於此值,定義字段類型爲text
,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率id, create_time, update_time
【說明:其中id
必爲主鍵,類型爲bigint unsigned
、單表時自增、步長爲1。create_time, update_time
的類型均爲datetime
類型。】alipay_task / force_project / trade_config
】varchar
超長字段,更不能是text
字段。3)不是惟一索引的字段】(正例
:商品類目名稱使用頻率高,字段長度短,名稱基本一不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢)500萬
行或者單表容量超過2GB
,才推薦進行分庫分表
【說明:若是預計三年後的數據量根本達不到這個級別,請不要在建立表時就分庫分表】對象 | 年齡區間 | 類型 | 字節 | 表示範圍 |
---|---|---|---|---|
人 | 150歲以內 | tinyint unsigned | 1 | 無符號值:0到255 |
龜 | 數百歲 | smallint unsigned | 2 | 無符號值:0到65535 |
恐龍化石 | 數千萬年 | int unsigned | 4 | 無符號值:0到約42.9億 |
太陽 | 約50億年 | bigint unsigned | 8 | 無符號值:0到約10的19次方 |
惟一特性
的字段,即便是多個字段的組合,也必須建成惟一索引【說明:不要覺得惟一索引影響了insert
速度,這個速度損耗能夠忽略,但提升查找速度是明顯的;另外,即便在應用層作了很是完善的校驗控制,只要沒有惟一索引,根據墨菲定律,必然有髒數據產生】join
。須要join
的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段須要有索引
【說明:即便雙表join也要注意表索引、SQL性能】varchar
字段上創建索引時,必須指定索引長度,不必對全字段創建索引,根據實際文本區分度決定索引長度便可【說明:索引的長度與區分度是一對矛盾體,通常對字符串類型數據,長度爲20的索引,區分度會高達90%以上,可使用count(distinct left(列名, 索引長度))/count(*)
的區分度來肯定】B-Tree
的最左前綴匹配
特性,若是左邊的值未肯定,那麼沒法使用此索引】order by
的場景,請注意利用索引的有序性。order by
最後的字段是組合索引的一部分,而且放在索引組合順序的最後,避免出現file_sort
的狀況,影響查詢性能正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引若是存在範圍查詢,那麼索引有序性沒法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 沒法排序。web
正例:可以創建索引的種類分爲主鍵索引、惟一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用
explain
的結果,extra
列會出現:using index
spring
offset
行,而是取offset+N
行,而後返回放棄前offset
行,返回N
行,那當offset
特別大的時候,效率就很是的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行SQL改寫】正例:先快速定位須要獲取的id段,而後再關聯:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
sql
range
級別,要求是ref
級別,若是能夠是consts
最好【說明:1)consts
單表中最多隻有一個匹配行(主鍵或者惟一索引),在優化階段便可讀取到數據。2)ref
指的是使用普通的索引(normal index)。3)range
對索引進行範圍檢索。】反例:
explain
表的結果,type=index,索引物理文件全掃描,速度很是慢,這個index
級別比較range
還低,與全表掃描是小巫見大巫。數據庫
where c>? and d=?
那麼即便c的區分度更高,也必須把d放在索引的最前列,即索引idx_d_c
】正例:若是
where a=? and b=?
,若是a列的幾乎接近於惟一值,那麼只須要單建idx_a
索引便可編程
1)寧濫勿缺。認爲一個查詢就須要建一個索引
2)寧缺勿濫。認爲索引會消耗空間、嚴重拖慢記錄的更新以及行的新增速度
3)抵制唯一索引。認爲業務的唯一性一概須要在應用層經過「先查後插」方式解決json
count(列名)
或count(常量)
來替代count(*)
,count(*)
是SQL92定義的標準統計行數的語法,跟數據庫無關,跟NULL和非NULL無關【說明:count(*)
會統計值爲NULL
的行,而count(列名)
不會統計此列爲NULL值的行】count(distinct col)
計算該列除NULL以外的不重複行數,注意count(distinct col1, col2)
若是其中一列全爲 NULL,那麼即便另外一列有不一樣的值,也返回爲0count(col)
的返回結果爲 0,但sum(col)
的返回結果爲NULL,所以使用sum()
時需注意NPE
問題正例:使用以下方式來避免sum的NPE問題:
SELECT IFNULL(SUM(column), 0) FROM table;
ISNULL()
來判斷是否爲NULL
值【說明:NULL與任何值的直接比較都爲NULL。 1)NULL<>NULL
的返回結果是NULL
,而不是false
。 2)NULL=NULL
的返回結果是NULL
,而不是true
。 3)NULL<>1
的返回結果是NULL
,而不是true
。】count
爲0應直接返回,避免執行後面的分頁語句student_id
是主鍵,那麼成績表中的student_id
則爲外鍵。若是更新學生表中的student_id
,同時觸發成績表中的student_id
更新,即爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度】in
操做能避免則避免,若實在避免不了,須要仔細評估in後邊的集合元素數量,控制在1000
個以內utf-8
編碼,注意字符統計函數的區別。【說明:SELECT LENGTH("輕鬆工做");
返回爲12;SELECT CHARACTER_LENGTH("輕鬆工做");
返回爲4;若是須要存儲表情,那麼選擇utf8mb4
來進行存儲,注意它與utf-8
編碼的區別。】TRUNCATE TABLE
比DELETE
速度快,且使用的系統和事務日誌資源少,但TRUNCATE
無事務且不觸發trigger
,有可能形成事故,故不建議在開發代碼中使用此語句【說明:TRUNCATE TABLE
在功能上與不帶WHERE
子句的DELETE
語句相同】*
做爲查詢的字段列表,須要哪些字段必須明確寫明【說明:1)增長查詢分析器解析成本。2)增減字段容易與resultMap
配置不一致。3)無用字段增長網絡消耗,尤爲是text
類型的字段】is
,而數據庫字段必須加is_
,要求在resultMap
中進行字段與屬性之間的映射。【說明:參見定義POJO類以及數據庫字段定義規定,在<resultMap>
中增長映射,是必須的。在MyBatis Generator
生成的代碼中,須要進行對應的修改】resultClass
當返回參數,即便全部類屬性名與數據庫字段一一對應,也須要定義;反過來,每個表也必然有一個POJO
類與之對應【說明:配置映射關係,使字段與DO
類解耦,方便維護。】sql.xml
配置參數使用:#{},#param#
不要使用${}
此種方式容易出現SQL 注入HashMap
與Hashtable
做爲查詢結果集的輸出【說明:resultClass=」Hashtable」
,會置入字段名和屬性值,可是值的類型不可控】gmt_modified
字段值爲當前時間update table set c1=value1,c2=value2,c3=value3;
這是不對的。執行SQL時,不要更新無改動的字段,一是易出錯;二是效率低;三是增長binlog
存儲@Transactional
事務不要濫用。事務會影響數據庫的QPS
,另外使用事務的地方須要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等<isEqual>
中的compareValue是與屬性值對比的常量,通常是數字,表示相等時帶上此條件;<isNotEmpty>
表示不爲空且不爲null時執行;<isNotNull>
表示不爲null值時執行。在
DAO
層,產生的異常類型有不少,沒法用細粒度的異常進行catch,使用catch(Exception e)
方式,並throw new DAOException(e)
,不須要打印日誌,因
爲日誌在Manager/Service
層必定須要捕獲並打印到日誌文件中去,若是同臺服務器再打日誌,浪費性能和存儲。在Service
層出現異常時,必須記錄出錯日誌到磁盤,儘量帶上參數信息,至關於保護案發現場。若是Manager
層與Service
同機部署,日誌方式與DAO層處理一致,若是是單獨部署,則採用與Service
一致的處理方式。Web 層毫不應該繼續往上拋異常,由於已經處於頂層,若是意識到這個異常將致使頁面沒法正常渲染,那麼就應該直接跳轉到友好錯誤頁面,加上用戶容易理解的錯誤提示信息。開放接口層要將異常處理成錯誤碼和錯誤信息方式返回。
DO
(Data Object):此對象與數據庫表結構一一對應,經過DAO層向上傳輸數據源對象
DTO
(Data Transfer Object):數據傳輸對象,Service或Manager向外傳輸的對象
BO
(Business Object):業務對象,由Service層輸出的封裝業務邏輯的對象
AO
(Application Object):應用對象,在Web層與Service層之間抽象的複用對象模型,極爲貼近展現層,複用度不高
VO
(View Object):顯示層對象,一般是Web向模板渲染引擎層傳輸的對象
Query
:數據查詢對象,各層接收上層的查詢請求。注意超過 2 個參數的查詢封裝,禁止使用Map類來傳輸
1)
GroupID
格式:com.{公司/BU }.業務線 [.子業務線]
,最多4級。正例:com.taobao.jstorm
或com.alibaba.dubbo.register
2)ArtifactID
格式:產品線名-模塊名
。語義不重複不遺漏,先到中央倉庫去查證一下。正例:dubbo-client / fastjson-api / jstorm-tool
3)Version
:主版本號.次版本號.修訂號。【1.主版本號:產品方向改變,或者大規模 API 不兼容,或者架構不兼容升級;2.次版本號:保持相對兼容性,增長主要功能特性,影響範圍極小的 API 不兼容修改;3.修訂號:保持徹底兼容性,修復 BUG、新增次要功能特性等】
說明:注意起始版本號必須爲:1.0.0,而不是0.0.1,正式發佈的類庫必須先去中央倉庫進行查證,使版本號有延續性,正式版本號不容許覆蓋升級。如當前版本:1.3.3,那麼下一個合理的版本號:1.3.4或1.4.0或2.0.0
<dependencies>
語句塊中,全部版本仲裁放在<dependencyManagement>
語句塊中【說明:<dependencyManagement>
裏只是聲明版本,並不實現引入,所以子項目須要顯式的聲明依賴,version
和scope
都讀取自父pom。而<dependencies>
全部聲明在主pom的<dependencies>
裏的依賴都會自動引入,並默認被全部的子項目繼承】1)精簡可控原則。移除一切沒必要要的API和依賴,只包含Service API、必要的領域模型對象、Utils類、常量、枚舉等
2)穩定可追溯原則。每一個版本的變化應該被記錄,二方庫由誰維護,源碼在哪裏,都須要能方便查到
TCP
協議的time_wait
超時時間【操做系統默認240
秒後,纔會關閉處於time_wait
狀態的鏈接,在高併發訪問下,服務器端會由於處於time_wait
的鏈接數太多,可能沒法創建新的鏈接,因此須要在服務器上調小此等待值】正例:在linux服務器上請經過變動
/etc/sysctl.conf
文件去修改該缺省值(秒):net.ipv4.tcp_fin_timeout = 30
File Descriptor
,簡寫爲 fd)【說明:主流操做系統的設計是將TCP/UDP
鏈接採用與文件同樣的方式去管理,即一個鏈接對應於一個fd。主流的linux服務器默認所支持最大fd數量爲1024
,當併發鏈接數很大時很容易由於fd不足而出現「open too many files」
錯誤,致使新的鏈接沒法創建。建議將linux服務器所支持的最大句柄數調高數倍(與服務器的內存數量相關)】-XX:+HeapDumpOnOutOfMemoryError
參數,讓JVM
碰到OOM
場景時輸出dump
信息【說明:OOM的發生是有機率的,甚至相隔數月纔出現一例,出錯時的堆內信息對解決問題很是有幫助】以上內容即是我一邊讀,一邊思考整理出來的。《Java開發手冊》是實踐開發中提煉出來的精華
,當中有不少小點都是值得拿出來仔細推敲,每每小的規約背後隱藏着大學問。或許你和我同樣,有些地方並不熟悉或者暫時並不理解爲何這樣規約,我的以爲這些困惑的點讀一遍、思考一遍遠遠不夠,應該收藏下來,有空的時候多讀、多思考。若是開發中遇到了手冊中相似的場景,那麼收穫會更大,理解會更深。最後,正如手冊中提到的願景,「碼出高效,碼出質量」,但願你我都能碼出一個新的高度。