Java開發最佳實踐(二) ——《Java開發手冊》之"異常處理、MySQL 數據庫"

2、異常日誌

(一) 異常處理

  • Java類庫中定義的能夠經過預檢查方式規避的RuntimeException異常不該該經過catch的方式來處理,好比:NullPointerExceptionIndexOutOfBoundsException等等【說明:沒法經過預檢查的異常除外,好比,在解析字符串形式的數字時,可能存在數字格式錯誤,不得不經過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);
  • 全部日誌文件至少保存15天,由於有些異常具有以「周」爲頻次發生的特色。網絡運行狀態、安全相關信息、系統監測、管理後臺操做、用戶敏感操做須要留存相關的網絡日誌很多於6個月
  • 應用中的擴展日誌(如打點、臨時監控、訪問日誌等)命名方式:appName_logType_logName.log。logType:日誌類型,如stats/monitor/access等;logName:日誌描述。這種命名的好處:經過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找【說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於經過日誌對系統進行及時監控。正例:force-web應用中單獨監控時區轉換異常,如:force_web_timeZoneConvert.log
  • 在日誌輸出時,字符串變量之間的拼接使用佔位符的方式【說明:由於String字符串的拼接會使用StringBuilderappend()方式,有必定的性能損耗。使用佔位符僅是替換動做,能夠有效提高性能。正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
  • 對於trace/debug/info級別的日誌輸出,必須進行日誌級別的開關判斷【說明:雖然在debug(參數)的方法體內第一行代碼isDisabled(Level.DEBUG_INT)爲真時(Slf4j的常見實現Log4jLogback),就直接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級別只記錄系統邏輯出錯、異常或者重要的錯誤信息】
  • 儘可能用英文來描述日誌錯誤信息,若是日誌中的錯誤信息用英文描述不清楚的話使用中文描述便可,不然容易產生歧義【國際化團隊或海外部署的服務器因爲字符集問題,使用全英文來註釋和描述日誌錯誤信息。】

