阿里Java开发规范

2022-10-20 08:16:54

一、编程规范

(一)命名规范

  1. 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束
    反例:_name / _name / $name / name_ / name$ / name_
  2. 所有编程相关的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式
  3. 类名使用UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID等。(DO等名词解释请见附录)
    正例ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
    反例forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion
  4. 方法名参数名成员变量局部变量都统一使用lowerCamelCase风格。
    正例localValue / getHttpMessage() / inputUserId
  5. 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
    正例MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
    反例MAX_COUNT / EXPIRED_TIME
  6. 抽象类命名使用AbstractBase开头;
    异常类命名使用Exception结尾;
    测试类命名以它要测试的类的名称开始,以Test结尾。
  7. 类型与中括号紧挨相连来表示数组。
    正例:定义整形数组int[] arrayDemo
    反例:在main参数中,使用String args[]来定义。
  8. POJO类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误。
    说明:在本文MySQL规约中的建表约定第一条,表达是与否的变量采用is_xxx的命名方式,所以,需要在<resultMap>设置从is_xxx到xxx的映射关系。
  9. 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式
    正例:应用工具类包名为com.alibaba.ei.kunlun.aap.util、类名为MessageUtils
  10. 避免子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可理解性降低。
    说明:子类、父类成员变量名相同,即使是public类型的变量也能够通过编译,另外,局部变量在同一方法内的不同代码块中同名也是合法的,这些情况都要避免。对于非setter/getter的参数名称也要避免与成员变量名称相同。
  11. 杜绝完全不规范的缩写,避免望文不知义。
    反例AbstractClass“缩写”成AbsClasscondition“缩写”成condiFunction缩写”成Fu,此类随意缩写严重降低了代码的可阅读性。
  12. 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达。
  13. 常量变量的命名时,表示类型的名词放在词尾,以提升辨识度。
    正例startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
    反例startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
  14. 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
    说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
    正例: public class OrderFactory; public class LoginProxy; public class ResourceObserver;
  15. 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,确定与接口方法相关,并且是整个应用的基础常量。
    正例:接口方法签名void commit(); 接口基础常量String COMPANY = "alibaba";
    反例:接口方法定义public abstract void f();
    说明:JDK8中接口允许有默认实现,那么这个default方法,是对所有实现类都有价值的默认实现。
  16. 接口和实现类的命名有两套规则:
    1)对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口内部的实现类Impl后缀与接口区别。
    正例CacheServiceImpl实现CacheService接口。
    2)如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able的形容词)。
    正例AbstractTranslator实现Translatable接口。
  17. 各层命名规约:
    A) Service/DAO层方法命名规约
         1) 获取单个对象的方法用get做前缀。
         2) 获取多个对象的方法用list做前缀,复数结尾,如:listObjects
         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. 如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;
    如果是非空代码块则:
         1) 左大括号前不换行。
         2) 左大括号后换行。
         3) 右大括号前换行。
         4) 右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
  2. 左小括号和右边相邻字符之间不出现空格;
    右小括号和左边相邻字符之间也不出现空格;
    而左大括号前需要加空格
    反例if ( a == b )
  3. if/for/while/switch/do等保留字与括号之间都必须加空格
  4. 任何二目、三目运算符的左右两边都需要加一个空格。
    说明:包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
  5. 采用4个空格缩进禁止使用Tab字符。
    正例
publicstaticvoidmain(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");// 在右大括号后直接结束,则必须换行}}
  1. 在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
    正例
double first=3.2d;int second=(int)first+2;
  1. 单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
    1)第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进,参考示例。
    2)运算符与下文一起换行。
    3)方法调用的点符号与下文一起换行。
    4)方法调用中的多个参数需要换行时,在逗号后进行。
    5)在括号前不要换行
    正例
StringBuilder sb=newStringBuilder();// 超过120个字符的情况下,换行缩进4个空格,并且方法前的点号一起换行
    sb.append("yang").append("hao")....append("chen")....append("chen")....append("chen");

反例

StringBuilder sb=newStringBuilder();// 超过120个字符的情况下,不要在括号前换行
    sb.append("you").append("are")...append("lucky");// 参数很多的方法调用可能超过120个字符,逗号后才是换行处method(args1, args2, args3,..., argsX);
  1. 方法参数在定义和传入时,多个参数逗号后面必须加空格。
    正例:下例中实参的args1,后边必须要有一个空格。
method(args1, args2, args3);
  1. IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式。
  2. 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
    说明:任何情形,没有必要插入多个空行进行隔开。

(三)OOP规约

  1. 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
  2. 所有的覆写方法,必须加@Override注解。
  3. Objectequals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
    正例"test".equals(object);
    反例object.equals("test");
    说明:推荐使用JDK7引入的工具类java.util.Objects#equals(Object a, Object b)
  4. 所有整型包装类对象之间值的比较,全部使用equals方法比较
    说明:对于Integer var = ? 在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
  5. 浮点数之间的等值判断基本数据类型不能用==来比较,包装数据类型不能用equals来判断
    说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数
    反例
