首頁  >  文章  >  Java  >  解析Java的編碼規範

解析Java的編碼規範

怪我咯
怪我咯原創
2017-06-25 10:12:352279瀏覽

一、程式設計規約

(一) 命名規約

1. 【強制】 程式碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束。

  反例: _nam / __name / $Object / name_  / name$ / Object$
2. 【強制】 程式碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。

  說明:正確的英文拼字和文法可以讓閱讀者易於理解,避免歧義。注意,即使純拼音命名方式也要避免採用。

  反例: DaZhePromotion [折扣] / getPingfenByName() [評分] / int 某變數= 3

##  正例: alibaba / taobao / youku / hangzhou 等國際通用的名稱,可視同英文。

3. 【強制】類別名稱使用UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:(領域模型的相關命名)DO / BO / DTO / VO等。

  正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion

  反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

4. 【實體名稱變數、局部變數都統一使用lowerCamelCase風格,必須遵從駝峰形式。

  正例: localValue / getHttpMessage() / inputUserId

5. 【強制】常數命名全部大寫,單字間用底線隔開,力求語義表達完整清楚,不要嫌名字長。

  正例: MAX_STOCK_COUNT

  反例: MAX_STOCK_COUNT


  反例:MAX_COUNT
6. 【強制】抽象類別命名使用Abstract或Base開頭;異常類別命名使用Exception結尾;測試類別命名以測試類別命名使用Abstract或Base開頭;異常類別命名使用Exception結尾;測試類別命名以測試類別要測試類別命名的類別的名稱開始,以Test結尾。

7. 【強制】中括號是陣列類型的一部分,陣列定義如下:String[] args;


  反例:請勿使用String args[]的方式來定義。

8. 【強制】POJO類別中布林類型的變量,都不要加is,否則部分框架解析會造成序列化錯誤。


  反例:定義為基本資料型別boolean isSuccess;的屬性,它的方法也是isSuccess(),RPC框架在反向解析的時候,「以為」對應的屬性名稱是success,導致屬性取得不到,進而拋出異常。

9. 【強制】包名統一使用小寫,點分隔符號之間有且僅有一個自然語意的英文單字。包名統一使用單數形式,但是類別名稱如果有複數意義,類別名稱可以使用複數形式。


   正例: 應用工具類別套件名稱為com.alibaba.open.util、類別名稱為MessageUtils(此規則參考spring的框架結構)

10. 【強制】杜絕完全不規範的縮寫,避免望文不知義。


  反例: AbstractClass「縮寫」命名成AbsClass;condition「縮寫」命名成 condi,此類隨意縮寫嚴重降低了程式碼的可閱讀性。

11. 【推薦】如果使用到了設計模式,建議在類別名稱中體現出具體模式。

  說明:將設計模式體現在名字中,有利於閱讀者快速理解架構設計想法。

  正例:public class OrderFactory;

     public class LoginProxy;


     public class ResourceObserver;

12. 【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持程式碼的簡潔性,並加上有效的Javadoc註解。盡量不要在介面裡定義變量,如果一定要定義變量,肯定是與介面方法相關,並且是整個應用的基礎常數。

  正例:介面方法簽章:void f(); 介面基礎常數表示:String COMPANY = "alibaba";


  反例:介面方法定義:public abstract void f(); 說明:JDK8中介面允許有預設實現,那麼這個default方法,就是對所有實作類別都有價值的預設實作。

13. 介面和實作類別的命名有兩套規則:

  1)【強制】對於Service和DAO類,基於SOA的理念,暴露出來的服務一定是接口,內部的實現類用Impl的後綴與介面區別。

  正例:CacheServiceImpl實作CacheService介面。

   2) 【推薦】 如果是形容能力的介面名稱,取對應的形容詞做介面名(通常是–able的形式)。


  正例:AbstractTranslator實作 Translatable。

14. 【參考】枚舉類別名稱建議帶上Enum後綴,枚舉成員名稱需要全大寫,單字間用底線隔開。

  說明:枚舉其實就是特殊的常數類,且建構方法被預設強制是私有。


  正例:枚舉名字:DealStatusEnum,成員名稱:SUCCESS / UNKOWN_REASON。

15. 【參考】各層命名規約:

   A) Service/DAO層方法命名規約

    1) 取得單一物件的方法用get做前綴。

    2) 取得多個物件的方法用list做前綴。

    3) 取得統計值的方法用count做前綴。

    4) 插入的方法用save(推薦)或insert做前綴。

    5) 刪除的方法用remove(建議)或delete做前綴。 ###

    6) 修改的方法用update做前綴。

  B) 領域模型命名規約

    1) 資料物件:xxxDO,xxx即為資料表名。

    2) 資料傳輸物件:xxxDTO,xxx為業務領域相關的名稱。

    3) 展示物件:xxxVO,xxx一般為網頁名稱。

    4) POJO是DO/DTO/BO/VO的統稱,禁止命名成xxxPOJO。

(二) 常數定義

1. 【強制】不允許出現任何魔法值(即未經定義的常數)直接出現在程式碼中。

  反例: String key="Id#taobao_"+tradeId;

##     cache.put(key, value);

2. 【強制】long或Long初始賦值時,必須使用大寫的L,不能是小寫的l,小寫容易跟數字1混淆,造成誤解。

  說明:Long a = 2l; 寫的是數字的21,還是Long型的2?

3. 【推薦】不要使用一個常數類別來維護所有常數,應該按常數功能進行歸類,分開維護。如:快取相關的常數放在類別:CacheConsts下;系統配置相關的常數放在類別:ConfigConsts下。

  說明:大而全的常數類,非得使用查找功能才能定位到修改的常數,不利於理解和維護。

4. 【建議】常數的複用層次有五層:跨應用共享常數、應用內共享常數、子工程內共享常數、包內共享常數、類別內共享常數。

  1) 跨應用共享常數:放置在二方庫中,通常是client.jar中的constant目錄下。

  2) 應用內共享常數:放置在一方庫的modules中的constant目錄下。

    反例:易懂變數也要統一定義成應用內共享常數,兩位攻城師在兩個類別中分別定義了表示「是」的變數:

#       類別A中:public static final String YES = "yes";

       類別B中:public static final String YES = "y"; A.YES.equals(B.YES),預期是true,但實際回傳為false,導致產生線上問題。

  3) 子工程內部共享常數:即在目前子工程的constant目錄下。

  4) 套件內共享常數:即在目前套件下單獨的constant目錄下。

  5) 類別內共享常數:直接在類別內部private static final定義。

5. 【推薦】如果變數值只在一個範圍內變化用Enum類別。如果還帶有名稱之外的延伸屬性,必須使用Enum類,下面正例中的數字就是延伸訊息,表示星期幾。

  正例:public Enum{ MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}

(三) 格式規約

1. 【強制】大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行;如果是非空程式碼區塊則:

  1) 左大括號前不換行。

  2) 左大括號後換行。

  3) 右大括號前換行。

  4) 右大括號後還有else等程式碼則不換行;表示終止右大括號後必須換行。