3、單元測試

  • 好的單元測試必須遵照AIR原則【說明:單元測試在線上運行時,感受像空氣(AIR)同樣並不存在,但在測試質量的保障上,倒是很是關鍵的。好的單元測試宏觀上來講,具備自動化、獨立性、可重複執行的特色。A:Automatic(自動化)I:Independent(獨立性)R:Repeatable(可重複)】
  • 單元測試應該是全自動執行的,而且非交互式的。測試用例一般是被按期執行的,執行過程必須徹底自動化纔有意義。輸出結果須要人工檢查的測試不是一個好的單元測試。單元測試中不許使用System.out來進行人肉驗證,必須使用assert來驗證
  • 保持單元測試的獨立性。爲了保證單元測試穩定可靠且便於維護,單元測試用例之間決不能互相調用,也不能依賴執行的前後次序【反例:method2須要依賴method1的執行,將執行結果做爲method2的輸入】
  • 單元測試是能夠重複執行的,不能受到外界環境的影響。【說明:單元測試一般會被放到持續集成中,每次有代碼check in時單元測試都會被執行。若是單測對外部環境(網絡、服務、中間件等)有依賴,容易致使持續集成機制的不可用。正例:爲了避免受外界環境影響,要求設計代碼時就把SUT的依賴改爲注入,在測試時用spring這樣的DI框架注入一個本地(內存)實現或者Mock實現】
  • 對於單元測試,要保證測試粒度足夠小,有助於精肯定位問題。單測粒度至可能是類級別,通常是方法級別【說明:只有測試粒度小才能在出錯時儘快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,那是集成測試的領域】
  • 核心業務、核心應用、核心模塊的增量代碼確保單元測試經過【說明:新增代碼及時補充單元測試,若是新增代碼影響了原有單元測試,請及時修正】
  • 單元測試代碼必須寫在以下工程目錄:src/test/java,不容許寫在業務代碼目錄下【說明:源碼編譯時會跳過此目錄,而單元測試框架默認是掃描此目錄】
  • 單元測試的基本目標:語句覆蓋率達到70%;核心模塊的語句覆蓋率和分支覆蓋率都要達到100%【說明:在工程規約的應用分層中提到的DAO層,Manager層,可重用度高的Service,都應該進行單元測試。】
  • 編寫單元測試代碼遵照BCDE原則,以保證被測試模塊的交付質量【B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等; C:Correct,正確的輸入,並獲得預期的結果; D:Design,與設計文檔相結合,來編寫單元測試; E:Error,強制錯誤信息輸入(如:非法數據、異常流程、業務容許外等),並獲得預期的結果】
  • 對於數據庫相關的查詢,更新,刪除等操做,不能假設數據庫裏的數據是存在的,或者直接操做數據庫把數據插入進去,請使用程序插入或者導入數據的方式來準備數據【反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增長一行做爲刪除目標,可是這一行新增數據並不符合業務插入規則,致使測試結果異常】
  • 和數據庫相關的單元測試,能夠設定自動回滾機制,不給數據庫形成髒數據。或者對單元測試產生的數據有明確的先後綴標識
  • 對於不可測的代碼在適當的時機作必要的重構,使代碼變得可測,避免爲了達到測試要求而書寫不規範測試代碼
  • 在設計評審階段,開發人員須要和測試人員一塊兒肯定單元測試範圍,單元測試最好覆蓋全部測試用例
  • 單元測試做爲一種質量保障手段,在項目提測前完成單元測試,不建議項目發佈後補充單元測試用例
  • 爲了更方便地進行單元測試,業務代碼應避免如下狀況【構造方法中作的事情過多;存在過多的全局變量和靜態方法;存在過多的外部依賴;存在過多的條件語句;說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構】
  • 不要對單元測試存在以下誤解【那是測試同窗乾的事情。1.本文是開發手冊,凡是本文內容都是與開發同窗強相關的;2.單元測試代碼是多餘的。系統的總體功能與各單元部件的測試正常與否是強相關的;3.單元測試代碼不須要維護。一年半載後,那麼單元測試幾乎處於廢棄狀態;4.單元測試與線上故障沒有辯證關係。好的單元測試可以最大限度地規避線上故障】

4、安全規約

  • 隸屬於用戶我的的頁面或者功能必須進行權限控制校驗【說明:防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單】
  • 用戶敏感數據禁止直接展現,必須對展現數據進行脫敏【說明:中國大陸我的手機號碼顯示爲:137****0969,隱藏中間4位,防止隱私泄露】
  • 用戶輸入的SQL、參數嚴格使用參數綁定或者 METADATA 字段值限定,防止SQL 注入,禁止字符串拼接SQL訪問數據庫
  • 用戶請求傳入的任何參數必須作有效性驗證【說明:忽略參數校驗可能致使:1.page size過大致使內存溢出;2.惡意order by致使數據庫慢查詢;3.任意重定向;4.SQL注入;5.反序列化注入;6.正則輸入源串拒絕服務ReDoS;說明:Java代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,可是若是攻擊人員使用的是特殊構造的字符串來驗證,有可能致使死循環的結果。】
  • 禁止向HTML頁面輸出未經安全過濾或未正確轉義的用戶數據
  • 表單、AJAX提交必須執行CSRF安全驗證【說明:CSRF(Cross-site request forgery)跨站請求僞造是一類常見編程漏洞。對於存在CSRF漏洞的應用/網站,攻擊者能夠事先構造好URL,只要受害者用戶一訪問,後臺便在用戶不知情的狀況下對數據庫中用戶參數進行相應修改。】
  • 在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放的 機制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷而致使資損
  • 發貼、評論、發送即時消息等用戶生成內容的場景必須實現防刷、文本內容違禁詞過濾等風控策略

5、MySQL數據庫

(一) 建表規約

  • 表達是與否概念的字段,必須使用is_xxx的方式命名,數據類型是unsigned tinyint(1表示是,0表示否)【說明:任何字段若是爲非負數,必須是unsigned。注意:POJO類中的任何布爾類型的變量,都不要加is前綴,因此,須要在<resultMap>設置從is_xxxXxx的映射關係。數據庫表示是與否的值,使用tinyint類型,堅持is_xxx的命名方式是爲了明確其取值含義與取值範圍】