float a=1.0F-0.9F;float b=0.9F-0.8F;if(a== b){// 预期进入此代码块,执行其它业务逻辑// 但事实上a==b的结果为false}Float x=Float.valueOf(a);Float y=Float.valueOf(b);if(x.equals(y)){// 预期进入此代码块,执行其它业务逻辑// 但事实上equals的结果为false}

正例

(1) 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。float a=1.0F-0.9Ffloat b=0.9F-0.8F;float diff=1e-6F;if(Math.abs(a- b)< diff){System.out.println("true");}(2) 使用BigDecimal来定义值,再进行浮点数的运算操作。BigDecimal a=newBigDecimal("1.0");BigDecimal b=newBigDecimal("0.9");BigDecimal c=newBigDecimal("0.8");BigDecimal x= a.subtract(b);BigDecimal y= b.subtract(c);if(x.compareTo(y)==0){System.out.println("true");}
  1. 定义数据对象DO类时,属性类型要与数据库字段类型相匹配Java实体类的属性类型与数据库表字段类型对应表
    正例:数据库字段的bigint必须与类属性的Long类型相对应。
    反例:某个案例的数据库表id字段定义类型bigint unsigned,实际类对象属性为Integer,随着id越来越大,超过Integer的表示范围而溢出成为负数。
  2. 关于基本数据类型与包装数据类型的使用标准如下:(基本数据类型及其包装类)
    1)所有的POJO类属性必须使用包装数据类型。
    2)RPC方法的返回值和参数必须使用包装数据类型。
    3)所有的局部变量使用基本数据类型。
    说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
    正例:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。
    反例:某业务的交易报表上显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这是不合理的,应该显示成中划线-。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败,异常退出。
  3. 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值。
  4. 循环体内,字符串的连接方式,使用StringBuilderappend方法进行扩展。
    说明:下例中,反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
    反例
String str="start";for(int i=0; i<100; i++){
        str= str+"hello";}

(四)日期时间

  1. 日期格式化时,传入pattern中表示年份统一使用小写的y。
    说明:日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year(JDK7之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。
    正例:表示日期和时间的格式如下所示:
newSimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  1. 在日期格式中分清楚大写的M和小写的m,大写的H和小写的h分别指代的意义。
    说明:日期格式中的这两对字母表意如下:
    1) 表示月份是大写的M;
    2) 表示分钟则是小写的m;
    3) 24小时制的是大写的H;
    4) 12小时制的则是小写的h。
  2. 获取当前毫秒数:System.currentTimeMillis(); 而不是new Date().getTime()
    说明:如果想获取更加精确的纳秒级时间值,使用System.nanoTime的方式。在JDK8中,针对统计时间等场景,推荐使用Instant类。

(五)集合处理

  1. 判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是size()==0的方式
  2. 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
    正例
List<String> list=newArrayList<>();
    list.add("1");
    list.add("2");Iterator<String> iterator= list.iterator();while(iterator.hasNext()){String item= iterator.next();if(删除元素的条件){
            iterator.remove();}}

反例

for(String item: list){if("1".equals(item)){
            list.remove(item);}}

(六)前后端规约

  1. 前后端交互的API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。
    说明
    1) 协议:生产环境必须使用HTTPS。
    2) 路径:每一个API需对应一个路径,表示API具体的请求地址:
         a) 代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已经表达动作意义。
         b) URL路径不能使用大写,单词如果需要分隔,统一使用下划线。
         c) 路径禁止携带表示请求内容类型的后缀,比如".json",“.xml”,通过accept头表达即可。
    3) 请求方法:对具体操作的定义,常见的请求方法如下:
         a)GET:从服务器取出资源。
         b)POST:在服务器新建一个资源。
         c)PUT:在服务器更新资源。
         d)DELETE:从服务器删除资源。
    4) 请求内容:URL带的参数必须无敏感信息或符合安全要求;body里带参数时必须设置Content-Type
    5) 响应体:响应体body可放置多种数据类型,由Content-Type头来确定。
  2. 前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}。
    说明:此条约定有利于数据层面上的协作更加高效,减少前端很多琐碎的null判断。
  3. 在前后端交互的JSON格式数据中,所有的key必须为小写字母开始的lowerCamelCase风格,符合英文表达习惯,且表意完整。
    正例errorCode / errorMessage / assetStatus / menuList / orderList / configFlag
    反例ERRORCODE / ERROR_CODE / error_message / error-message / errormessage / ErrorMessage / msg
  4. 对于需要使用超大整数的场景,服务端一律使用String字符串类型返回,禁止使用Long类型。
  5. HTTP请求通过URL传递参数时,不能超过2048字节。
  6. HTTP请求通过body传递内容时,必须控制长度,超出最大长度后,后端解析会出错。
    说明:nginx默认限制是1MB,tomcat默认限制为2MB,当确实有业务需要传较大内容时,可以通过调大服务器端的限制
  7. 在翻页场景中,用户输入参数的小于1,则前端返回第一页参数给后端;后端发现用户输入的参数大于总页数,直接返回最后一页。

