談談ali與Google的Java開發規範

  無規矩不成方圓,編碼規範就如同協議,有了Http、TCP等各類協議,計算機之間纔能有效地通訊,一樣的,有了一致的編碼規範,程序員之間纔能有效地合做。道理你們都懂,可現實中的咱們,常常一邊吐槽別人的代碼,一邊寫着被吐槽的代碼,究其根本,就是缺少聽從編碼規範的意識!多年前,Google發佈Google Java Style來定義Java編碼時應遵循的規範;今年年初阿里則發佈阿里巴巴Java 開發手冊,並隨後迭代了多個版本,直至9月份又發佈了pdf終極版。這兩大互聯網巨頭的初衷,都是但願可以統一標準,使業界編碼達到一致性,提高溝通和研發效率,這對於咱們碼農無疑是很讚的一筆福利呀。筆者將兩份規範都通讀了一遍,其中列舉的很多細則跟平時的編碼習慣基本是符合的,不過仍是有很多新奇的收穫,忍不住記錄在此,供往後念念不忘~html

Java開發規範總覽

1、Google Java Style

  Google的java開發規範主要分爲6大部分:源文件基本規範、源文件結構、代碼格式、命名、編程實踐和Javadoc,各部分概要以下:java

一、源文件基本規範(source file basics):文件名、文件編碼、特殊字符的規範要求 二、源文件結構(source file structure):版權許可信息、package、import、類申明的規約 三、代碼格式(formatting):大括號、縮進、換行、列長限制、空格、括號、枚舉、數組、switch語句、注四、解、註釋、和修飾符等格式要求 五、命名(Naming):標識符、包名、類名、方法名、常量名、很是量成員名、參數名、局部變量的命名規範 六、編程實踐(Programming Practices):@override、異常捕獲、靜態成員、Finalizers等用法規約程序員

2、阿里巴巴Java開發手冊

  阿里的Java開發手冊相對於前者更上一層樓,它除了基本的編程風格的規約外,還給出了日誌、單元測試、安全、MySQL、工程結構等代碼以外的規約,聽說是阿里近萬名開發同窗集體智慧的結晶,至關了得,仍是挺值得借鑑一下的。各部分概要以下:算法

一、編程規約:命名風格、常量、代碼格式、OOP、集合處理、併發、控制語句、註釋等 二、異常日誌:異常處理、日誌的命名、保留時間、輸出級別、記錄信息等 三、單元測試:AIR原則(Automatic,Independent,Repeatable)、單側的代碼目錄、目標,單側的寫法,即BCDE原則(Border,Correct,Design,Error) 四、安全規約:權限校驗、數據脫敏、參數有效校驗、CSRF安全過濾、防重放限制、風控策略等 五、MySQL數據庫:建表、索引、SQL語句、ORM映射等 六、工程結構:應用分層、二方庫依賴(座標命名、接口約定、pom配置)、服務器端各項配置(TCP超時、句柄數、JVM參數等)spring

熟知的規範

  對於你們已經爛熟於心並已習慣遵照的一些編碼規範,好比類名、常量的命名、數組的定義、Long類型的字面等,就不在此一一列出了,只想就一些平時編碼中較容易個性化,並可能會存在爭議的規範進行一番探討。爲了便於說明,用G表示規範出自於Google Java StyleA表示規範出自於阿里巴巴Java開發手冊shell

[A]IDE的text file encoding設置爲UTF-8;IDE中文件的換行符使用Unix格式,不要使用Windows格式([G]文件編碼:UTF-8)數據庫

  看似簡單的一個編碼約定,在實際開發過程當中卻常常出現不一致,因爲咱們是中文操做系統,系統編碼是GBK。當兩個協做的開發人員IDE,一個採用系統默認編碼,一個設置爲UTF-8,那麼二人看對方寫的中文註釋就各自都是亂碼了,很尷尬。對於「換行符使用Unix格式」,這個在編寫shell和hive腳本時踩過好幾回坑,並且錯誤提示很隱晦,一時半會還真察覺不出來,只能說這個規範請務必遵照!編程

