/Effectice-Coding

个人对阿里巴巴Java开发手册 有关初学者必学的总结

Effectice-Coding

个人对阿里巴巴Java开发手册 有关初学者必学的一些常用的规范的总结 不涉及并发、异常处理等 官方的很全面

源自:

一、编程规约

(一)命名风格

  1. 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
    反例_name / __name / $name / name_ / name$ / name__

  2. 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
    说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用。
    正例:alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
    反例:DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3

  3. 【强制】类名使用UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO / AO / PO等。
    正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
    反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

  4. 【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。
    正例: localValue / getHttpMessage() / inputUserId

  5. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
    正例:MAX_STOCK_COUNT
    反例:MAX_COUNT

  6. 【强制】类型与中括号紧挨相连来定义数组。
    正例:定义整形数组int[] arrayDemo;
    反例:在main参数中,使用String args[]来定义。

  7. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
    正例:应用工具类包名为com.alibaba.ai.util、类名为MessageUtils(此规则参考spring的框架结构)

  8. 【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
    正例:从远程仓库拉取代码的类命名为PullCodeFromRemoteRepository。
    反例:变量int a; 的随意命名方式。
    个人注:很常见的问题, 在算法题上可以允许反例,但再大一些的程序里就不要出现反例了,你能保证一个月后再看这个变量还知道他的意义吗

  9. 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
    说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
    正例

public class OrderFactory;
public class LoginProxy;
public class ResourceObserver; 
  1. 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
    正例:接口方法签名void f(); 接口基础常量String COMPANY = "alibaba";
    反例:接口方法定义public abstract void f();
    说明:JDK8中接口允许有默认实现,那么这个default方法,是对所有实现类都有价值的默认实现。

  2. 接口和实现类的命名有两套规则:
    1)【强制】对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别。
    正例:CacheServiceImpl实现CacheService接口。

  3. 【参考】各层命名规约:
    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?

(三)代码格式

  1. 【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:
    1) 左大括号前不换行。
    2) 左大括号后换行。
    3) 右大括号前换行。
    4) 右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
  2. 【强制】 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。详见第5条下方正例提示。
    反例
if (空格a == b空格)
  1. 【强制】if/for/while/switch/do等保留字与括号之间都必须加空格。

  2. 【强制】任何二目、三目运算符的左右两边都需要加一个空格。
    说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。


    正例: (涉及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");
               // 在右大括号后直接结束,则必须换行
           }
       }
    
  3. 【强制】单行字符数限制不超过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); 
  1. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
    正例:下例中实参的"a",后边必须要有一个空格。
method("a", "b", "c"); 
  1. 【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式。

  2. 【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
    说明:没有必要插入多个空行进行隔开。

(四) OOP规约

  1. 【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

  2. 【强制】所有的覆写方法,必须加@Override注解。
    说明:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

  3. 【强制】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
    正例:"test".equals(object);
    反例:object.equals("test");
    说明:推荐使用java.util.Objects#equals(JDK7引入的工具类) 个人注:反例是一个十分常见的错误

  4. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。
    说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

  5. 【强制】定义DO/DTO/VO等POJO类时,不要设定任何属性默认值
    反例:POJO类的gmtCreate默认值为new Date();但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。

  6. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。

  7. 【强制】POJO类必须写toString方法。使用IDE中的工具:source> generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString。
    说明:在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。

  8. 【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。
    反例

  public Integer getData() {      
      if (condition) {  
        return this.data + 100;  
      } else { 
        return this.data - 100; 
      }  
  }
  1. 【推荐】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
    说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
    反例
  String str = "start";
  for (int i = 0; i < 100; i++) {
      str = str + "hello";      
  }

(七) 控制语句

  1. 【强制】在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使空代码。
  2. 【强制】在if/else/for/while/do语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:
if (condition) statements;
  1. 【推荐】除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
    说明:很多if语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
    正例
// 伪代码如下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); 
if (existed) {    
   ... 
}  

反例

if ((file.open(fileName, "w") != null) && (...) || (...)) {     
  ... 
}
  1. 【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作(这个try-catch是否可以移至循环体外)。
  2. 【推荐】避免采用取反逻辑运算符。
    说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
    正例:使用if (x < 628) 来表达 x 小于628。
    反例:使用if (!(x >= 628)) 来表达 x 小于628。

五、MySQL数据库

(一) 建表规约

  1. 【强制】表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint( 1表示是,0表示否)。
    说明:任何字段如果为非负数,必须是unsigned
    正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除。

  2. 【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
    说明:MySQL在Windows下不区分大小写,但在Linux下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
    正例:aliyun_admin,rdc_config,level3_name
    反例:AliyunAdmin,rdcConfig,level_3_name

  3. 【强制】表名不使用复数名词。
    说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于DO类名也是单数形式,符合表达习惯。

  4. 【强制】禁用保留字,如descrangematchdelayed等,请参考MySQL官方保留字。

  5. 【强制】如果存储的字符串长度几乎相等,使用char定长字符串类型。

  6. 【推荐】表的命名最好是加上“业务名称_表的作用”。
    正例:alipay_task / force_project / trade_config
    个人注:在学校做课设或平时的小项目时 方便管理 如:软件工程_用户表、数据库原理_用户表

  7. 【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
    正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

对象 年龄区间 类型 字节
150岁之内 unsigned tinyint 1
数百岁 unsigned smallint 2
恐龙化石 数千** unsigned int 4
太阳 约50亿年 unsigned bigint 8

(三) SQL语句

  1. 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
    说明:1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。
    个人注: 想要什么查什么,平时使用select都是 * ,这是一个很常见的习惯问题。
  2. 【强制】不要使用count(列名)或count(常量)来替代count(),count()是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。
    说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。
  3. 【强制】count(distinct col) 计算该列除NULL之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
  4. 【强制】当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题。
    正例:可以使用如下方式来避免sum的NPE问题:
SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table; 
  1. 【强制】使用ISNULL()来判断是否为NULL值。 说明:NULL与任何值的直接比较都为NULL。
    1) NULL<>NULL的返回结果是NULL,而不是false
    2) NULL=NULL的返回结果是NULL,而不是true
    3) NULL<>1的返回结果是NULL,而不是true

六、工程结构

(一) 应用分层

  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接口。
  1. 【参考】分层领域模型规约:
  • 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类来传输。