目录
编码规约
(一)命名风格:
1、杜绝完全不规范的缩写,严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
2、代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
3、类名使用 大驼峰风格,但以下情形例外:DO / BO / DTO / VO / AO / PO 等。
4、方法名、参数名、成员变量、局部变量都统一使用 小驼峰 风格。
5、常量命名全部大写,单词间用下划线隔开,名字长点不要紧,一定要做到意思清楚明白。
6、包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。
7、代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
8、类型与中括号紧挨相连来定义数组。例:int[] arrayDemo;
9、抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类名开始,以Test结尾。
(二)常量定义:
1、不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
反例:String key = "Id#taobao_"+tradeId;
2、给Long类型变量初始化赋值时要使用大写的L,不能是小写的 l,小写的 l 容易跟数字1混淆,降低可读性。
3、不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。
4、常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
(三)代码格式:
1、大括号的使用约定:
如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块需要满足以下规则:
(1)左大括号前不换行。
(2)左大括号后换行。
(3)右大括号前换行。
(4)右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
7、方法参数在定义和传入时,多个参数逗号后边必须加空格。
例:下例中实参的"a",后边必须要有一个空格。
method("a", "b", "c");
8、IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式。
9、不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
(四)OOP规约:
1、避免通过一个类的对象引用访问此类的静态变量或静态方法,直接用类名来访问即可。(通过类的对象调用静态变量或方法的话会增加编译成本)
2、所有的覆写方法,必须加@Override注解。
3、外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
4、不能使用过时的类或方法。
5、Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
正例:"test".equals(object);
反例:object.equals("test");
6、所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。
7、关于基本数据类型与包装数据类型的使用标准如下:
【强制】所有的POJO类属性必须使用包装数据类型。
【强制】RPC方法的返回值和参数必须使用包装数据类型。
说明:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
【推荐】所有的局部变量使用基本数据类型。
8、POJO类必须写toString方法。使用IDE中的工具:source> generate toString 时,如果继承了另一个POJO类,注意在前面加一下super.toString。
9、构造方法里面禁止加入任何业务逻辑。
10、循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。(对照String)
11、final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
(1)不允许被继承的类,如:String类。
(2)不允许修改引用的域对象,如:POJO类的域变量。
(3)不允许被重写的方法,如:POJO类的setter方法。
(4)不允许运行过程中重新赋值的局部变量。
(5)避免上下文重复使用一个变量,使用final描述可以强制重新定义一个变量,方便更好地进行重构。
(五)集合处理
1、关于hashCode和equals的处理,遵循如下规则:
(1)因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
(2)如果自定义对象作为Map的键,那么必须重写hashCode和equals。
2、 ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException (类型转换异常)异常。
3、在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生ConcurrentModificationException 异常。
4、使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。
5、使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
6、不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator 方式。
(六)控制语句
1、在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使空代码。
2、在if/else/for/while/do语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:if (condition) statements;
3、在高并发场景中,避免使用”等于”判断作为中断或退出的条件。如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
4、尽量不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
5、避免采用取反逻辑运算符。取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
(七)注释规约
1、类、类属性、类方法的注释必须使用Javadoc规范,使用/**内容*/格式,不得使用// xxx方式。
示例:
类(types)注释模板:
/**
* @ClassName: ${type_name}
* @Description:${todo}
* @author: ******
* @date: ${date} ${time}
*/
方法(method)注释模板:
/**
* @MethodName: ${enclosing_method}
* @Description: ${todo}
* @param ${tags}
* @return ${return_type}
* @throws
* @date ${date} ${time}
* @author ******
*/
2、所有的抽象方法(包括接口中的方法)必须要用Javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
3、所有的类都必须添加创建者和创建日期。
4、方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
5、与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。
6、代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
7、谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
8、好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
(八)其它
1、在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
2、注意 Math.random() 这个方法返回是double类型,注意取值的范围 0≤x<1(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。
3、不要在视图模板中加入任何复杂的逻辑。
4、任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
5、及时清理不再使用的代码段或配置信息。
异常日志
(一)异常处理
1、异常不要用来做流程控制,条件控制。
2、catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。
3、捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
4、有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。
5、不要在finally块中使用return。
6、捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
7、注意防止空指针异常。
8、避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
(二)日志规约
1、日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。
2、避免重复打印日志,浪费磁盘空间,务必在log4j.xml中设置additivity=false。
3、异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws往上抛出。
4、谨慎地记录日志。生产环境禁止输出debug日志;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
5、可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
安全规约
1、隶属于用户个人的页面或者功能必须进行权限控制校验。
说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。
2、用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
说明:个人手机号码显示为:158****9119,隐藏中间4位,防止隐私泄露。
3、用户请求传入的任何参数必须做有效性验证。
4、禁止向HTML页面输出未经安全过滤或未正确转义的用户数据。
5、表单、AJAX提交必须执行CSRF安全过滤。(CSRF跨站域伪造请求)
6、在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷导致资损。
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
7、发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。
MySQL数据库
(一)建表规约
1、表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint( 1表示是,0表示否)。
说明: unsigned tinyint类型只能存储0到255的整数,不能存储负数,tinyint的范围是-128到127的整数。
正例:表达逻辑删除的字段名is_deleted,1 表示删除,0 表示未删除。
2、表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
3、表名不使用复数名词。
说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量。
4、禁用保留字,如desc、range、match、delayed等。
5、主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名。
说明:pk_ 即primary key;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的类型均为datetime类型,前者表示创建时间,后者表示更新时间。
10、表的命名最好是加上“业务名称_表的作用”。
11、库名与应用名称尽量一致。
12、如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
二)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值。
5、在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。
6、数据订正(特别是删除、修改记录操作)时,要先select,避免出现误删除,确认无误才能执行更新语句。
7、 in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内。
8、 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但TRUNCATE 无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此语句。
(三)ORM映射
1、在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。
2、 POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射。
说明:参见定义POJO类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。在MyBatis Generator生成的代码中,需要进行对应的修改。
3、不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应。
4、 sql.xml配置参数使用:#{},#param# 不要使用${} 此种方式容易出现SQL注入。
5、 iBATIS自带的queryForList(String statementName,int start,int size)不推荐使用。
说明:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再通过subList 取start,size的子集合。
正例:Map<String, Object> map = new HashMap<String, Object>(); map.put("start", start); map.put("size", size);
6、不允许直接拿HashMap与Hashtable作为查询结果集的输出。
说明:resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控。
7、更新数据表记录时,必须同时更新记录对应的gmt_modified字段值为当前时间。
8、 @Transactional事务不要滥用。事务会影响数据库的QPS(每秒查询率),另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
相关资料:
https://www.jianshu.com/p/bc8fed863eca 白话阿里巴巴Java开发手册(编程规约)
https://www.jianshu.com/p/5b6d180bd1c2 白话阿里巴巴Java开发手册(异常日志)
https://www.jianshu.com/p/9528c4ea1504 白话阿里巴巴Java开发手册(安全规约)
https://github.com/alibaba/p3c/blob/master/eclipse-plugin/README_cn.md 阿里巴巴 Java 开发规约插件