[A]代碼中的命名嚴禁使用拼音與英文混合的方式,更不容許直接使用中文的方式。設計模式

  大多數程序員仍是都會聽從英文的命名方式,但在實際工做中還真有遇到過拼音與英文混用的命名,好比建立報文的函數命名爲createBaowen,看起來怪怪的,有點不三不四。api

[A]抽象類命名使用Abstract或Base開頭;異常類使用Exception結尾;測試類以它要測試的類的名稱開始,以Test結尾

  以spring源碼爲例,其抽象類都是以Abstract開頭,異常類以Exception結尾,測試類則是以Tests結尾。

[A]POJO類中布爾類型的變量,都不要加is,不然部分框架解析會引發序列化錯誤。

  這個問題一說你們都知道,但實際倒是很容易被忽視!由於Boolean一般表達「是」或「否」的意思,可能一遇到布爾變量,你們會習慣性地將它與is關聯起來,「很天然」地就會以is開頭定義變量。但筆者想說的是,這其實反應了至少兩個問題:一、對JavaBean屬性命名規範不熟;二、對框架解析POJO的原理不熟,如RPC反向解析、spring MVC參數綁定、MyBatis處理映射等。

private boolean isActive;
//lombok、Eclipse生成getter、setter的結果以下,框架會誤把變量解析成active
public boolean isActive() {
  return isActive;
}
public void setActive(boolean isActive) {
  this.isActive = isActive;
}

  在搞清這兩個問題前,仍是建議老老實實按規範來吧。

包名統一使用小寫,點分隔符之間有且僅有一個天然語義的英語單詞。包名統一使用單數形式,類名如有複數含義,則可以使用複數形式。

  實際工做中看到過包名包含下劃線的,如org.sherlockyb.user_manage.dao,仍是有必要統一一下。

[A]不容許任何魔法值(即未經定義的常量)直接出如今代碼中。 反例:String key = "Id#taobao_" + tradeId; ​ cache.put(key, value);

  避免硬編碼問題是每一個程序員都應該具有的基本素養,硬編碼所帶來的可讀性差、維護困難等問題,衆所周知。

[A,G]採用空格縮進,禁止使用tab字符。

  這是Google和ali一致的規約,只不過前者是一個tab對應2個空格,後者則是4個空格。之因此不提倡tab鍵,是由於不一樣的IDE對tab鍵的「翻譯」默認有所差別,容易因不一樣程序員的個性化而致使同一份代碼的格式混亂。

[A,G]單行字符數限制不超過120/100個字符,超出須要換行,換行時遵循以下規則: 1)[A,G]第二行相對於第一行縮進4個空格,從第三行開始,再也不繼續縮進。 2)[A]運算符或方法調用的點符號與下文一塊兒換行([G]如果非賦值運算符,則在該符號前斷開;如果賦值運算符foreach中的分號,則在該符號後斷開)。 4)[A]方法調用時,多個參數,須要換行時,在逗號後進行([G]逗號與前面的內容留在同一行)。 5)在括號前不要換行。

  對於單行字符限制,阿里的是120,Google的是100。我的以爲120略長,特別是當用筆記本碼代碼時,對於超限的代碼行,常常要用橫向滾動條,不太友好,我的推薦100的限制。

沒有必要增長若干空格來使某一行的字符與上一行對應位置的字符對齊。

  在變量較多時,這種對齊是一種累贅。雖然說有IDE的自動格式化功能,但多人協做時,難保各自的格式化沒有差別,會因格式變化而形成沒必要要的代碼行改動,無疑會給你的代碼合併徒增困擾。

方法體內的執行語句組、變量的定義語句組、不一樣的業務邏輯之間或者不一樣的語義之間插入一個空行。相同業務邏輯和語義之間不須要插入空行。

  代碼分塊就如同文章分段,整潔的代碼具備更強的自解釋性。

外部正在調用或者二方庫依賴的接口,不容許修改方法簽名,避免對接口調用方產生影響。做爲提供方,接口過期必須加@Deprecated註解,並清晰地說明採用的新接口或者新服務是什麼;做爲調用方,有義務去考證過期方法的新實現是什麼。

  接口契約,是使用方和調用方良好協做的有效保障,請務必遵照。