正例:表達邏輯刪除的字段名is_deleted,1表示刪除,0表示未刪除linux

  • 表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只出現數字。數據庫字段名的修改代價很大,由於沒法進行預發佈,因此字段名稱須要慎重考慮【說明:MySQL在 Windows下不區分大小寫,但在Linux下默認是區分大小寫。所以,數據庫名、表名、字段名,都不容許出現任何大寫字母,避免節外生枝】

正例:aliyun_adminrdc_configlevel3_name
反例:AliyunAdmin,rdcConfig,level_3_name程序員

  • 表名不使用複數名詞【表名應該僅僅表示表裏面的實體內容,不該該表示實體數量,對應於DO類名也是單數形式,符合表達習慣】
  • 禁用保留字,如desc、range、match、delayed等,請參考MySQL官方保留字
  • 主鍵索引名爲pk_字段名;惟一索引名爲uk_字段名;普通索引名則爲idx_字段名【說明:pk_primary keyuk_unique keyidx_index的簡稱】
  • 小數類型爲decimal,禁止使用floatdouble【說明:在存儲的時候,floatdouble都存在精度損失的問題,極可能在比較值的時候,獲得不正確的結果。若是存儲的數據範圍超過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
  • 庫名與應用名稱儘可能一致
  • 若是修改字段含義或對字段表示的狀態追加時,須要及時更新字段註釋
  • 字段容許適當冗餘,以提升查詢性能,但必須考慮數據一致【冗餘字段應遵循:1)不是頻繁修改的字段。2)不是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

  • 利用覆蓋索引來進行查詢操做,避免回表【說明:若是一本書須要知道第11章是什麼標題,會翻開第11章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的做用。】

正例:可以創建索引的種類分爲主鍵索引、惟一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用explain的結果,extra列會出現:using indexspring

  • 利用延遲關聯或者子查詢優化超多分頁場景【說明:MySQL並非跳過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.idsql

  • 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

(三) SQL語句

  • 不要使用count(列名)count(常量)來替代count(*)count(*)是SQL92定義的標準統計行數的語法,跟數據庫無關,跟NULL和非NULL無關【說明:count(*)會統計值爲NULL的行,而count(列名)不會統計此列爲NULL值的行】
  • count(distinct col)計算該列除NULL以外的不重複行數,注意count(distinct col1, col2)若是其中一列全爲 NULL,那麼即便另外一列有不一樣的值,也返回爲0
  • 當某一列的值全是NULL時,count(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更新,即爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度】
  • 禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性
  • 數據訂正(特別是刪除、修改記錄操做)時,要先select,避免出現誤刪除,確認無誤才能執行更新語句
  • in操做能避免則避免,若實在避免不了,須要仔細評估in後邊的集合元素數量,控制在1000個以內
  • 若是有國際化須要,全部的字符存儲與表示,均以utf-8編碼,注意字符統計函數的區別。【說明:SELECT LENGTH("輕鬆工做");返回爲12;SELECT CHARACTER_LENGTH("輕鬆工做"); 返回爲4;若是須要存儲表情,那麼選擇utf8mb4來進行存儲,注意它與utf-8編碼的區別。】
  • TRUNCATE TABLEDELETE速度快,且使用的系統和事務日誌資源少,但TRUNCATE無事務且不觸發trigger,有可能形成事故,故不建議在開發代碼中使用此語句【說明:TRUNCATE TABLE在功能上與不帶WHERE子句的DELETE語句相同】

(四) ORM映射

  • 在表查詢中,一概不要使用*做爲查詢的字段列表,須要哪些字段必須明確寫明【說明:1)增長查詢分析器解析成本。2)增減字段容易與resultMap配置不一致。3)無用字段增長網絡消耗,尤爲是text類型的字段】
  • POJO類的布爾屬性不能加is,而數據庫字段必須加is_,要求在resultMap中進行字段與屬性之間的映射。【說明:參見定義POJO類以及數據庫字段定義規定,在<resultMap>中增長映射,是必須的。在MyBatis Generator生成的代碼中,須要進行對應的修改】
  • 不要用resultClass當返回參數,即便全部類屬性名與數據庫字段一一對應,也須要定義;反過來,每個表也必然有一個POJO類與之對應【說明:配置映射關係,使字段與DO類解耦,方便維護。】
  • sql.xml配置參數使用:#{},#param#不要使用${}此種方式容易出現SQL 注入
  • 不容許直接拿HashMapHashtable做爲查詢結果集的輸出【說明:resultClass=」Hashtable」,會置入字段名和屬性值,可是值的類型不可控】
  • 更新數據表記錄時,必須同時更新記錄對應的gmt_modified字段值爲當前時間
  • 不要寫一個大而全的數據更新接口。傳入爲POJO類,無論是否是本身的目標更新字段,都進行update table set c1=value1,c2=value2,c3=value3; 這是不對的。執行SQL時,不要更新無改動的字段,一是易出錯;二是效率低;三是增長binlog存儲
  • @Transactional事務不要濫用。事務會影響數據庫的QPS,另外使用事務的地方須要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等
  • <isEqual>中的compareValue是與屬性值對比的常量,通常是數字,表示相等時帶上此條件;<isNotEmpty>表示不爲空且不爲null時執行;<isNotNull>表示不爲null值時執行。