2. 【強制】 左括號和後一個字元之間不出現空格;同樣,右括號和前一個字元之間也不出現空格。詳見第5條下方正例提示。
3. 【強制】if/for/while/switch/do等保留字與左右括號之間都必須加空格。
4. 【強制】任何運算子左右必須加一個空格。

  說明:運算子包含賦值運算子=、邏輯運算子&&、加減乘除符號、三目運算子等。

5. 【強制】縮排採用4個空格,禁止使用tab字元。
說明: 如果使用 tab 縮進,必須設定 縮進,必須設定 縮進,必須設定 縮進,必須設定 縮進,必須設定 縮進,必須設定 1個 tab 為 4個空格。 IDEA 設定 tab 為 4個空格時, 請勿勾選 Use tab character ;而在 eclipse 中,必須勾選 insert spaces for tabs 。
正例: (涉及1-5點)

public static void main(String args[]) {// 缩进4个空格String say = "hello";// 运算符的左右必须有一个空格int flag = 0;// 关键词if与括号之间必须有一个空格,括号内的f与左括号,0与右括号不需要空格if (flag == 0) {
            System.out.println(say);
        }// 左大括号前加空格且不换行;左大括号后换行if (flag == 1) {
            System.out.println("world");// 右大括号前换行,右大括号后有else,不用换行} else {
            System.out.println("ok");// 在右大括号后直接结束,则必须换行        }
    }

6. 【强制】单行字符数限不超过 120 个,超出需要换行时 个,超出需要换行时 遵循如下原则:

  1) 第二行相对一缩进 4个空格,从第三行开始不再继续缩进参考示例。

  2) 运算符与下文一起换行。

  3) 方法调用的点符号与下文一起换行。

  4) 在多个参数超长,逗号后进行换行。

  5) 在括号前不要换行,见反例。

  正例:
    StringBuffer sb = new StringBuffer();
    //超过120个字符的情况下,换行缩进4个空格,并且方法前的点符号一起换行
    sb.append("zi").append("xin")...
    .append("huang")...
    .append("huang")...
    .append("huang");
  反例:
    StringBuffer sb = new StringBuffer();
    //超过120个字符的情况下,不要在括号前换行
    sb.append("zi").append("xin")...append
    ("huang");
    //参数很多的方法调用可能超过120个字符,不要在逗号前换行
    method(args1, args2, args3, ...
    , argsX);
7. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。

  正例:下例中实参的"a",后边必须要有一个空格。
    method("a", "b", "c");
8. 【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用windows格式。
9. 【推荐】没有必要增加若干空格来使某一行的字符与上一行的相应字符对齐。

  正例:

int a = 3;long b = 4L;float c = 5F;
StringBuffer sb = new StringBuffer();


說明:增加sb這個變量,如果需要對齊,則給a、b、c都要增加幾個空格,在變量比較多的情況下,是一種累贅的事情。
10. 【推薦】方法體內的執行語句組、變數的定義語句組、不同的業務邏輯之間或不同的語意之間插入一個空行。相同業務邏輯和語意之間不需要插入空白行。

  說明:沒有必要插入多行空格進行隔開。

(四) OOP規則

1. 【強制】避免透過一個類別的物件引用存取此類的靜態變數或靜態方法,無謂增加編譯器解析成本,直接用類別名來參觀即可。
2. 【強制】所有的覆寫方法,必須加@Override註解。

  反例:getObject()與get0bject()的問題。一個是字母的O,一個是數字的0,加@Override可以準確判斷是否覆蓋成功。另外,如果在抽象類別中對方法簽章進行修改,其實作類別會馬上編譯報錯。
3. 【強制】相同參數類型,相同業務意義,才可以使用Java的可變參數,避免使用Object。

  說明:可變參數必須放置在參數清單的最後。 (提倡同學盡量不用可變參數程式設計)

  正例:public User getUsers(String type, Integer... ids)
4. 【強制】對外暴露的介面簽名,原則上不允許修改方法簽名,避免對介面呼叫方產生影響。介面過時必須加@Deprecated註解,並清楚說明採用的新介面或新服務是什麼。
5. 【強制】不能使用過時的類別或方法。

  說明:java.net.URLDecoder 中的方法decode(String encodeStr) 這個方法已經過時,應該使用雙參數decode(String source, String encode)。接口提供方既然明確是過時接口,那麼有義務同時提供新的接口;作為調用方來說,有義務去考證過時方法的新實現是什麼。
6. 【強制】Object的equals方法容易拋空指標異常,應使用常數或確定有值的物件來呼叫equals。

  正例: "test".equals(object); 反例: object.equals("test");

  說明:推薦使用java.util.Objects#equals (JDK7引入的工具類別)
7. 【強制】所有的相同類型的包裝類別物件之間值的比較,全部使用equals方法比較。

  說明:對於Integer var=?在-128至127之間的賦值,Integer對像是在IntegerCache.cache產生,會復用已有對象,這個區間內的Integer值可以直接使用==進行判斷,但是這個區間之外的所有數據,都會在堆上產生,並不會復用已有對象,這是一個大坑,推薦使用equals方法進行判斷。
8. 【強制】關於基本資料型別與包裝資料型別的使用標準如下:

  1) 所有的POJO類別屬性必須使用包裝資料型別。

  2) RPC方法的傳回值和參數必須使用包裝資料型別。

  3) 所有的局部變數【建議】使用基本資料型態。

  說明:POJO類別屬性沒有初值是提醒使用者在需要使用時,必須自己明確地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。 正例:資料庫的查詢結果可能是null,因為自動拆箱,用基本資料型別接收有NPE風險。

  反例:例如顯示成交總額漲跌情況,即正負x%,x為基本資料類型,呼叫的RPC服務,呼叫不成功時,回傳的是預設值,頁顯示:0%,這是不合理的,應該顯示成中劃線-。所以包裝資料型別的null值,能夠表示額外的訊息,如:遠端呼叫失敗,異常退出。
9. 【強制】定義DO/DTO/VO等POJO類別時,不要設定任何屬性預設值。

  反例:POJO類別的gmtCreate預設值為new Date();但是這個屬性在資料提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。
10. 【強制】序列化類別新增屬性時,請不要修改serialVersionUID字段,避免反序列失敗;如果完全不相容升級,避免反序列化混亂,那麼請修改serialVersionUID值。

  說明:注意serialVersionUID不一致會拋出序列化執行階段例外。
11. 【強制】建構方法裡面禁止加入任何業務邏輯,如果有初始化邏輯,請放在init方法中。
12. 【強制】POJO類別必須寫toString方法。使用IDE的中工具:source> generate toString時,如果繼承了另一個POJO類,注意在前面加一下super.toString。

  说明:在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。
13. 【推荐】使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。
  说明:
    String str = "a,b,c,,";
    String[] ary = str.split(",");
    //预期大于3,结果是3
    System.out.println(ary.length);