全部的相同類型的包裝類對象之間值的比較,所有用equals方法比較。 說明:對於Integer var = ?在**-128至127**範圍內的賦值,Integer對象是在IntegerCache.cache產生,會複用已有對象,這個區間內的Integer值能夠直接使用==進行判斷,可是這個區間以外的全部數據,都會在堆上產生,並不會複用已有對象,這是個大坑,推薦使用equals方法進行判斷。

  這裏補充幾點,除了Integer,其餘包裝類型如Long、Byte等都有各自的cache。這裏只提到了等值比較,對於>,<等非等值比較,不必手動拆箱去比較,包裝類型之間直接能夠比較大小,親測有效。例如:

Long a = new Long(1000L);
Long b = new Long(222L);
Long c = new Long(2000L);
Assert.isTrue(a > b && a < c);  //斷言成功

[A]關於基本數據類型與包裝數據類型的使用標準以下: 1)全部的POJO類屬性必須使用包裝數據類型。 2)RPC方法的返回值和參數必須使用包裝數據類型。 3)全部的局部變量使用基本數據類型。 說明:POJO類屬性沒有初值是提醒使用者在須要使用時,必須本身顯式地進行賦值,任何NPE問題,或者入口檢查,都由使用者來保證

  基本類型做爲入參和返回值有多種弊病,如不情願的默認值,NPE風險等,除了局部變量,其餘慎用。

序列化類新增屬性時,請不要修改serialVersionUID字段,避免反序列化失敗;若是徹底不兼容升級,避免反序列化混亂,那麼請修改serialVersionUID值。

  serialVersionUID是Java爲每一個序列化類產生的版本標識:版本相同,相互之間則可序列化和反序列化;版本不一樣,反序列化時會拋出InvalidClassException。因不一樣的jdk編譯極可能會生成不一樣的serialVersionUID默認值,一般須要顯式指定,如1L。

[A]final能夠聲明類、成員變量、方法、以及本地變量,下列狀況使用final關鍵字: 1)不容許被繼承的類,如:String類。 2)不容許修改引用的域對象,如:POJO類的域變量。 3)不容許被重寫的方法,如:POJO類的setter方法。 4)不容許運行過程當中從新賦值的局部變量,如傳遞給匿名內部類的局部變量。

  final關鍵字有諸多好處,好比JVM和Java應用都會緩存final變量,以提升性能;final變量可在多線程環境下放心共享,無需額外的同步開銷;JVM會對final修飾的方法、變量及類進行優化等,詳情可見深刻理解Java中的final關鍵字

慎用Object的clone方法來拷貝對象。 說明:對象的clone方法默認是淺拷貝,特別是引用類型成員。若想實現深拷貝,須要重寫clone方法實現屬性對象的拷貝。

  Java中的賦值操做都是值傳遞,好比咱們經常使用來「複製」DTO的工具,不管是spring的BeanUtils.copyProperties,仍是Apache commons的BeanUtils.cloneBean,實際上也只是兩個DTO之間成員的引用複製,成員指向的對象仍是同一個,用到此類工具的時候要有這個意識,否則容易踩坑。

[A]類成員與方法訪問控制從嚴: 1)若是不容許外部直接經過new來建立對象,那麼構造方法必須是private。 2)工具類不容許有public或default構造方法。 3)類非static成員變量而且與子類共享,必須是protected。 4)類非static成員變量而且僅在本類使用,必須是private。 5)類static成員變量若是僅在本類使用,必須是private。 6)如果static成員變量,必須考慮是否爲final。 7)類成員方法只供類內部調用,必須是private。 8)類成員方法只對繼承類公開,那麼限制爲protected。 說明:任何類、方法、參數、變量,嚴控訪問範圍。過於寬泛的訪問範圍,不利於模塊解耦。

  最小權限原則(Principal of least privilege,POLP)是每一個程序員應遵照的,可有效避免數據以及功能受到錯誤或惡意行爲的破壞。