6、工程結構

  • 分層異常處理規約

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.jstormcom.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

  • 底層基礎技術框架、核心數據管理平臺、或近硬件端系統謹慎引入第三方實現
  • 全部pom文件中的依賴聲明放在<dependencies>語句塊中,全部版本仲裁放在<dependencyManagement>語句塊中【說明:<dependencyManagement>裏只是聲明版本,並不實現引入,所以子項目須要顯式的聲明依賴,versionscope都讀取自父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服務器所支持的最大句柄數調高數倍(與服務器的內存數量相關)】
  • 給JVM環境參數設置-XX:+HeapDumpOnOutOfMemoryError參數,讓JVM碰到
    OOM場景時輸出dump信息【說明:OOM的發生是有機率的,甚至相隔數月纔出現一例,出錯時的堆內信息對解決問題很是有幫助】
  • 在線上生產環境,JVM的Xms和Xmx設置同樣大小的內存容量,避免在GC後調整堆大小帶來的壓力

7、設計規約

  • 存儲方案和數據結構須要認真地進行設計和評審
  • 需求分析與系統設計在考慮主幹功能的同時,須要充分評估異常流程與業務邊界
  • 謹慎使用繼承的方式來進行擴展,優先使用聚合/組合的方式來實現
  • 系統設計主要目的是明確需求、理順邏輯、後期維護,次要目的用於指導編碼。
  • 設計的本質就是識別和表達系統難點,找到系統的變化點,並隔離變化點
  • 系統架構設計的目的【1.肯定系統邊界。肯定系統在技術層面上的作與不作;2.肯定系統內模塊之間的關係。肯定模塊之間的依賴關係及模塊的宏觀輸入與輸出; 3.肯定指導後續設計與演化的原則。使後續的子系統或模塊設計在規定的框架內繼續演化;4.肯定非功能性需求。非功能性需求是指安全性、可用性、可擴展性等】

我的小結

以上內容即是我一邊讀,一邊思考整理出來的。《Java開發手冊》是實踐開發中提煉出來的精華,當中有不少小點都是值得拿出來仔細推敲,每每小的規約背後隱藏着大學問。或許你和我同樣,有些地方並不熟悉或者暫時並不理解爲何這樣規約,我的以爲這些困惑的點讀一遍、思考一遍遠遠不夠,應該收藏下來,有空的時候多讀、多思考。若是開發中遇到了手冊中相似的場景,那麼收穫會更大,理解會更深。最後,正如手冊中提到的願景,「碼出高效,碼出質量」,但願你我都能碼出一個新的高度。

相關文章
相關標籤/搜索