14. 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
15. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。 说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为方法信息价值较低,所有Service和DAO的getter/setter方法放在类体最后。
16. 【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名=参数名。在getter/setter方法中,尽量不要增加业务逻辑,增加排查问题的难度。

  反例:

public Integer getData() {if (true) {return data + 100;
        } else {return data - 100;
        }
    }

17. 【推荐】循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。 反例:

String str = "start";for (int i = 0; i < 100; i++) {
    str = str + "hello";
}

说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
18. 【推荐】final可提高程序响应效率,声明成final的情况:

  1) 不需要重新赋值的变量,包括类属性、局部变量。

  2) 对象参数前加final,表示不允许修改引用的指向。

  3) 类方法确定不允许被重写。
19. 【推荐】慎用Object的clone方法来拷贝对象。

  说明:对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现属性对象的拷贝。
20. 【推荐】类成员与方法访问控制从严:

  1) 如果不允许外部直接通过new来创建对象,那么构造方法必须是private。

  2) 工具类不允许有public或default构造方法。

  3) 类非static成员变量并且与子类共享,必须是protected。

  4) 类非static成员变量并且仅在本类使用,必须是private。

  5) 类static成员变量如果仅在本类使用,必须是private。

  6) 若是static成员变量,必须考虑是否为final。

  7) 类成员方法只供类内部调用,必须是private。

  8) 类成员方法只对继承类公开,那么限制为protected。

  说明:任何类、方法、参数、变量,严控访问范围。过宽泛的访问范围,不利于模块解耦。

  思考:如果是一个private的方法,想删除就删除,可是一个public的Service方法,或者一个public的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心的。

(五) 集合处理

1. 【强制】关于hashCode和equals的处理,遵循如下规则:

  1) 只要重写equals,就必须重写hashCode。

  2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。

  3) 如果自定义对象做为Map的键,那么必须重写hashCode和equals。

    正例:String重写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用。
2. 【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;

  说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。
3. 【强制】 在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException 异常。
4. 【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。

  反例:直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。

  正例:

List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);

说明:使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。

5. 【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。

  说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。 String[] str = new String[] { "a", "b" }; List list = Arrays.asList(str);

  第一种情况:list.add("c"); 运行时异常。

  第二种情况:str[0]= "gujin"; 那么list.get(0)也会随之修改。
6. 【强制】泛型通配符来接收返回的数据,此写法的泛型集合不能使用add方法。

  说明:苹果装箱后返回一个对象,此对象就不能往里加任何水果,包括苹果。
7. 【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

  反例:

List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");for (String temp : a) {if ("1".equals(temp)) {
        a.remove(temp);
    }
}

说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗? 正例:

Iterator<String> it = a.iterator();while (it.hasNext()) {
    String temp = it.next();if (删除元素的条件) {
        it.remove();
    }
}

8. 【强制】 在JDK7版本以上,Comparator要满足自反性,传递性,对称性,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常。

  说明:

  1) 自反性:x,y的比较结果和y,x的比较结果相反。

  2) 传递性:x>y,y>z,则x>z。

  3) 对称性:x=y,则x,z比较结果和y,z比较结果相同。

  反例:下例中没有处理相等的情况,实际使用中可能会出现异常:

new Comparator<Student>() {
    @Overridepublic int compare(Student o1, Student o2) {return o1.getId() > o2.getId() ? 1 : -1;
    }
}

9. 【推荐】集合初始化时,尽量指定集合初始值大小。 说明:ArrayList尽量使用ArrayList(int initialCapacity) 初始化。

10. 【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。

  说明:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法。

  正例:values()返回的是V值集合,是一个list集合对象;keySet()返回的是K值集合,是一个Set集合对象;entrySet()返回的是K-V值组合集合。
11. 【推荐】高度注意Map类集合K/V能不能存储null值的情况,如下表格:

集合类 Key Value Super 说明
Hashtable 不允许为null 不允许为null Dictionary 线程安全
ConcurrentHashMap 不允许为null 不允许为null AbstractMap 分段锁技术
TreeMap 不允许为null 允许为null AbstractMap 线程不安全
HashMap 允许为null 允许为null AbstractMap 线程不安全

 

 

 

 

  反例: 由于HashMap的干扰,很多人认为ConcurrentHashMap是可以置入null值,注意存储null值时会抛出NPE异常。
12. 【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。

  说明:稳定性指集合每次遍历的元素次序是一定的。有序性是指遍历的结果是按某种比较规则依次排列的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。
13. 【参考】利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。

(六) 并发处理

1. 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。

  说明:资源驱动类、工具类、单例工厂类都需要注意。
2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

  正例:

public class TimerTaskThread extends Thread {public TimerTaskThread(){super.setName("TimerTaskThread"); 
    ...
}

3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

4. 【强制】线程池不允许使用 Executors去创建,而是通过ThreadPoolExecutor去创建,这样的处理方式让写同学更加明确线程池运行规则,避资源耗尽风险。

  说明: Executors返回的线程池对象的弊端如下 :

  1)FixedThreadPool和 SingleThread:

    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

  2)CachedThreadPool和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
5. 【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。

  正例:注意线程安全,使用DateUtils。亦推荐如下处理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
    @Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");
    }
 }

说明:如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替Simpledateformatter,官方给出的解释:simple beautiful strong immutable thread-safe。

6. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
7. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

  说明:线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。
8. 【强制】并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。

  说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
9. 【强制】多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
10. 【推荐】使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法可以执行,避免主线程无法执行至countDown方法,直到超时才返回结果。

  说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。
11. 【推荐】避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。
  说明:Random实例包括java.util.Random 的实例或者 Math.random()实例。

  正例:在JDK7之后,可以直接使用API ThreadLocalRandom,在 JDK7之前,可以做到每个线程一个实例。
12. 【推荐】通过双重检查锁(double-checked locking)(在并发场景)实现延迟初始化的优化问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration),推荐问题解决方案中较为简单一种(适用于JDK5及以上版本),将目标属性声明为 volatile型。

反例:

class Foo {private Helper helper = null;public Helper getHelper() {if (helper == null)synchronized (this) {if (helper == null)
                    helper = new Helper();
            }return helper;
    }// other functions and members...}

13. 【參考】volatile解決多執行緒記憶體不可見問題。對於一寫多讀,是可以解決變數同步問題,但是如果多寫,同樣無法解決執行緒安全性問題。如果是count++操作,使用以下類別實作:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是JDK8,建議使用LongAdder對象,比AtomicLong效能更好(減少樂觀鎖的重試次數)。

14. 【參考】 HashMap在容量不夠進行resize時由於高並發可能出現死鏈,導致CPU飆升,在開發過程中註意規避此風險。
15. 【參考】ThreadLocal無法解決共享物件的更新問題,ThreadLocal物件建議使用static修飾。這個變數是針對一個執行緒內所有操作共有的,所以設定為靜態變量,所有此類實例共享此靜態變量,也就是說在類別第一次被使用時裝載,只分配一塊儲存空間,所有此類的物件(只要是這個執行緒內定義的)都可以操控這個變數。

(七) 控制語句

1. 【強制】在一個switch區塊內,每個case要麼透過break/return等來終止,要麼註解說明程式將繼續執行到哪一個case為止;在一個switch區塊內,都必須包含一個default語句並且放在最後,即使它什麼程式碼也沒有。
2. 【強制】在if/else/for/while/do語句中必須使用大括號,即使只有一行程式碼,避免使用下面的形式:if (condition) statements;
3. 【建議】推薦盡量少用else,if-else的方式可以改寫成:
  if(condition){
    ...
    return obj;
  }##    return obj;
  }#>#se)#se#se 的業務的邏輯嗎/>#/#se#se>}#.程式碼;
  說明:如果非得使用if()...else if()...else...方式表達邏輯,【強制】請勿超過3層,超過請使用狀態設計模式。
  正例:邏輯上超過3層的if-else程式碼可以使用衛語句,或是狀態模式來實現。
4. 【推薦】除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提高可讀性。

  說明:很多if語句內的邏輯相當複雜,閱讀者需要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,如果閱讀者分析邏輯表達式錯誤呢?

  正例:
    //偽代碼如下
    boolean existed = (file.open(fileName, "w") != null) && (...) || (|| );
    if (existed) {
      ...
    }
  反例:
   }
  反例:
"),   }
  反例:
"),   ) || (...)) {
      ...

    }

5. 【推薦】循環體中的語句要考慮性能,以下操作盡量移至循環體外處理,如定義對象、變數、取得資料庫連接,進行不必要的try-catch操作(這個try-catch是否可以移至循環體外)。

6. 【建議】介面入參保護,這種場景常見的是用來做批量操作的介面。

7. 【參考】方法中需要進行參數校驗的場景:

  1) 呼叫頻次低的方法。


  2) 執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致中間執行回退,或者錯誤,那得不償失。
  3) 需要極高穩定性和可用性的方法。

  4) 對外提供的開放接口,不管是RPC/API/HTTP接口。

  5) 敏感權限入口。

8. 【參考】方法中不需要參數校驗的場景:

  1) 極有可能被循環呼叫的方法,不建議對參數進行校驗。但在方法說明裡必須註明外部參數檢查。

  2) 底層的方法呼叫頻度都比較高,一般不校驗。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層才會暴露問題。一般DAO層與Service層都在同一個應用程式中,部署在同一台伺服器中,所以DAO的參數校驗,可以省略。

  3) 被宣告成private只會被自己程式碼所呼叫的方法,如果能夠確定呼叫方法的程式碼傳入參數已經做過檢查或肯定不會有問題,此時可以不校驗參數。

(八) 註解規約

1. 【強制】類別、類別屬性、類別方法的註解必須使用Javadoc規範,使用/**內容*/格式,不得使用/ /xxx方式。 ######  說明:在IDE編輯視窗中,Javadoc方式會提示相關註釋,產生Javadoc可以正確輸出對應註釋;在IDE中,工程呼叫方法時,不進入方法即可懸浮提示方法、參數、返回值的意義,提高閱讀效率。 ###2. 【強制】所有的抽象方法(包括介面中的方法)必須要用Javadoc註解、除了傳回值、參數、異常說明外,還必須指出該方法做什麼事情,實作什麼功能。 ###

  說明:對子類別的實作要求,或呼叫注意事項,請一併說明。
3. 【強制】所有的類別都必須加入創建者資訊。
4. 【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註解使用/* */註釋,注意與程式碼對齊。
5. 【強制】所有的枚舉類型欄位必須要有註釋,說明每個資料項的用途。
6. 【推薦】與其「半吊子」英文來註釋,不如用中文註釋把問題說清楚。專有名詞與關鍵字保持英文原文即可。

  反例:“TCP連接逾時”解釋成“傳輸控制協議連接超時”,理解反而費腦筋。
7. 【建議】程式碼修改的同時,註解也要進行對應的修改,尤其是參數、回傳值、異常、核心邏輯等的修改。

  說明:程式碼與註解更新不同步,就像路網與導航軟體更新不同步一樣,如果導航軟體嚴重滯後,就失去了導航的意義。
8. 【參考】註解掉的程式碼盡量要配合說明,而不是簡單的註解掉。

  說明:程式碼被註解掉有兩種可能性:

    1)後續會恢復此段程式碼邏輯。

    2)永久不用。前者如果沒有備註訊息,難以得知註釋動機。後者建議直接刪掉(程式碼倉庫保存了歷史代碼)。
9. 【參考】對於註解的要求:

  第一、能夠準確反應設計思想和程式碼邏輯;

  第二、能夠描述業務意義,使別的程式設計師能夠迅速了解程式碼背後的訊息。完全沒有註釋的大段代碼對於閱讀者形同天書,註釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路;註釋也是給繼任者看的,使其能夠快速接替自己的工作。
10. 【參考】好的命名、程式碼結構是自解釋的,註解力求精簡準確、表達到位。避免出現註釋的一個極端:過度過濫的註釋,程式碼的邏輯一旦修改,修改註釋是相當大的負擔。

反例:
  // put elephant into fridge
  put(elephant, fridge);
方法名稱put,加上兩個有意義的變數名稱elephant和fridge,已經說明了這是在做什麼,語意清晰的程式碼不需要額外的註解。
11. 【參考】特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,透過標記掃描,經常清理此類標記。線上故障有時候就是來自這些標記處的程式碼。

  1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間]) 表示需要實現,但目前還未實現的功能。這其實是一個Javadoc的標籤,目前的Javadoc還沒有實現,但已經被廣泛使用。只能應用於類別,介面和方法(因為它是一個Javadoc標籤)。   2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間]) 在註釋中用FIXME標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。

(九) 其它

1. 【強制】在使用正規表示式時,利用好其預編譯功能,可以有效加快正規匹配速度。

  說明:不要在方法體內定義:Pattern pattern = Pattern.compile(規則);
2. 【強制】velocity呼叫POJO類別的屬性時,建議直接使用屬性名稱取值即可,模板引擎會自動依規範呼叫POJO的getXxx(),如果是boolean基本資料型別變數(boolean命名不需要加上is前綴),會自動呼叫isXxx()方法。

  說明:注意如果是Boolean包裝類別對象,優先呼叫getXxx()的方法。
3. 【強制】後台輸送給頁面的變數必須加上$!{var}-中間的感嘆號。

  說明:如果var=null或不存在,那麼${var}會直接顯示在頁面上。
4. 【強制】注意Math.random() 這個方法回傳是double類型,注意取值的範圍0≤x<1(能夠取到零值,注意除零異常),如果想取得整數類型的隨機數,不要將x放大10的若干倍然後取整,直接使用Random物件的nextInt或nextLong方法。
5. 【強制】取得目前毫秒數System.currentTimeMillis(); 而不是new Date().getTime();

  說明:如果想取得更精確的奈秒時間值,用System.nanoTime()。在JDK8中,針對統計時間等場景,建議使用Instant類別。