[A]ArrayList的subList結果不可強轉成ArrayList,不然會拋出ClassCastException異常。

  這裏補充一點,SubList並未實現Serializable接口,若RPC接口的List類型參數接受了SubList類型的實參,則在RPC調用時會報出序列化異常。好比咱們經常使用的guava中的Lists.partition,切分後的子list實際都是SubList類型,在傳給RPC接口以前,須要用**new ArrayList()**包一層,不然會報序列化異常。

[A]在subList場景中,高度注意對原集合元素個數的修改,會致使子列表的遍歷、增長、刪除均會產生ConcurrentModificationException異常。

  這個仍是得從源碼的角度來解釋。SubList在構造時實際是直接持有了原list的引用,其add、remove等操做實際都是對原list的操做,咱們不妨以add爲例:

public void add(int index, E element) {
  rangeCheckForAdd(index);
  checkForComodification();        // 檢查this.modCount與原list的modCount是否一致
  l.add(index+offset, element);    // 原list新增了一個元素
  this.modCount = l.modCount;      // 將原list更新後的modCount同步到this.modCount
  size++;
}

  能夠看出,SubList生成以後,經過SubList進行add、remove等操做時,modCount會同步更新,因此沒問題;而若是此後還對原list進行add、remove等操做,SubList是感知不到modCount的變化的,會形成modCount不一致,從而報出ConcurrentModificationException異常。故一般來說,從原list取了SubList以後,是不建議再對原list作結構上的修改的

[A]使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear方法會拋出UnsupportedOperationException異常。

  相似的,guava的Maps.toMap方法,返回的是一個ImmutableMap,是不可變的,不能對其調用add、remove等操做,使用時應該有這個意識!

在JDK7版本及版本以上,Comparator必須知足:1)x,y比較結果和y,x比較結果相反;2)x>y,y>z,則x>z;3)x=y,則x,z比較結果和y,z比較結果相同。否則Arrays.sort,Collections.sort會報IllegalArgumentException異常。

  JDK從1.6升到1.7以後,默認排序算法由MergeSort變爲TimSort,對於任意兩個比較元素x、y,其Comparator結果必定要是肯定的,特別是對於x=y的狀況,肯定返回0,不然可能出現Comparison method violates its general contract!錯誤。

[A]線程池不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors返回的線程池對象的弊端以下: 1)FixedThreadPoolSingleThreadPool:容許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而致使OOM。 2)CachedThreadPoolScheduledThreadLocal:容許的建立線程數爲Integer.MAX_VALUE,可能會建立大量的線程,從而致使OOM。

  如今通常不多會用Executors去建立線程池了,一般會使用spring的ThreadPoolExecutorFactoryBean或者guava的MoreExecutors.listeningDecorator對前者包裝一下,對於像線程數、隊列大小等都是經過配置來設定。

[A]高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。

  一句話歸納就是,能不鎖就不鎖,即使鎖,也儘可能使鎖的粒度最小化。

[A]表達異常分支時,少用if-else方式,可以使用衛語句代替。對於if()...else if()...else...方式,請勿超過3層。對於超過的,可以使用衛語句、策略模式、狀態模式等來實現。

if(condition) {
  ...
  return obj;
}
// 接着寫else的業務邏輯代碼;

  冗長的if-else可讀性差,維護困難,推薦使用衛語句,邏輯清晰明瞭。

[A]代碼修改的同時,註釋也作同步修改,尤爲是參數、返回值、異常、核心邏輯等的修改。

  這個在實際工程代碼中還真看到過很多,代碼與註釋牛頭不對馬嘴,儘可能別留坑給後來者,應該算在程序猿的基本素養以內吧。

謹慎註釋掉代碼。在上方詳細說明,而不是簡單的註釋掉。若是無用,則刪除。 說明:代碼被註釋掉有兩種可能:1)後續會恢復此段代碼邏輯。2)永久不用。前者若是沒有備註信息,難以知曉註釋動機。後者建議直接刪掉(代碼倉庫保存了歷史代碼)。

  這個就更無力吐槽了,比上一條更常見,so,這條規範強烈推薦!