二、MySQL数据库

(一)建表规约

  1. 表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint1表示是,0表示否)。
    说明:任何字段如果为非负数,必须是unsigned
    注意:POJO类中的任何布尔类型的变量,都不要加is前缀,所以,需要在<resultMap>设置从is_xxx到Xxx的映射关系。数据库表示是与否的值,使用tinyint类型,坚持is_xxx的命名方式是为了明确其取值含义与取值范围。
    正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除。
  2. 表名、字段名必须使用小写字母或数字禁止出现数字开头禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
    说明:MySQL在Windows下不区分大小写,但在Linux下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
    正例aliyun_admin,rdc_config,level3_name
    反例AliyunAdmin,rdcConfig,level_3_name
  3. 表名不使用复数名词
    说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于DO类名也是单数形式,符合表达习惯。
  4. 禁用保留字,如desc、range、match、delayed等,请参考MySQL官方保留字
  5. 主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名。
    说明:pk_ 即primary key;uk_ 即 unique key;idx_ 即index的简称。
  6. 小数类型decimal禁止使用float和double
    说明:在存储的时候,floatdouble 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过decimal 的范围,建议将数据拆成整数和小数并分开存储。
  7. 如果存储的字符串长度几乎相等,使用char定长字符串类型。
  8. varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
  9. 表必备三字段id, create_time, update_time
    说明:其中id必为主键,类型为bigint unsigned、单表时自增、步长为1。create_time, update_time的类型均为datetime类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。

(二)索引规约

  1. 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
    说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
  2. 超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。
    说明:即使双表join也要注意表索引、SQL性能。
  3. varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
    说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
  4. 利用延迟关联或者子查询优化超多分页场景。
    说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
    正例:先快速定位需要获取的id段,然后再关联:
SELECT t1.*FROM1as t1,(select idfrom1where 条件LIMIT100000,20)as t2where t1.id=t2.id

(三)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 IFNULL(SUM(column), 0) FROM table;
  4. 使用ISNULL()来判断是否为NULL值。
    说明NULL与任何值的直接比较都为NULL
    1)NULL<>NULL的返回结果是NULL,而不是false
    2)NULL=NULL的返回结果是NULL,而不是true
    3)NULL<>1的返回结果是NULL,而不是true
    反例:在SQL语句中,如果在null前换行,影响可读性。
    select * from table where column1 is null and column3 is not null;
    ISNULL(column)是一个整体,简洁易懂。从性能数据上分析,ISNULL(column)执行效率更快一些。
  5. 代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。
  6. 不得使用外键与级联,一切外键概念必须在应用层解决。 说明:(概念解释)学生表中的student_id是主键,那么成绩表中的student_id则为外键。如果更新学生表中的student_id,同时触发成绩表中的student_id更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
  7. 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
  8. **数据订正(**特别是删除或修改记录操作)时,要先select,避免出现误删除,确认无误才能执行更新语句。
  9. 对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。
    说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。
    正例select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;
    反例:在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出1052异常:Column 'name' in field list is ambiguous

(四)ORM映射

  1. 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
    说明
    1)增加查询分析器解析成本。
    2)增减字段容易与resultMap配置不一致。
    3)无用字段增加网络消耗,尤其是text类型的字段。
  2. POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射。
    说明:参见定义POJO类以及数据库字段定义规定,在sql.xml增加映射,是必须的。
  3. 不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义<resultMap>;反过来,每一个表也必然有一个<resultMap>与之对应。 说明:配置映射关系,使字段与DO类解耦,方便维护。
  4. sql.xml配置参数使用:#{},#param# 不要使用${} 此种方式容易出现SQL注入。
  5. iBATIS自带的queryForList(String statementName,int start,int size)不推荐使用。
    说明:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再通过subListstart,size的子集合。
    正例
Map<String,Object> map=newHashMap<>(16);
    map.put("start", start);
    map.put("size", size);
  1. 不允许直接拿HashMapHashtable作为查询结果集的输出。
  2. 更新数据表记录时,必须同时更新记录对应的update_time字段值为当前时间。
  3. 不要写一个大而全的数据更新接口。传入为POJO类,不管是不是自己的目标更新字段,都进行update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增加binlog存储。

附录

在这里插入图片描述
注:上述摘选自阿里巴巴《Java开发手册(嵩山版)》的部分内容
如有疑问请自行点击链接下载。

  • 作者:不会写代码的小周
  • 原文链接:https://blog.csdn.net/qq_45397526/article/details/122784569
    更新时间:2022-10-20 08:16:54