6. 【推薦】盡量不要在vm中加入變數宣告、邏輯運算符,更不要在vm模板中加入任何複雜的邏輯。
7. 【推薦】任何資料結構的建構或初始化,都應指定大小,避免資料結構無限增長吃光記憶體。
8. 【推薦】對於“明確停止使用的程式碼和配置”,如方法、變數、類別、設定檔、動態配置屬性等要堅決從程式中清理出去,避免造成過多垃圾。

二、異常日誌

(一) 異常處理

1. 【強制】不要捕獲Java類庫中定義的繼承自RuntimeException的運行時異常類,如:IndexOutOfBoundsException / NullPointerException,這類異常由程式設計師預先檢查來規避,保證程式健全性。

  正例:if(obj != null) {...}

  反例:try { obj.method() } catch(NullPointerException e){...}
2. 【強制】異常不要用來做流程控制,條件控制,因為異常的處理效率比條件分支低。
3. 【強制】對大段程式碼進行try-catch,這是不負責任的表現。 catch時請分辨穩定程式碼和非穩定程式碼,穩定程式碼指的是無論如何不會出錯的程式碼。對於非穩定程式碼的catch盡可能進行區分異常類型,再做對應的異常處理。
4. 【強制】捕獲異常是為了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化為用戶可以理解的內容。
5. 【強制】有try塊放到了事務代碼中,catch異常後,如果需要回滾事務,一定要注意手動回滾事務。
6. 【強制】finally區塊必須對資源物件、流物件進行關閉,有異常也要做try-catch。

  說明:如果JDK7,可以使用try-with-resources方式。
7. 【強制】不能在finally區塊中使用return,finally區塊中的return回傳後方法結束執行,不會再執行try區塊中的return語句。
8. 【強制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。

  說明:如果預期對方拋的是繡球,實際接到的是鉛球,就會產生意外狀況。
9. 【建議】方法的傳回值可以為null,不強制傳回空集合,或空物件等,必須加入註解充分說明在什麼情況下會傳回null值。呼叫方需要進行null判斷防止NPE問題。

  說明:本規約明確防止NPE是呼叫者的責任。即使被呼叫方法傳回空集合或空對象,對呼叫者來說,也並非高枕無憂,必須考慮到遠端呼叫失敗,運行時異常等場景傳回null的情況。
10. 【推薦】防止NPE,是程式設計師的基本修養,注意NPE產生的場景:

  1) 傳回類型為包裝資料類型,有可能是null,返回int值時注意判空。

    反例:public int f(){ return Integer物件}; 若為null,自動解箱拋NPE。

  2) 資料庫的查詢結果可能為null。

  3) 集合裡的元素即使isNotEmpty,取出的資料元素也可能為null。
  4) 遠端呼叫返回對象,一律要求進行NPE判斷。

  5) 對於Session中所獲得的數據,建議NPE檢查,避免空指標。

  6) 級聯調用obj.getA().getB().getC();一連串調用,易產生NPE。
11. 【推薦】在程式碼中使用“拋例外”或“返回錯誤碼”,對於公司外的http/api開放介面必須使用“錯誤碼”;而應用內部推薦異常拋出;跨應用間RPC呼叫優先考慮使用Result方式,封裝isSuccess、「錯誤碼」、「錯誤簡短訊息」。 說明:關於RPC方法回傳方式使用Result方式的理由:

  1)使用拋異常回傳方式,呼叫方如果沒有捕獲到就會產生執行階段錯誤。

  2)如果不加棧訊息,只是new自訂異常,加入自己的理解的error message,對於呼叫端解決問題的幫助不會太多。如果加了堆疊訊息,在頻繁呼叫出錯的情況下,資料序列化和傳輸的效能損耗也是問題。
12. 【推薦】定義時區分unchecked / checked 例外,避免直接使用RuntimeException拋出,更不允許拋出Exception或Throwable,應使用有業務意義的自訂例外。推薦業界已定義過的自訂異常,如:DAOException / ServiceException等。
13. 【參考】避免重複的程式碼(Don’t Repeat Yourself),即DRY原則。 說明:隨意複製貼上程式碼,必然會導致程式碼的重複,在以後需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共通方法,或抽象公共類,甚至是共用模組。

  正例:一個類別中有多個public方法,都需要進行數行相同的參數校驗操作,這個時候請抽取:
  private boolean checkParam(DTO dto){...}

(二) 日誌規約

1. 【強制】應用中不可直接使用日誌系統(Log4j、Logback)中的API,而應依賴使用日誌框架SLF4J中的API,使用門面模式的日誌框架,有利於維護和各類別的日誌處理方式統一。
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  private static final Logger logger = LoggerFactory.getLogger(Abc.class);##2。保存15天,因為有些異常具備以「週」為頻次發生的特性。
3. 【強制】應用程式中的擴充日誌(如打點、臨時監控、存取日誌等)命名方式:appName_logType_logName.log。 logType:日誌類型,建議分類有stats/desc/monitor/visit等;logName:日誌描述。這種命名的好處:透過檔案名稱就可知道日誌檔案屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找。

  正例:mppserver應用程式中單獨監控時區轉換異常,如: mppserver_monitor_timeZoneConvert.log 說明:建議對日誌進行分類,錯誤日誌和業務日誌盡量分開存放,便於開發人員查看,也便於透過日誌對系統進行及時監控。

