# 开发规范 > 来源: [阿里巴巴开发规约](https://github.com/alibaba/p3c.git) > ## 编程规范 ### 命名规约 #### 1.【强制】 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。 > 反例: ```text _name/__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_COUNT ------ #### 6.【强制】 抽象类命名使用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.【强制】 内部的实现类用Impl的后缀与接口区别。 > 正例: > > CacheServiceImpl实现CacheService接口。 ------ #### 13.【参考】 枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。 > 说明: > > 枚举其实就是特殊的常量类,且构造方法被默认强制是私有。 > > 正例: > > 枚举名字:DealStatusEnum > > 成员名称:SUCCESS/ UNKOWN_REASON。 ------ #### 14.【参考】 - 各层命名规约: - Service/DAO层方法命名规约 1. 获取单个对象的方法用get做前缀。 2. 获取多个对象的方法用list做前缀。 3. 获取统计值的方法用count做前缀。 4. 插入的方法用save(推荐)或insert做前缀。 5. 删除的方法用remove(推荐)或delete做前缀。 6. 修改的方法用update做前缀。 - 领域模型命名规约 1. 展示对象:xxxVO,xxx一般为网页名称。 ------ ### 常量定义 #### 1.【强制】 不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。 > 反例: ```vbnet String key = "Id#taobao_"+tradeId; cache.put(key, value); ``` ------ #### 2.【强制】 long或者Long初始赋值时,必须使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。 > 说明: ```java Long a = 2l; ``` > 写的是数字的21,还是Long型的2? ------ #### 3.【推荐】 不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存相关的常量放在类:CacheConsts下;系统配置相关的常量放在类:ConfigConsts下。 > 说明: > > 大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。 ------ #### 4.【推荐】 如果变量值仅在一个范围内变化用Enum类。如果还带有名称之外的延伸属性,必须使用Enum类,下面正例中的数字就是延伸信息,表示星期几。 > 正例: ```scss public Enum{ MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7); } ``` ------ ### 格式规约 #### 1.【强制】 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则: 1. 左大括号前不换行。 2. 左大括号后换行。 3. 右大括号前换行。 4. 右大括号后还有else等代码则不换行; 5. 表示终止右大括号后必须换行。 ------ #### 2.【强制】 左括号和后一个字符之间不出现空格;同样,右括号和前一个字符之间也不出现空格。 ------ #### 3.【强制】 **任何运算符左右必须加一个空格。** > 说明: > 运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号、三目运行符等。 ------ #### 4.【强制】 **针对这个规范,其实在开发工具中统一配置,不过扩展来看是否可以弄一个统一的配置文件进行开发工具导入,这样不需要每次都要配置,或者出现开发人员自行更改的情况。** 单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则: 1. 第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进,参考示例。 2. 运算符与下文一起换行。 3. 方法调用的点符号与下文一起换行。 4. 在多个参数超长,逗号后进行换行。 5. 在括号前不要换行,见反例。 > 正例: ```go StringBuffer sb = new StringBuffer(); //超过120个字符的情况下,换行缩进4个空格,并且方法前的点符号一起换行 sb.append("zi").append("xin")... .append("huang")... .append("huang")... .append("huang"); ``` > 反例: ```go StringBuffer sb = new StringBuffer(); //超过120个字符的情况下,不要在括号前换行 sb.append("zi").append("xin")...append ("huang"); //参数很多的方法调用可能超过120个字符,不要在逗号前换行 method(args1, args2, args3, ... , argsX); ``` > ECLIPSE中配置单行字符数长度 ![image](http://note.youdao.com/yws/api/personal/file/ADB2974FEC924B3B8491D801B1168DFC?method=download&shareKey=6485e2a0b4fa5048f14fa2fc1f2cdca3) ------ #### 5.【强制】 方法参数在定义和传入时,多个参数逗号后边**必须加空格**。 > 正例: > > 下例中实参的"a",后边必须要有一个空格。method("a", "b", "c"); ------ #### 6.【强制】 **IDE的textfileencoding设置为UTF-8;IDE中文件的换行符使用Unix格式,不要使用windows格式。 ** > 如何设置ECLIPSE的换行符格式 > > ![image](http://note.youdao.com/yws/api/personal/file/F417FCDF1C3F477989F2D39BF5434E03?method=download&shareKey=0a989ca4638f096d81478eed75fcec75) > > 如何在ECLIPSE编辑页面中显示\r\n换行符,分为统一配置以及引入插件后直接通过点击图标控制 > > ![image](http://note.youdao.com/yws/api/personal/file/0E15F6E9F6A94065854CA60DA5D0B881?method=download&shareKey=8bd48c5230dfb15e8b9c2f9158817bb1) > > ![image](http://note.youdao.com/yws/api/personal/file/6EF3D3D823004387951795212972A7F1?method=download&shareKey=48336c026580953d42945e243ec8043c) ------ #### 7.【推荐】 没有必要增加若干空格来使某一行的字符与上一行的相应字符对齐。 > 正例: ```java int a = 3; long b = 4L; float c = 5F; StringBuffer sb = new StringBuffer(); ``` > 说明: > > 增加sb这个变量,如果需要对齐,则给a、b、c都要增加几个空格,在变量比较多的情况下,是一种累赘的事情。 ------ #### 8.【强制】 **方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。** > 说明: > > 没有必要插入**多行空格**进行隔开。 ------ ### OOP规约 #### 1.【强制】 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。 ------ #### 2.【强制】 所有的覆写方法,必须加@Override注解。 > 反例: > > getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。 ------ #### 3.【强制】 相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。 > 说明: > > 可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程) > > 正例: > > public User getUsers(String type, Integer... ids) ------ #### 4.【强制】 对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。 > 说明: > > ++**这里需要注意,所有项目组开发或调用任何接口(其他三方API不算),不允许直接在现有的接口上进行修改,一定要按照这个规则进行,有个接口替换的过程,或者理解为接口并行,给调用方一个修改的时间。同样对于调用方而言要遵守下面的第五点要求。**++ ------ #### 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.【强制】 关于基本数据类型与包装数据类型的使用标准如下: - 所有的POJO类属性必须使用包装数据类型。 - RPC方法的返回值和参数必须使用包装数据类型。 - 所有的局部变量【推荐】使用基本数据类型。 > 说明: > > 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> generatetoString时,如果继承了另一个POJO类,注意在前面加一下super.toString。 > 说明: > > 在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。 ------ #### 13.【推荐】 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。 > 说明: ```rust 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方法中,尽量不要增加业务逻辑,增加排查问题的难度。 > 反例: ```kotlin public Integer getData(){ if(true) { return d ata + 100; } else { return data - 100; } } ``` > 注: > > 这个可能大部分人没有使用过,但是我之前在写直销回单打印的时候用过这种方式,当时是考虑调用的时候直接放入一个值,其他对应值根据一定的逻辑处理直接SET,以后这种方式要避免,不要在SETTER里面加上其他的逻辑处理。 ------ #### 17.【推荐】 循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。 > 反例: ```python String str = "start"; for(int i=0; i<100; i++){ str = str + "hello"; } ``` > 说明: > > 反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。 ------ #### 18.【推荐】 final可提高程序响应效率,声明成final的情况: - 不需要重新赋值的变量,包括类属性、局部变量。 - 对象参数前加final,表示不允许修改引用的指向。 - 类方法确定不允许被重写。 ------ #### 19.【推荐】 慎用Object的clone方法来拷贝对象。 > 说明: > > 对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现属性对象的拷贝。 ------ #### 20.【推荐】 类成员与方法访问控制从严: - 如果不允许外部直接通过new来创建对象,那么构造方法必须是private。 - 工具类不允许有public或default构造方法。 - 类非static成员变量并且与子类共享,必须是protected。 - 类非static成员变量并且仅在本类使用,必须是private。 - 类static成员变量如果仅在本类使用,必须是private。 - 若是static成员变量,必须考虑是否为final。 - 类成员方法只供类内部调用,必须是private。 - 类成员方法只对继承类公开,那么限制为protected。 > 说明: > > 任何类、方法、参数、变量,严控访问范围。过宽泛的访问范围,不利于模块解耦。思考:如果是一个private的方法,想删除就删除,可是一个public的Service方法,或者一个public的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心的。 ------ ### 集合处理 #### 1.【强制】 关于hashCode和equals的处理,遵循如下规则: - 只要重写equals,就必须重写hashCode。 - **因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。** - **如果自定义对象做为Map的键,那么必须重写hashCode和equals。** > 正例: > > String重写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用。 ------ #### 2.【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubListcannotbecasttojava.util.ArrayList; > 说明: > > subList返回的是ArrayList的内部类SubList,并不是ArrayList,而是ArrayList的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。 > > 注: > > 这个可能不会用到,注意一下。 ------ #### 3.【强制】 在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException异常。 ------ #### 4.【强制】 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。 > 反例: > > 直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。 > > 正例: ```vhdl List list = new ArrayList(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对象加锁。 > 反例: ```csharp List a = new ArrayList(); a.add("1"); a.add("2"); for (String temp : a) { if("1".equals(temp)){ a.remove(temp); } } ``` > 说明: > > 以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗? > > 正例: ```dart Iterator it = a.iterator(); while(it.hasNext()){ String temp = it.next(); if(删除元素的条件){ it.remove(); } } ``` ------ #### 8.【推荐】 使用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值组合集合。 ------ #### 9.【参考】 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。 > 说明: > > 稳定性指集合每次遍历的元素次序是一定的。有序性是指遍历的结果是按某种比较规则依次排列的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。 ------ #### 10.【参考】 利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。 ------ ### 控制语句 #### 1.【强制】 在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case 为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。 ------ #### 2.【强制】 在if/else/for/while/do语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if(condition)statements; > 注: > > 这是一个统一规范的问题,虽然说符合JAVA语法但是控制语句一定要有成对的“{}”。 ------ #### 3.【强制】 除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。 > 说明: > > 很多if语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢? > > 正例: > > //伪代码如下 ```java boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } ``` > 反例: ```erlang if ((file.open(fileName, "w") != null) && (...) || (...)) { ... } ``` > 注: > > 现有的很多判断逻辑都是一大堆,即使在开发环境下使用DEBUG也不宜读,这种方式会有一个落地值,方便定位问题。 ------ #### 4.【强制】 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作(这个try-catch是否可以移至循环体外)。 > 注: > > 所有人在开发时一定要考虑这个,或者在小组开发的过程中要统一定位try-catch的位置,不要随意抓捕,任何异常的信息都是有用的。 ------ #### 5.【强制】 接口入参保护,这种场景常见的是用于做批量操作的接口。 > 注: > > **接口入参保护指的是要对参数进行校验,大量批量操作非常消耗时间,所以一定要在接口这里做一个数量级和参数准确性的把控。具体的场景可以参照下面两个规则。** ------ #### 6.【参考】 方法中需要进行参数校验的场景: - 调用频次低的方法。 - 执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。 - 需要极高稳定性和可用性的方法。 - 对外提供的开放接口,不管是RPC/API/HTTP口。 - 敏感权限入口。 ------ #### 7.【参考】 方法中不需要参数校验的场景: - 极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查。 - 底层的方法调用频度都比较高,一般不校验。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般DAO层与Service层都在同一个应用中,部署在同一台服务器中,所以DAO的参数校验,可以省略。 - 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。 ------ ### 注释规约 #### 1.【强制】 类、类属性、类方法的注释必须使用Javadoc规范,使用 ```cpp /** 内容 */ ``` 格式,不得使用 ```bash //xxx ``` 方式。 > 说明: > > 在IDE编辑窗口中,Javadoc方式会提示相关注释,生成Javadoc可以正确输出相应注释;在IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。 ------ #### 2.【强制】 所有的抽象方法(包括接口中的方法)必须要用Javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。 > 说明: > > 对子类的实现要求,或者调用注意事项,请一并说明。 ------ #### 3.【强制】 所有的类都必须添加创建者信息。 ------ #### 4.【强制】 方法内部单行注释,在被注释语句上方另起一行。 ```cpp 使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐。 ``` ------ #### 5.【强制】 所有的枚举类型字段必须要有注释,说明每个数据项的用途。 ------ #### 6.【强制】 与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。 > 反例: > > “TCP连接超时”解释成“传输控制协议连接超时”,理解反而费脑筋。 ------ #### 7.【强制】 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。 > 说明: > > 代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,就失去了导航的意义。 ------ #### 8.【强制】 注释掉的代码尽量要配合说明,而不是简单的注释掉。 > 说明: - 代码被注释掉有两种可能性: - 后续会恢复此段代码逻辑。 - 永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。 ------ #### 9.【强制】 - 对于注释的要求: - 第一、能够准确反应设计思想和代码逻辑; - 第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。 ------ #### 10.【强制】 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。 > 反例: > > // put elephant into fridge > > put(elephant, fridge); > > 方法名put,加上两个有意义的变量名elephant和fridge,已经说明了这是在干什么,语义清晰的代码不需要额外的注释。 ------ #### 11.【强制】 **特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。** - 待办事宜(TODO):(标记人,标记时间,[预计处理时间])表示需要实现,但目前还未实现的功能。这实际上是一个Javadoc的标签,目前的Javadoc还没有实现,但已经被广泛使用。只能应用于类,接口和方法( 因为它是一个Javadoc标签)。 - 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])在注释中用FIXME标记某代码是错误的,而且不能工作,需要及时纠正的情况。 ------ ### 其它 #### 1.【强制】 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 > 说明: > > 不要在方法体内定义: > Pattern pattern = Pattern.compile(规则); > > 注: > > [参考1](http://blog.csdn.net/rubenyu/article/details/6591767) > > [参考2](http://www.cnblogs.com/sparkbj/articles/6207103.html) ------ #### 2.【强制】 ```scss 注意Math.random()这个方法返回是double类型,注意取值的范围0≤x<1(能够取到零值,注意除零异常), 如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。 ``` ------ #### 3.【强制】 获取当前毫秒数System.currentTimeMillis();而不是new Date().getTime(); > 说明: > > 如果想获取更加精确的纳秒级时间值,用System.nanoTime()。在JDK8中,针对统计时间等场景,推荐使用Instant类。 ------ #### 4.【推荐】 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。 > 注: > > 仅做参考,因现有系统业务的复杂性,如果需要实现这一点(降低内存的使用率)再做考虑。 ------ #### 5.【强制】 对于“明确停止使用的代码和配置”,如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾。 > 注: > 在以后使用GIT后,分支的管理会“重”起来,且无用的文件或片段可通过版本控制回溯,在保证业务逻辑的前提下不要保留“垃圾”。 ------ ## 异常日志 ### 异常处理 #### 1.【强制】 不要捕获Java类库中定义的继承自RuntimeException的运行时异常类,如:IndexOutOfBoundsException/ NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。 > 正例: > > if(obj != null) {...} > > 反例: > > try { obj.method() } catch(NullPointerException e){ > ... > } ------ #### 2.【强制】 对大段代码进行try-catch,这是不负责任的表现。catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。 > 注: > > 这个在现有系统中是存在的,为了方便处理,后面每个业务系统开发小组自行决定一个统一(公用)的异常抓捕位置,这样既实现了这个规则也在一定情况下让代码变得整洁有序。 ------ #### 3.【强制】 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。 > 注: > > 这个在之前有提到过,任何异常信息都是有用的,不要随意进行一个抓捕,要配合上面的规则统一化管理。 > > @see:控制语句#第五点 ------ #### 4.【强制】 有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。 ------ #### 5.【强制】 finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 > 说明: > > 如果JDK7,可以使用try-with-resources方式。 ------ #### 6.【强制】 不能在finally块中使用return,finally块中的return返回后方法结束执行,不会再执行try块中的return语句。 ------ #### 7.【强制】 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 > 说明: > > 如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。 > > 注: > > 这个在TA的设计中有考虑,所有开发人员要考虑异常抓捕的范围,由小到大,最后有一个Exception最大的。 ------ #### 8.【强制】 方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。调用方需要进行null判断防止NPE问题。 > 说明: > > 本规约明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回null的情况。 > > 注: > > 这个在以后的开发过程中会经常遇到,之前看QF实现代码的时候就发现这个问题了,应该是刘泽坤那面写的,之前没有一个肯定的答复,现在参照这个规则实现,在注释上进行充分说明。 ------ #### 9.【推荐】 防止NPE,是程序员的基本修养,注意NPE产生的场景: - 返回类型为包装数据类型,有可能是null,返回int值时注意判空。 > 反例: ```csharp public int f(){ return Integer 对象 }; ``` > 如果为null,自动解箱抛NPE。 - 数据库的查询结果可能为null。 - 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 - 远程调用返回对象,一律要求进行NPE判断。 - 对于Session中获取的数据,建议NPE检查,避免空指针。 - 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。 > 注: > > **这个要结合上一个规则来看,我在这做一个整理(可以讨论一下),接口(方法)提供方无需考虑是否返回的是NULL值,接口(方法)调用方必须要考虑接口(方法)返回值为空的情况(这个在提供方的注释或API上要有详细的说明,在什么情况下会有NULL返回,餐勺上面的规则)。** ------ #### 10.【强制】 在代码中使用“抛异常”还是“返回错误码”,对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑 使用Result方式,封装isSuccess、“错误码”、“错误简短信息”。 > 说明: > > 关于RPC方法返回方式使用Result方式的理由: - 使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 - 如果不加栈信息,只是new自定义异常,加入自己的理解的errormessage,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。 > **注: > > 错误代码的规范也要统一起来并形成错误代码表,作为日志跟踪、问题排查、系统交互等后期工作的前置条件,之后这个丰富起来才能说明系统的健壮性。 > > 系统的健壮性不是实现了多少功能,也不是多么的牛,而是你可控的范围有多少,错误代码表就是系统健壮性的一个量级或文档化的实现。** ------ #### 11.【强制】 定义时区分unchecked/ checked异常,避免直接使用RuntimeException抛出,更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的 自定义异常,如:DAOException/ ServiceException等。 ------ #### 12.【参考】 避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 > 说明: > > 随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。 > > 正例: > > 一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取: > > private boolean checkParam(DTO dto) > **注: > > 这个作为参考保留,是以后的一个方向,因为现有业务逻辑的原因,不能也没有时间进行细化,所以业务层面的代码还是要有冗余的,但是作为工具层面的代码一定要考虑这个DRY原则。** ------ ## 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_ 即uniquekey;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类型。 > 注: > > **这个规则需要详细讨论,就参照现有sofa那面的内容** ------ #### 10.【强制】 表的命名最好是加上“业务名称_表的作用”。 > 正例: > > tiger_task/ tiger_reader/ mpp_config ------ #### 11.【强制】 库名与应用名称尽量一致。 ------ #### 12.【强制】 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。 ------ #### 13.【推荐】 字段允许适当冗余,以提高性能,但是**必须考虑数据同步的情况**。冗余字段应遵循: - 不是频繁修改的字段。 - 不是varchar超长字段,更不能是text字段。 > 正例: > > **商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。** ------ #### 14.【推荐】 单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。 > 说明: > > 如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。 ------ #### 15.【参考】 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。 > 正例: > > 人的年龄用unsigned tinyint(表示范围0-255,人的寿命不会超过255岁 > );海龟就必须是smallint,但如果是太阳的年龄,就必须是int;如果是所有恒星的年龄都加起来,那么就必须使用bigint。 ------ ### 索引规约 #### 1.【强制】 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。 > 说明: > > 不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。 ------ #### 2.【强制】 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。 > 说明: > > 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。 ------ #### 3.【强制】 **页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。** > 说明: > > 索引文件具有B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。 > 注: > > ++**这个规则建议不进作为开发及数据库设计规则,也要作为需求产品设计规则,而且现有需求即使只支持右模糊也无任何影响。**++ ------ ### 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问题: ```sql SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table; ``` ------ #### 4.【强制】 使用ISNULL()来判断是否为NULL值。注意:NULL与任何值的直接比较都为NULL。 > 说明: - NULL<>NULL的返回结果是NULL,而不是false。 - NULL=NULL的返回结果是NULL,而不是true。 - 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编码,那么字符计数方法注意: > 说明: ```sql SELECT LENGTH("轻松工作");返回为12 SELECT CHARACTER_LENGTH("轻松工作");返回为4 ``` > 如果要使用表情,那么使用utfmb4来进行存储,注意它与utf-8编码的区别。 > 注: > > 以后如无特殊要求,统一使用utf-8编码。 ------ #### 11.【强制】 TRUNCATE TABLE比DELETE速度快,且使用的系统和事务日志资源少,但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此语句。 > 说明: > > TRUNCATE TABLE在功能上与不带WHERE子句的DELETE语句相同。 ------ ### ORM规约 #### 1.【强制】 在表查询中,一律不要使用* 作为查询的字段列表,需要哪些字段必须明确写明。 > 说明: - 增加查询分析器解析成本。 - 增减字段容易与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); > 注: > > **这个平台那面研究一下是否现在使用的mybatis版本也是这样,后面提供封装方法的时候要注意。** ------ #### 6.【强制】 更新数据表记录时,必须同时更新记录对应的gmt_modified字段值为当前时间。 ------ #### 7.【强制】 不要写一个大而全的数据更新接口,传入为POJO类,不管是不是自己的目标更新字段,都进行update table set c1=value1,c2=value2,c3=value3; 这是不对的。 执行SQL时,尽量不要更新无改动的字段 一是易出错; 二是效率低; 三是binlog增加存储。 ------ #### 8.【强制】 @Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。 ------ ## 工程规约 ### 二方库规约 #### 1.【强制】 定义GAV遵从以下规则: - GroupID格式:com.{公司/BU}.业务线.[子业务线],最多4级。 > 说明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress等BU一级;子业务线可选。 > > 正例: > > com.taobao.jstorm或com.alibaba.dubbo.register - ArtifactID格式:产品线名-模块名。语义不重复不遗漏,先到仓库中心去查证一下。 > 正例: > > dubbo-client/ fastjson-api/ jstorm-tool - Version:详细规定参考下方。 ------ #### 2.【强制】 二方库版本号命名方式:主版本号.次版本号.修订号 - 主版本号:当做了不兼容的API修改,或者增加了能改变产品方向的新功能。 - 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。 - 修订号:修复bug,没有修改方法签名的功能加强,保持API兼容性。 > 说明: > > 起始版本号必须为:1.0.0,而不是0.0.1 ------ #### 3.【强制】 依赖于一个二方库群时,必须定义一个统一版本变量,避免版本号不一致。 > 说明: > > 依赖springframework-core,-context,-beans,它们都是同一个版本,可以定义一个变量来保存版本:${spring.version},定义依赖的时候,引用该版本。 ------ #### 4.【强制】 禁止在子项目的pom依赖中出现相同的GroupId,相同的ArtifactId,但是不同的Version。 > 说明: > > 在本地调试时会使用各子项目指定的版本号,但是合并成一个war,只能有一个版本号出现在最后的lib目录中。曾经出现过线下调试是正确的,发布到线上出故障的先例。 ------ #### 5.【参考】 为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则: - 精简可控原则。移除一切不必要的API和依赖,只包含ServiceAPI、必要的领域模型对象、Utils类、常量、枚举等。如果依赖其它二方库,尽量是provided引入,让二方库使用者去依赖具体版本号;无log具体实现,只依赖日志框架。 - 稳定可追溯原则。每个版本的变化应该被记录,二方库由谁维护,源码在哪里,都需要能方便查到。除非用户主动升级版本,否则公共二方库的行为不应该发生变化。