Java开发规范及注意事项
编程规约
- POJO类中布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误
- 包名统一小写,点分隔符之间有且仅有一个自然语义的英文单词。包名统一使用单数形式,但是类名如果有复数含义,可以使用复数形式。(应用工具类包名为 com.ly.hotel.util、类名为 MessageUtils)
- 如果有使用到设计模式,在命名时体现出来(public class LoginProxy / public class AccessAdaptor / public class ServiceFactory)
- 接口和类中的方法和属性不要任何修饰符号(public也不要加),保持代码的简洁性。
- Service和Dao层的接口,实现要加Impl后缀
- 枚举类名带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。(ProcessStatusEnum 枚举成员变量:SUCCESS / FAIL / UNKNOWN_STATUS)
- 不允许任何魔法值(未预先定义的常量)出现在代码中
- 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
- 定义 BO / DO / DTO / VO 等 POJO 类时,不要设定任何属性默认值。
- 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
- 在高并发时,能使用无锁尽量使用无锁的数据结构,使用锁时,选取粒度小的锁。
- 在 if / else / for / while / do 语句中必须使用大括号。即使只有一行代码,避免采用 单行的编码方式
- 在高并发场景中,避免使用” 等于”判断作为中断或退出的条件。
- 所有的类都必须添加创建者和创建日期。
- 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。
异常日志违约
- Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
- 异常不要用来做流程控制,条件控制。
- 不要在 finally 块中使用 return。(Java的实现机制是在调用try代码块的return之前就会去执行finally里面的代码 )
单元测试规约
- 在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好 覆盖所有测试用例。
- 单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项 目提测前完成单元测试。
- 单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
- 对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级 别,一般是方法级别。
工程结构规约
• 开放接口层:可直接封装Service方法暴露成RPC接口;通过Web封装成http接口;进行网关安全控制、流量控制等。
• 终端显示层:各个端的模板渲染并执行显示的层。当前主要是velocity渲染,JS渲染,JSP渲染,移动端展示等。
• Web层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
• Service层:相对具体的业务逻辑服务层。
• Manager层:通用业务处理层,它有如下特征:
1)对第三方平台封装的层,预处理返回结果及转化异常信息。
2)对Service层通用能力的下沉,如缓存方案、中间件通用处理。
3)与DAO层交互,对多个DAO的组合复用。
• DAO层:数据访问层,与底层MySQL、Oracle、Hbase等进行数据交互。
• 外部接口或第三方平台:包括其它部门RPC开放接口,基础平台,其它公司的HTTP接口。
在DAO层,产生的异常类型有很多,无法用细粒度的异常进行catch,使用catch(Exception e)方式,并throw new DAOException(e),不需要打印日志,因为日志在Manager/Service层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。在Service层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。如果Manager层与Service同机部署,日志方式与DAO层处理一致,如果是单独部署,则采用与Service一致的处理方式。Web层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。
定义GAV遵从以下规则:
- GroupID格式:com.{公司/BU }.业务线 [.子业务线],最多4级。
- ArtifactID格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下。
- Version:详细规定参考下一条。
正例:com.zyp.jstorm 或 com.zyp.dubbo.register
二方库版本号命名方式:主版本号.次版本号.修订号
- 主版本号:产品方向改变,或者大规模API不兼容,或者架构不兼容升级。
- 次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的API不兼容修改。
- 修订号:保持完全兼容性,修复BUG、新增次要功能特性等。
注意起始版本号必须为:1.0.0,而不是0.0.1,正式发布的类库必须先去中央仓库进行查证,使版本号有延续性,正式版本号不允许覆盖升级。如当前版本:1.3.3,那么下一个合理的版本号:1.3.4 或 1.4.0 或 2.0.0
- 禁止在子项目的pom依赖中出现相同的GroupId,相同的ArtifactId,但是不同的Version。
- 所有pom文件中的依赖声明放在语句块中,所有版本仲裁放在语句块中。
数据库规约
- 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
- 表名不使用复数名词。
- 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
- 小数类型为decimal,禁止使用 float 和 double
- varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
- 表必备三字段: id, gmt_create, gmt_modified。
- 表的命名最好是加上“业务名称_表的作用” (hotel_trade_task / flight_trade_config)
- 库名与应用名称尽量一致。
- 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循: 不是频繁修改的字段。不是 varchar 超长字段,更不能是 text 字段。
- 单表行数超过== 500 万行==或者单表容量超过 2GB,才推荐进行分库分表。
- 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。 (不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。 )
- 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
- 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 ( 索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。 )
- 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例: where a=? and b=? order by c; 索引:a_b_c
反例: 索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
- 不得使用外键与级联,一切外键概念必须在应用层解决。
- 使用 ISNULL() 来判断是否为 NULL 值。
- 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。
- 不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
- 数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。
- sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。
- 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字 段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,== 不要更新无改动的字段==,一是易出错;二是效率低;三是增加 binlog 存储。