1)對於註釋的要求:第1、能準確反映設計思想和代碼邏輯;第2、能描述業務含義,使別人能迅速瞭解到代碼背後的信息;第3、好的命名、代碼結構是自解釋性的,註釋力求精簡準確、表達到位。避免過多過濫的註釋。 2)finally塊必須對資源對象、流對象進行關閉,有異常也要作try-catch。如果JDK7及以上,可以使用try-with-resources。不能再finally塊中使用return,finally塊中的return返回後方法結束執行,不會再執行try塊中的return語句。 3)防止NPE,是程序員的基本素養,注意NPE產生的場景:   1.返回類型爲基本數據類型,return包裝數據類型的對象時,自動拆箱有可能產生NPE   2.數據庫的查詢結果可能爲null。   3.遠程調用返回對象時,一概要求進行空指針判斷,防止NPE。   4.對於Session中獲取的數據,建議NPE檢查,避免空指針。   5.級聯調用obj.getA().getB().getC();一連串調用,易產生NPE。正例:使用JDK8的Optional類來防止NPE問題。 4)在代碼中使用「拋異常」仍是「返回錯誤碼」,對於公司外的http/api開放接口必須使用「錯誤碼」;而應用內部推薦異常拋出;跨應用間RPC調用優先考慮使用Result方式,封裝isSuccess()方法、「錯誤碼」、「錯誤簡短信息」。 5)避免出現重複的代碼(Don't Repeat Yourself),即DRY原則。

  以上幾條,皆是毫無爭議的基本規範,且行且遵照。

1)日誌文件推薦至少保存15天,由於有些異常具有以「周」爲頻次發生的特色。 2)對trace/debug/info級別的日誌輸出,必須使用條件輸出形式或者使用佔位符的方式。以免沒必要要的字符串拼接,浪費系統資源。 3)避免重複打印日誌,浪費磁盤空間,對於特定包的日誌,務必設置additivity=false。 4)異常信息應該包括兩類信息:案發現場信息異常堆棧信息。若是不處理,則經過關鍵字throws往上拋。

  關於日誌的幾條不錯的規範。日誌做爲服務器行爲的平常軌跡,對於統計分析、故障排錯意義巨大,要慎重對待纔是。

1)好的單元測試必須遵照AIR原則。   A:Automatic(自動化)。全自動執行,非交互式的。使用assert驗證,而非System.out。   I:Independent(獨立性)。單側用例之間不能產生依賴,互相獨立。   R:Repeatable(可重複)。可重複執行,不能受到外界環境的影響。對於外部依賴,經過spring等DI框架注入一個本地(內存)實現或者Mock實現。 2)單元測試的基本目標:語句覆蓋率達到70%;核心模塊的語句覆蓋率和分支覆蓋率都要達到100%。 3)編寫單元測試代碼遵照BCDE原則:   B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。   C:Correct,正確的輸入,並獲得預期的結果。   D:Design,與設計文檔相結合,來編寫單元測試。   E:Error,強制錯誤信息輸入(如:非法數據、異常流程、非業務容許輸入等),並獲得預期結果。

  關於單元測試的幾條不錯的規範。單元測試是代碼質量的有效保障!太多的想固然、自覺得是,每每會跳過單測,最終自作自受。曾經的筆者也犯過相似毛病,還好及時糾正。

新奇的收穫

  這裏將列出一些筆者以爲有新收穫的規範,有的是平時編碼過程當中沒有嚴格遵照的,好比switch中default偶爾加偶爾不加;有的則是目前還不太清楚的規範。

[A]杜絕徹底不規範的縮寫,避免望文不知義。 反例:AbstractClass的「縮寫」命名成AbsClass;condition的「縮寫」命名成condi,此類隨意縮寫嚴重下降了代碼的可閱讀性。

  說來慚愧,這類不規範的縮寫,筆者以前還真幹過幾回。有時候是覺着變量太長,致使明明邏輯很簡單的一條語句,就超過了列限制,因而乎主觀地縮寫命名,如mergedRegionReportDtos縮寫爲mRegReportDtos,accountIdToHourReportDtos縮寫爲accountIdToHrDtos,至關混亂有木有!因此,若是對英文單詞的縮寫拿不定的話,仍是直接用原單詞吧,長點就長點,可讀性很重要。