4. 【強制】對trace/debug/info等級的日誌輸出,必須使用條件輸出形式或使用佔位符的方式。

  說明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日誌等級是warn,上述日誌不會列印,但會執行字串拼接操作,如果symbol是對象,會執行toString()方法,浪費了系統資源,執行了上述操作,最終日誌卻沒有列印。

  正例:(條件)

  if (logger.isDebugEnabled()) {
    logger.debug("Processing trade with id: " + id + " symbollogger.debug("Processing trade with id: " + id + " symbolbollogger.debug("Processing trade with id: " + id + " symbolbol: " + sym); ##  }
  正例:(佔位符)
  logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
#5. 【強制】避免重複重複重複重複重複重複重複列印日誌,浪費磁碟空間,務必在log4j.xml中設定additivity=false。

  正例:

6. 【強制】異常訊息應該包含兩類資訊:案發現場資訊和異常堆疊資訊。如果不處理,那麼往上拋。


  正例:logger.error(各類別參數或物件toString + "_" + e.getMessage(), e);

7. 【推薦】可以使用warn日誌等級來記錄使用者輸入參數錯誤的情況,避免用戶投訴時,無所適從。注意日誌輸出的級別,error級別只記錄系統邏輯出錯、異常等重要的錯誤訊息。如非必要,請不要在此場景打出error等級。

8. 【推薦】謹慎地記錄日誌。生產環境禁止輸出debug日誌;選擇性地輸出info日誌;如果使用warn來記錄剛上線時的業務行為訊息,一定要注意日誌輸出量的問題,避免把伺服器磁碟撐爆,並記得及時刪除這些觀察日誌。

  說明:大量地輸出無效日誌,不利於系統效能提升,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能為問題排查帶來好處?

三、MySQL規約

(一) 建表規約

#1. 【強制】表達是與否概念的字段,必須使用is_xxx的方式命名,資料類型是unsigned tinyint(1表示是,0表示否),此規則同樣適用於odps建表。

  說明:任何欄位如果為非負數,必須是unsigned。

2. 【強制】表名、欄位名稱必須使用小寫字母或數字;禁止出現數字開頭,禁止兩個底線中間只出現數字。資料庫欄位名的修改代價很大,因為無法進行預發布,所以欄位名稱需要慎重考慮。


  正例:getter_admin,task_config,level3_name

  反例:GetterAdmin,taskConfig,level_3_name

3. 【強制】表名不使用複數名詞。 說明:表名應該只表示表裡面的實體內容,不應該表示實體數量,對應DO類別名稱也是單數形式,符合表達習慣。

4. 【強制】停用保留字,如desc、range、match、delayed等,請參考MySQL官方保留字。
5. 【強制】唯一索引名為uk_欄位名;普通索引名則為idx_欄位名稱。 說明:uk_ 即 unique key;idx_ 即index的簡稱。
6. 【強制】小數類型為decimal,禁止使用float和double。

  說明:float和double在儲存的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結果。如果儲存的資料範圍超過decimal的範圍,建議將資料拆成整數和小數分開儲存。
7. 【強制】如果儲存的字串長度幾乎相等,使用char定長字串類型。
8. 【強制】varchar是可變長字串,不預先分配儲存空間,長度不要超過5000,如果儲存長度大於此值,定義欄位類型為text,獨立出來一張表,用主鍵對應,避免影響其它字段索引效率。
9. 【強制】表必備三字段:id, gmt_create, gmt_modified。

  說明:其中id必為主鍵,類型為unsigned bigint、單表時自增、步長為1。 gmt_create, gmt_modified的類型皆為date_time型別。
10. 【推薦】表的命名最好是加上「業務名稱_表的作用」。 正例:tiger_task / tiger_reader / mpp_config
11. 【建議】庫名與應用程式名稱盡量一致。
12. 【推薦】如果修改欄位意義或對欄位表示的狀態追加時,需要及時更新欄位註解。
13. 【推薦】欄位允許適當冗餘,以提高效能,但是必須考慮資料同步的情況。冗餘欄位應遵循:

  1)不是頻繁修改的欄位。 2)不是varchar超長字段,更不能是text字段。

    正例:商品類別目名稱使用頻率高,欄位長度短,名稱基本上一成不變,可在相關聯的表中冗餘儲存類別目名稱,避免關聯查詢。
14. 【建議】單表行數超過500萬行或單表容量超過2GB,才建議進行分庫分錶。

  說明:如果預計三年後的資料量根本達不到這個級別,請不要在建立表格時就分庫分錶。
15. 【參考】合適的字元儲存長度,不但節省資料庫表空間、節約索引存儲,更重要的是提升檢索速度。

  正例:人的年齡用unsigned tinyint(表示範圍0-255,人的壽命不會超過255歲);海龜就必須是smallint,但如果是太陽的年齡,就必須是int;如果是所有恆星的年齡加起來,那麼就必須使用bigint。

(二) 索引規約

1. 【強制】業務上具有唯一特性的字段,即使是組合字段,也必須建成唯一索引。

  說明:不要以為唯一索引影響了insert速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外,即使在應用層做了非常完善的校驗和控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生。
2. 【強制】 超過三個表格禁止join。需要join的字段,資料類型保持絕對一致;多表關聯查詢時,保證被關聯的字段需要有索引。

  說明:即使雙表join也要注意表索引、SQL效能。
3. 【強制】在varchar欄位上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文字區分度決定索引長度。

  說明:索引的長度與區分度是一對矛盾體,一般對字串類型數據,長度為20的索引,區分度會高達90%以上,可以使用count(distinct left(列名, 索引長度))/count(*)的區分度來決定。
4. 【強制】頁面搜尋嚴禁左模糊或全模糊,如果需要請走搜尋引擎來解決。

  說明:索引檔案具有B-Tree的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。
5. 【推薦】如果有order by的場景,請注意利用索引的有序性。 order by 最後的欄位是組合索引的一部分,並且放在索引組合順序的最後,避免file_sort的情況,影響查詢效能。

  正例:where a=? and b=? order by c; 索引:a_b_c

  反例:索引中有範圍查找,那麼索引有序性無法利用,如:WHERE a> ;10 ORDER BY b; 索引a_b無法排序。
6. 【建議】利用覆蓋索引來進行查詢操作,來避免回表操作。

  說明:如果一本書需要知道第11章是什麼標題,會翻開第11章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。

  正例:能夠建立索引的種類:主鍵索引、唯一索引、普通索引,而覆蓋索引是一種查詢的一種效果,用explain的結果,extra列會出現:using index。
7. 【推薦】利用延遲關聯或子查詢優化超多分頁場景。

  說明: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.id
8. 【推薦】 SQL效能優化的目標:至少要達到range 級別,要求是ref級別,如果可以是consts最好。

  說明:

    1)consts 單表中最多只有一個符合行(主鍵或唯一索引),在最佳化階段即可讀取到資料。

    2)ref 指的是使用普通的索引(normal index)。

    3)range 對索引進行範圍檢索。

  反例:explain表的結果,type=index,索引實體檔案全掃描,速度非常慢,這個index級別比較range還低,與全表掃描是小巫見大巫。
9. 【推薦】建立組合索引的時候,區分度最高的在最左邊。 正例:如果where a=? and b=? ,a列的幾乎接近唯一值,那麼只需要單建idx_a索引即可。

  說明:存在非等號和等號混合判斷條件時,在建立索引時,請先將等號條件的列前置。如:where a>? and b=? 那麼即使a的區分度更高,也必須把b放在索引的最前面。
10. 【參考】建立索引時避免有下列極端誤解:

  1)誤認為一個查詢就需要建立一個索引。

  2)誤以為索引會消耗空間、嚴重拖曳更新和新增速度。

  3)誤認為唯一索引一律需要在應用層以「先查後插」方式解決。

(三) SQL規則

1. 【強制】不要用count(列名)或count(常數)來取代count(*),count(*)就是SQL92定義的標準統計行數的語法,跟資料庫無關,跟NULL和非NULL無關。

  說明:count(*)會統計值為NULL的行,而count(列名)不會統計此列為NULL值的行。
