Java编码规范

Posted by Yezhiwei on November 14, 2017

OOP

  • 类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO。

  • 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

  • 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。

  • POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。

  • 包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。

  • 杜绝完全不规范的缩写,避免望文不知义。

  • 如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。如:public class OrderFactory。

  • 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。如:CacheServiceImpl 实现 CacheService 接口。

  • 枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。

  • long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1 混 淆,造成误解。

  • 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。反例:if (空格a == b空格)

  • if/for/while/switch/do 等保留字与括号之间都必须加空格。

  • 任何二目、三目运算符的左右两边都需要加一个空格。

  • 采用 4 个空格缩进,禁止使用 tab 字符。

  • 注释的双斜线与注释内容之间。正例:// 注释内容

  • 方法参数在定义和传入时,多个参数逗号后边必须加空格。

  • IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式, 不要使用 Windows 格式。

  • Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。如:"test".equals(object);

  • 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

  • 关于基本数据类型与包装数据类型的使用标准如下:

    1. 所有的POJO类属性必须使用包装数据类型。

    2. RPC方法的返回值和参数必须使用包装数据类型。

    3. 所有的局部变量使用基本数据类型。

  • 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如 果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。

  • 关于 hashCode 和 equals 的处理,遵循如下规则:

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

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

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

  • ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。

  • 在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、 删除均会产生ConcurrentModificationException 异常。

  • 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。

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

  • 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

  • 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

并发处理

  • 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加 锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。

  • 多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获 抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

  • HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在 开发过程中可以使用其它数据结构或加锁来规避此风险。

控制语句

  • 表达异常的分支时,少用 if-else 方式,这种方式可以改写成
if (condition) { 
	...
	return obj;
}
// 接着写 else 的业务逻辑代码;
  • 下列情形,不需要进行参数校验:

    1. 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。

    2. 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所 以 DAO 的参数校验,可以省略。

    3. 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参 数已经做过检查或者肯定不会有问题,此时可以不校验参数。

注释规约

  • 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/** 内容 */格式,不得使用 // xxx方式。

  • 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。

  • 所有的类都必须添加创建者和创建日期。

  • 对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含 义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同 天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看 的,使其能够快速接替自己的工作。

异常日志

  • 异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。

  • 对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理。

  • 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

  • 不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句。

  • 定义时区分unchecked/checked 异常,避免直接抛出newRuntimeException(), 更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义 过的自定义异常,如:DAOException / ServiceException等。

日志

  • 应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  • 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,推荐分类有 stats/desc/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知 道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于 通过日志对系统进行及时监控

  • 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方 式。logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);使用占位符

  • 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws 往上抛出。如:logger.error(各类参数或者对象toString + "_" + e.getMessage(), e);

  • 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适 从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必 要,请不要在此场景打出 error 级别。

安全

  • 隶属于用户个人的页面或者功能必须进行权限控制校验。如:查看他人的私信 内容、修改他人的订单。

  • 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。如:查看个人手机号码会显示成:158****9119,隐藏中间 4 位,防止隐私泄露。

  • 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库。

  • 用户请求传入的任何参数必须做有效性验证。page size 过大导致内存溢出,恶意 order by 导致数据库慢查询,SQL 注入等。

  • 表单、AJAX 提交必须执行 CSRF 安全过滤。CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户 不知情情况下对数据库中用户参数进行相应修改。

  • 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其 它用户,并造成短信平台资源浪费。

MySQL数据库

建表

  • 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint ( 1表示是,0表示否)。任何字段如果为非负数,必须是 unsigned。

  • 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库 名、表名、字段名,都不允许出现任何大写字母

  • 表名不使用复数名词。

  • 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

  • 小数类型为 decimal,禁止使用 float 和 double。

  • 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。

  • varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长 度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。

  • 表的命名最好是加上“业务名称_表的作用”。如:alipay_task / force_project / trade_config

  • 库名与应用名称尽量一致。

  • 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:

    1)不是频繁修改的字段。

    2)不是 varchar 超长字段,更不能是 text 字段。

  • 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

索引

  • 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

  • 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。

  • 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可。

  • 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。如:where a=? and b=? order by c; 索引:a_b_c

  • 利用延迟关联或者子查询优化超多分页场景。

SQL语句

  • 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

  • 不得使用外键与级联,一切外键概念必须在应用层解决。

  • 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

  • 数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执 行更新语句。

  • in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。

  • 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。

MyBatis

  • POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行 字段与属性之间的映射。

  • 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需 要定义;反过来,每一个表也必然有一个与之对应。

  • sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。

  • 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。

参考阿里巴巴Java开发手册(终极版).pdf