[A]若是模塊、接口、類、方法使用了設計模式,在命名時體現出具體模式,有利於閱讀者快速理解架構設計理念。類示例:OrderFactoryLoginProxyResourceObserver

  沒啥好說的,一樣是爲了提高代碼的自解釋性。spring源碼中隨處可見這樣的命名風格:AbstractAutowireCapableBeanFactoryCglib2AopProxyBeanDefinitionParserDelegate

[A]接口類中的方法和屬性不要加任何修飾符號(public也不要加),保持代碼的簡潔性,並加上有效的Javadoc註釋。儘可能不要在接口裏定義變量,若是必定要定義變量,確定是與接口方法有關,而且是整個應用的基礎常量。 正例:接口方法簽名:void f(); ​ 接口基礎常量表示:String COMPANY = "alibaba"; 反例:接口方法定義:public abstract void f(); 說明:JDK8中接口容許有默認實現,那麼這個default方法,是對全部實現類都有價值的默認實現。

  目前所見過的組內代碼,有太多的接口中方法都是加了public,也許是後來的編碼者看到前任留下的已有方法都加了,爲了保持一致,因而乎也加了public。說到底仍是最初的良好規範沒有造成,致使給後來者以錯誤的指引!簡單纔是美,把public 去掉吧。

[A]接口的命名規則:若是是形容能力的接口名稱,取對應的形容詞作接口名(一般是-able的形式) 正例:AbstractTranslator實現Translatable

  Log4j中的AppenderAttachable,JDK中的AutoCloseable,Appendable等。

[A]各層命名規約: A)Service/DAO層方法命名前綴規約 ​ 1)獲取對象時,單個用get/多個用list;2)獲取統計值用count ​ 3)插入用save/insert;4)刪除用remove/delete;5)修改用update

  關於資源的CRUD,這塊的方法命名至關亂,太容易個性化了!至少目前組內代碼,要啥有啥:query與get並存,查詢列表和計數的都是get,並未作區分;一下子remove,一下子delete;既有save也有insert。當你Ctrl+O的時候,想找個count某元素的方法時賊費勁,急需統一!

[A]不要使用一個常量類維護全部常量,按常量功能進行歸類,分開維護。 說明:大而全的常量類,非得使用查找功能才能定位到修改的常量,不利於理解和維護。 正例:緩存相關常量放在類CacheConsts下,系統配置相關常量放在類ConfigConsts下。

[A]常量的複用層次有五層:跨應用共享常量、應用內共享常量、子工程內共享常量、包內共享常量、類內共享常量。 1)跨應用共享常量:放置在二方庫中,一般是client.jar中的constant目錄下。 2)應用內共享常量:放置在一方庫中,一般是modules中的constant目錄下。 3)子工程內共享常量:當前子工程的constant目錄下。 4)包內共享常量:當前包下單獨的constant目錄下。 5)類內共享常量:直接在類內部private static final定義。

  常量的維護也可運用設計模式思想,單一職責,分層,嚴格控制做用域,使常量更清晰,易於理解,便於維護。

[A]類內方法定義順序依次是:共有方法或保護方法 > 私有方法 > getter/setter方法。但有個規則特例:[A,G]當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一塊兒。即重載永不分離說明:共有方法是類的調用者和維護者最關係的方法,首屏展現最好;保護方法雖然只是子類關心,也多是「模板設計模式」下的核心方法;而私有方法外部通常不須要特別關心,是一個黑盒實現;由於承載的信息價值較低,全部Service和DAO的getter/setter方法放在類的最後。

  方法的排版要有秩序,這樣在咱們Ctrl+O的時候才能更方便的查閱方法列表。阿里的約定是比較通用的規則,對此,Google的見解則不一樣,它認爲類的成員順序不存在惟一的通用法則,重要的是,每一個類應該以維護者所能解釋的排序邏輯去排序它的成員。常見的反例:新的方法老是習慣性地添加到類的結尾,排序毫無心義。