2. 【強制】count(distinct col) 計算該列除NULL之外的不重複數量。注意 count(distinct col1, col2) 如果其中一列全為NULL,那麼即使另一列有不同的值,也回傳為0。
3. 【強制】當某一列的值全是NULL時,count(col)的回傳結果為0,但sum(col)的回傳結果為NULL,因此使用sum()時需注意NPE問題。

  正例:可以使用以下方式來避免sum的NPE問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
4. 【強制】使用ISNULL()來判斷是否為NULL值。注意:NULL與任何值的直接比較都是NULL。

  說明: 1) NULL<>NULL的回傳結果是NULL,而不是false。

      2) NULL=NULL的回傳結果是NULL,而不是true。

        3) NULL<>1的回傳結果是NULL,而不是true。
5. 【強制】 在程式碼中寫分頁查詢邏輯時,若count為0應直接傳回,避免執行後面的分頁語句。
6. 【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。 說明:(概念解釋)學生表中的student_id是主鍵,那麼成績表中的student_id則為外鍵。如果更新學生表中的student_id,同時觸發成績表中的student_id更新,則為級聯更新。外鍵與級聯更新適用於單機低並發,不適合分散式、高並發叢集;級聯更新是強阻塞,存在資料庫更新風暴的風險;外鍵影響資料庫的插入速度。
7. 【強制】禁止使用預存程序,預存程序難以調試和擴展,更沒有移植性。
8. 【強制】資料訂正時,刪除和修改記錄時,要先select,避免出現誤刪除,確認無誤才能執行更新語句。
9. 【推薦】in操作能避免則避免,若實在避免不了,需要仔細評估in後邊的集合元素數量,控制在1000個之內。
10. 【參考】如果有全球化需要,所有的字元儲存與表示,均以utf-8編碼,那麼字元計數方法注意:

  說明: SELECT LENGTH("輕鬆工作") ; 返回為12 SELECT CHARACTER_LENGTH("輕鬆工作"); 返回為4 如果要使用表情,那麼使用utfmb4來進行存儲,注意它與utf-8編碼的區別。
11. 【參考】 TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但TRUNCATE無事務且不觸發trigger,有可能造成事故,故不建議在開發代碼中使用此語句。

  說明:TRUNCATE TABLE 在功能上與不含 WHERE 子句的 DELETE 語句相同。

(四) ORM規則

1. 【強制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。

  說明:

    1)增加查詢分析器解析成本。

    2)增減欄位容易與resultMap配置不一致。
2. 【強制】POJO類別的boolean屬性不能加is,而資料庫欄位必須加is_,要求在resultMap中進行欄位與屬性之間的對應。

  說明:參見定義POJO類別以及資料庫欄位定義規定,在sql.xml增加映射,是必須的。
3. 【強制】不要用resultClass當回傳參數,即使所有類別屬性名與資料庫欄位一一對應,也需要定義;反過來,每個表也必然有一個與之對應。

  說明:配置映射關係,使欄位與DO類別解耦,方便維護。
4. 【強制】xml配置中參數注意使用:#{},#param# 不要使用${} 此種方式容易出現SQL注入。
5. 【強制】iBATIS自帶的queryForList(String statementName,int start,int size)不建議使用。

  說明:其實作方式是在資料庫取到statementName對應的SQL語句的所有記錄,再透過subList取start,size的子集合,線上因為這個原因曾經出現過OOM。
正例:在sqlmap.xml中引入#start#, #size
#  Map map = new HashMap();
  map.put("start" , start);
  map.put("size", size);
6. 【強制】不允許直接拿HashMap與Hashtable作為查詢結果集的輸出。
7. 【強制】更新資料表記錄時,必須同時更新記錄對應的gmt_modified欄位值為目前時間。
8. 【推薦】不要寫一個大而全的資料更新接口,傳入為POJO類,不管是不是自己的目標更新字段,都進行update table set c1=value1,c2=value2,c3=value3 ; 這是不對的。執行SQL時,盡量不要更新無改動的字段,一是易出錯;二是效率低;三是binlog增加儲存。
9. 【參考】@Transactional事務不要濫用。事務會影響資料庫的QPS,另外使用事務的地方需要考慮各方面的回溯方案,包括快取回溯、搜尋引擎回滾、訊息補償、統計修正等。
10. 【參考】中的compareValue是與屬性值對比的常數,一般是數字,表示相等時帶上此條件;表示不為空且不為null時執行; 表示不為null值時執行。

四、工程規約

(一) 應用分層

1. 【建議】圖中預設上層依賴下層,箭頭關係表示可直接依賴,如:開放介面層可以依賴Web層,也可以直接依賴Service層,依此類推:
 開放介面層:可直接封裝Service介面暴露成RPC介面;透過Web封裝成http介面;閘道控制層等。
 終端機顯示圖層:各端的範本渲染並執行顯示圖層。目前主要是velocity渲染,JS渲染,JSP渲染,行動端展示層等。
 Web層:主要是將存取控制轉發,各類別基本參數校驗,或是不重複使用的業務簡單處理等。
 Service層:相對特定的業務邏輯服務層。
 Manager層:通用業務處理層,它有以下功能:

  1) 對第三方平台封裝的層,預處理傳回結果及轉換例外資訊;

  2)對Service層通用能力的下沉,如快取方案、中間件通用處理;

  3) 與DAO層交互,對DAO的業務通用能力的封裝。
 DAO層:資料存取層,與底層MySQL、Oracle、Hbase進行資料互動。
 外部接口或第三方平台:包括其它部門RPC開放接口,基礎平台,其它公司的HTTP接口。
2. 【參考】 (分層異常處理規約)在DAO層,產生的異常類型有很多,無法用細粒度異常進行catch,使用catch(Exception e)方式,並throw new DAOException(e),不需要列印日誌,因為日誌在Manager/Service層一定需要捕獲並打到日誌檔案中去,如果同台伺服器再打日誌,浪費效能和儲存。當Service層出現異常時,必須記錄日誌訊息到磁碟,盡可能帶上參數訊息,相當於保護案發現場。若Manager層與Service同機部署,日誌方式與DAO層處理一致,若是單獨部署,則採用與Service一致的處理方式。 Web層絕不應該繼續往上拋異常,
因為已經處於頂層,無繼續處理異常的方式,如果意識到這個異常將導致頁面無法正常渲染,那麼就應該直接跳到友好錯誤頁面,盡量加上友善的錯誤提示訊息。開放介面層要將異常處理成錯誤碼和錯誤訊息方式回傳。
3. 【參考】分層領域模型規約:
 DO(Data Object):與資料庫表結構一一對應,透過DAO層向上傳輸資料來源物件。
 DTO(Data Transfer Object):資料傳輸對象,Service和Manager向外傳輸的對象。
 BO(Business Object):業務物件。可以由Service層輸出的封裝業務邏輯的物件。
 QUERY:資料查詢對象,各層接收上層的查詢請求。註:超過2個參數的查詢封裝,禁止使用Map類別來傳輸。
 VO(View Object):顯示圖層對象,通常是Web向模板渲染引擎層傳輸的對象。