[A]對多個資源、數據庫表、對象同時加鎖時,須要保持一致的加鎖順序,不然可能會形成死鎖。 說明:線程一須要對錶A、B、C依次所有加鎖後才能夠進行更新操做,那麼線程二的加鎖順序也必須是A、B、C,不然可能出現死鎖。

  從死鎖產生的條件出發來避免死鎖。好比咱們根據一批ids批量更新數據庫記錄時,預先對ids排序,也是一種能有效下降死鎖發生機率的措施。

[A]使用CountDownLatch進行異步轉同步操做,每一個線程退出前必須調用countDown方法,線程執行代碼注意catch異常,確保countDown方法被執行到,避免主線程沒法執行至await方法,直到超時才返回結果。

避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed致使的性能降低。 說明:Random實例包括java.util.Random的實例或者Math.random的方式。 正例:在JDK7以後,能夠直接使用API ThreadLocalRandom,而在JDK7以前,須要編碼保證每一個線程持有一個實例。

volatile關鍵字解決多線程內存不可見問題。對於一寫多讀,是能夠解決變量同步問題,可是若是多寫,一樣沒法解決線程安全問題。若是是count++操做,使用以下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 若是是JDK8,推薦使用LongAdder對象,比AtomicLong性能更好(減小樂觀鎖的重試次數)。

  volatile關鍵字只是保證了同一個變量在多線程中的可見性,更多的是用於修飾做爲開關狀態的變量。可是volatile只提供了內存可見性,而沒有提供原子性!volatile變量在每次被線程訪問時,都強迫從主內存中重讀該變量的值,而當該變量發生變化時,又會強迫線程將最近的值刷新到主內存,對於像boolean flag = true等原子性賦值操做是沒問題的,但volatile不能保證複合操做的原子性,如count++

[A]除經常使用方法(如getXxx/isXxx)等外,不要在條件判斷中執行其餘複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。

  這個筆者以前確實有過這樣的壞習慣,爲了省略一條賦值語句,將if中的條件搞得比較複雜,代碼冗長,可讀性也差,得不償失。

[A]參數校驗與否: 須要校驗的:1)對外提供的開發接口,無論是RPC/API/HTTP接口;2)敏感權限入口;3)須要極高穩定性和可用性的方法 不需校驗的:1)極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。2)底層調用頻度較高的方法。如通常Service會作參數校驗,到了DAO層,參數校驗可省略。3)被聲明爲private只會被本身代碼所調用的方法,若是能肯定傳入參數已作過檢查或者確定不會有問題,此時可不校驗參數。

  過多的參數校驗,不只是冗餘代碼,並且還影響性能,只在必要的時候作校驗。

1)隸屬於用戶我的的頁面或功能必須進行權限控制校驗。說明:防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據。 2)用戶請求傳入的任何參數必須作有效性校驗。忽略參數校驗可能致使:1)page size過大致使內存溢出;2)惡意order by致使數據庫慢查詢;3)任意重定向;4)SQL注入;5)反序列化注入;6)正則輸入源串拒絕服務ReDos 3)表單、AJAX提交必須執行CSRF(Cross-site request forgery)安全過濾 4)在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放機制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷,資損。 5)發帖、評論、發送即時消息等用戶生成內容的場景必須實現防刷、文本內容違禁詞過濾等風控策略。

  基本的安全意識仍是要有的,一旦踩了坑,後果不堪設想。

1)數據庫表達是與否概念的字段,必須使用is_xxx的方式命名,數據類型是unsigned tinyint(1表示是,0表示否)。 2)禁用保留字,如desc、range、match、delayed等,參考MySQL官方保留字。 3)主鍵索引名爲pk_字段名;惟一索引名爲uk_字段名;普通索引名爲idx_字段名。 4)varchar是可變長字符串,不預先分配存儲空間,長度不要超過5000,若是大於此值,則選用text,獨立出來一張表,用主鍵來對應,避免影響其餘字段索引效率。 5)字段容許適當冗餘,以提升查詢性能,但必須考慮數據一致性。冗餘字段應遵照:1.不是頻繁修改;2.不是varchar超長字段,更不能是text字段。 6)單錶行數超過500萬行或者單表容量超過2GB,才推薦分庫分表。 7)頁面搜索嚴禁左模糊或者全模糊,若是須要請走搜索引擎來解決。 8)如有order by的場景,請注意利用索引的有序性。order by最後的字段是組合索引的一部分,並放在索引組合順序的最後,避免出現file_sort的狀況,影響查詢性能。 正例:where a=? and b=? order by c; 索引:a_b_c 9)利用覆蓋索引來進行查詢操做,避免回表。很形象的比喻:若是一本書須要知道第11章是什麼標題,會翻開第11章對應的那一頁嗎?目錄(索引列)瀏覽一下就好,這個目錄就是起到覆蓋索引的目的。覆蓋索引的explain結果中,extra列會出現:using index。 10)利用延遲關聯或子查詢優化超多分頁場景。說明:MySQL並非跳過offset行,而是取offset+N行,而後放棄前offset行,返回N行,那當offset特別大的時候,效率就很是低下。 11)建組合索引的時候,區分度最高的在最左邊。舉極端例子:若是where a=? and b=?,a的列幾乎接近於惟一值,那麼只需單建idx_a索引便可。 12)不要使用count(列名)或count(常量)來替代count(*),count(*)是SQL92定義的標準統計行數的語法,跟數據庫無關,跟NULL和非NULL無關。count(列名)會忽略此列爲NULL值的行。 13)不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣:級聯更新時強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。 14)數據訂正時,刪除和修改記錄時,要先select,避免出現誤刪除,確認無誤後才能執行更新語句。 15)在表查詢中,一概不要使用*做爲查詢的字段列表,須要哪些字段必須明確寫明。 16)@Transactional事務不要濫用。事務會影響數據庫的QPS,另外使用事務的地方須要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等。

  數據庫操做的一些基本常識,數據庫性能變壞,多數狀況是因爲上層應用的不合理使用致使的。

高併發服務器建議調小TCP協議的time_wait超時時間。 說明:操做系統默認240秒後,纔會關閉處於time_wait狀態的鏈接,在高併發訪問下,服務器端會由於處於time_wait的鏈接數太多,可能沒法創建新的鏈接,故須要在服務器上調小此閾值。對於Linux服務器,變動/etc/sysctl.conf中的net.ipv4.tcp_fin_timeout

我的補充

  這裏補充一部分手冊以外的規範,一些是筆者在實際工做中遇到過,實踐過的經驗,一些是組內大牛分享實踐的,如有不合理的地方還請你們指正。   

1)客戶端socket超時配置應區分鏈接超時和讀超時。用connect timeout控制鏈接創建的超時時間,用read timeout控制流讀取數據的超時時間。代碼示例:

socket.connect(new InetSocketAddress(host, port), 2000);  //設置鏈接超時爲2s。
socket.setSoTimeout(10*1000);  //設置讀超時爲10s。

2)對於QPS很是高的RPC接口,應該將RPC客戶端socket的讀超時儘可能設短,以便當該接口不可用時,能快速超時返回,使客戶端能及時處理,避免上層應用所以環節等待時間過長而將上層服務打垮。 例如,socket.setSoTimeout(1000),將讀超時設置爲1s。

3)數據庫查詢時,除了order by須要利用索引的有序性,對於group by操做,在數據量大時,有無利用索引的性能差別特別大。

4)數據庫批量操做時,要分批進行,避免一次操做涉及記錄數過多,致使事務超時。 例如:根據ids批量更新數據,先用Lists.partition分批拆分紅多個子list,而後每一個list走一次更新,使單個事務儘快結束,分批大小通常設置1000。

5)字符串分割時,用Apache Commons中的StringUtils.splitPreserveAllTokens(...)代替JDK中的str.split(..),避免JDK對末尾空串的過濾致使結果與預期不一致。

  寫在最後,筆者想用阿里巴巴Java開發手冊的做者孤盡大神的採訪名言來結束此文:

別人都說咱們是搬磚的碼農,但咱們知道本身是追求個性的藝術家。也許咱們不會過多在乎本身的外表和穿着,但在咱們不羈的外表下,骨子裏追求着代碼的美、系統的美,代碼規範其實就是一個對程序美的定義

原文同步更新。

相關文章
相關標籤/搜索