(二) 二方庫規約

1. 【強制】定義GAV遵從下列規則:

  1) GroupID格式:com.{公司/BU }.業務線.[子業務線],最多4級。

    說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress等BU一級;子業務線可選。

    正例:com.taobao.jstorm 或 com.alibaba.dubbo.register

  2) ArtifactID格式:產品線名-模組名。語意不重複不遺漏,先到倉庫中心去查證。

    正例:dubbo-client / fastjson-api / jstorm-tool 3) Version:詳細規定參考下方。
2. 【強制】二方庫版本號命名方式:主版本號.次版本號.修訂號

  1) 主版本號:當做了不相容的API 修改,或者增加了能改變產品方向的新功能。

  2) 次版本號:當做了向下相容的功能性新增(新增類別、介面等)。

  3) 修訂號:修復bug,沒有修改方法簽章的功能加強,保持 API 相容性。

    說明: 起始版本號必須為: 1.0.0,而不是0.0.1
3. 【強制】線上應用不要依賴SNAPSHOT版本(安全包除外);正式發布的類別庫必須使用RELEASE版本號升級+1的方式,且版本號碼不允許覆蓋升級,必須到中央倉庫進行查證。

  說明:不依賴SNAPSHOT版本是保證應用程式發布的冪等性。另外,也可以加快編譯時的打包建置。
4. 【強制】二方庫的新增或升級,保持除功能點之外的其它jar包仲裁結果不變。如果有改變,必須明確評估和驗證,建議進行dependency:resolve前後信息比對,如果仲裁結果完全不一致,那麼通過dependency:tree命令,找出差異點,進行排除jar包。
5. 【強制】二方庫裡可以定義枚舉類型,參數可以使用枚舉類型,但是介面返回值不允許使用枚舉類型或包含枚舉類型的POJO物件。
6. 【強制】依賴一個二方庫群時,必須定義一個統一版本變量,避免版本號不一致。

  說明:依賴springframework-core,-context,-beans,它們都是同一個版本,可以定義一個變數來保存版本:${spring.version},定義依賴的時候,引用該版本。
7. 【強制】禁止在子項目的pom依賴中出現相同的GroupId,相同的ArtifactId,但是不同的Version。

  說明:在本機偵錯時會使用各子項目指定的版本號,但合併成一個war,只能有一個版本號出現在最後的lib目錄。曾經出現過線下調試是正確的,發佈到線上出故障的先例。
8. 【建議】所有pom檔案中的依賴聲明放在語句區塊中,所有版本仲裁放在語句區塊中。

  說明:裡只是聲明版本,並不實現引入,因此子項目需要明確的聲明依賴,version和scope都讀取自父pom。而所有聲明在主pom的裡的依賴都會自動引入,並默認被所有的子項目繼承。
9. 【建議】二方庫盡量不要有配置項,最低限度不要再增加配置項。
10. 【參考】為避免應用二方庫的依賴衝突問題,二方庫發布者應遵循以下原則:

  1)精簡可控原則。移除一切不必要的API和依賴,只包含 Service API、必要的領域模型物件、Utils類別、常數、枚舉等。如果依賴其它二方庫,盡量是provided引入,讓二方庫使用者去依賴具體版本號;無log具體實現,只依賴日誌框架。

  2)穩定可追溯原則。每個版本的變更應該被記錄,二方庫由誰維護,原始碼在哪裡,都需要能方便查到。除非使用者主動升級版本,否則公共二方庫的行為不應該改變。

(三) 伺服器規約

1. 【建議】高並發伺服器建議調小TCP協定的time_wait逾時時間。 說明:作業系統預設240秒後,才會關閉處於time_wait狀態的連接,在高並發存取下,伺服器端會因為處於time_wait的連接數太多,可能無法建立新的連接,所以需要在伺服器上調小此等待值。

  正例:在linux伺服器上請透過變更/etc/sysctl.conf檔去修改該缺省值(秒): net.ipv4.tcp_fin_timeout = 30
2. 【推薦】調大伺服器支援的最大檔案句柄數(File Descriptor,簡寫為fd)。

  說明:主流作業系統的設計是將TCP/UDP連線採用與檔案相同的方式去管理,也就是一個連線對應於一個fd。主流的linux伺服器預設支援最大fd數量為1024,當並發連線數很大時很容易因為fd不足而出現「open too many files」錯誤,導致新的連線無法建立。 建議將linux伺服器所支援的最大句柄數調高數倍(與伺服器的記憶體數量相關)。
3. 【推薦】給JVM設定-XX:+HeapDumpOnOutOfMemoryError參數,讓JVM碰到OOM場景時輸出dump訊息。

  說明:OOM的發生是有機率的,甚至有規律地相隔數月才出現一例,出現時的現場資訊對查錯非常有價值。
4. 【參考】伺服器內部重定向使用forward;外部重定向位址使用URL拼裝工具類別來生成,否則會帶來URL維護不一致的問題和潛在的安全風險。

五、安全規約

1. 【強制】隸屬於使用者個人的頁面或功能必須進行權限控制校驗。 說明:防止沒有做水平權限校驗就可隨意存取、操作別人的數據,例如查看、修改別人的訂單。
2. 【強制】使用者敏感資料禁止直接展示,必須對展示資料脫敏。 說明:查看個人手機號碼會顯示成:158****9119,隱藏中間4位,防止隱私外洩。
3. 【強制】使用者輸入的SQL參數嚴格使用參數綁定或METADATA欄位值限定,防止SQL注入,禁止字串拼接SQL存取資料庫。
4. 【強制】使用者請求傳入的任何參數必須做有效性驗證。 說明:忽略參數校驗可能導致:
 page size過大導致記憶體溢位
 惡意order by導致資料庫慢查詢
 任意重定向
 SQL注入
 反序列化注入
 正規輸入來源字串拒絕服務ReDoS 說明: Java JavaJava程式碼以程式碼用正規則來驗證客戶端的輸入,有些正則寫法驗證一般使用者輸入沒有問題,但如果攻擊人員使用的是特殊建構的字串來驗證,有可能導致死循環的效果。
5. 【強制】禁止向HTML頁面輸出未經安全過濾或未正確轉義的使用者資料。
6. 【強制】表單、AJAX提交必須執行CSRF安全過濾。 說明:CSRF(Cross-site request forgery)跨站請求偽造是一類常見程式設計漏洞。對於有CSRF漏洞的應用程式/網站,攻擊者可以事先建構好URL,只要受害者使用者一訪問,後台便在使用者不知情情況下對資料庫中使用者參數進行相應修改。
7. 【強制】在使用平台資源,譬如簡訊、郵件、電話、下單、支付,必須實現正確的防重播限制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷、資損。

  說明:如註冊時發送驗證碼到手機,如果沒有限制次數和頻率,那麼可以利用此功能騷擾到其它用戶,並造成短信平台資源浪費。

#

以上是解析Java的編碼規範的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn