Java后台开发规范-版本一
1.Java编程规范
1.1.命名风格
代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO
接口类中的方法和属性不要加任何修饰符号,保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法 list 命名。
3) 获取统计值的方法用 count 做前缀。
3) 分页的方法用 page 做后缀。
4) 插入的方法用 save 做前缀。
5) 删除的方法用 delete 做前缀。
6) 修改的方法用 update 做前缀。
领域模型命名规约
1) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
2) 展示对象:xxxVO,xxx 一般为实体名称。
3) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO
分层领域模型规约
DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
BO(Business Object):业务对象。由 Service 层输出的封装业务逻辑的对象。
AO(ApplicationObject):应用对象。在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
1.2.代码格式化规范
每次编辑完一个类后使用idea的格式化功能,格式化代码和去掉无用导入的包,win快捷键为 Ctrl+Alt+l 和 Ctrl+Alt+o,Mac为ctrl+option+o 和 option+command+l。
项目的代码格式统一为UTF-8。
1.3.API命名规范
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新完整的资源(客户端提供改变后的完整资源)。
DELETE(DELETE):从服务器删除资源。
基本规范
使用'/'表示层级关系
url 不能以'/'结尾
url 中不能包含空格
url 中不能以文件后缀结尾
url 中字母小写,单词间加下划线
不要再url中添加CRUD
说明 ActionName HttpMapping HttpRequestBody HttpResponseBody
查询所有 list GET /v1/user/list?xx=xx N/A Resource* list
获取单个资源 query GET /v1/user/1 N/A Resource*
创建单个资源 create POST /v1/user/create Resource Resource*
更新单个资源 update PUT /v1/user/update Resource Resource*
删除单个资源 delete DELETE /v1/user/delete N/ Empty
分页条件查询 page GET /v1/user/page?page=0&size=10 N/A Resource
批量添加 batchCreate POST /batch_create Resource* list Resource IDS
批量删除 batchDelete POST /batch_delete Resource IDS Empty
更新用户的年龄 updateAge POST /v1/user/1/age?value=20 N/A {"key":"age","value":"20"}
1.4.异常处理规范
异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理。
捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。
有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。
finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句。
方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分 说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题。
1.5.模块化开发规范
模块化开发是指公司共有的基础模块的开发如:短信、权限、支付、公共工具、邮件等。
模块化开发的pom模版
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.domain.module</groupId>
<artifactId>domain-module</artifactId>
<version>1.0.0.RELEASE</version>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>releases</id>
<name>Nexus release Repository</name>
<url>http://192.168.0.110:8081/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>Nexus snapshots Repository</name>
<url>http://192.168.0.110:8081/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project>
模块化定义 GAV 遵从以下规则:
GroupID格式:com.{域名}.业务线.[子业务线],最多4级。
ArtifactID格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下。
模块化命名方式:主版本号.次版本号.修订号
主版本号:产品方向改变,或者大规模API不兼容,或者架构不兼容升级。
次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的API不兼容修改。 3) 修订号:保持完全兼容性,修复BUG、新增次要功能特性等。
2.前后端对接规范
后端返回格式统一为json格式数据。
{"code":20000,"message":"success","data":null}
后端返回的code字段,以4开头则为前端请求有误,以5开头则是后端接口问题。2开头则表示成功。
后端返回的message字段表示请求的信息,成功是success,错误则会提示相应的异常信息或返回error。
后端返回的data字段则是本次请求的数据,无数据则返回null。
后端返回的时间格式统一为时间戳
3.数据库设计规范
3.1.建表规约
表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 tinyint (1表示是,0表示否)。
表名不使用复数名词。
禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
小数类型为 decimal,禁止使用 float 和 double。
如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长 度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。
表必备三字段:id, gmt_create, gmt_modified。
表的命名最好是加上"业务名称_表的作用"。
库名与应用名称尽量一致。
单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
3.2.索引规约
业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
利用覆盖索引来进行查询操作,避免回表。
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
建组合索引的时候,区分度最高的在最左边。
3.3.SQL 语句
不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。
使用 ISNULL()来判断是否为 NULL 值。
在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
不得使用外键与级联,一切外键概念必须在应用层解决。
禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。
in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内
如果有全球化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符统计函数的区别。
TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。
3.4.ORM 映射
在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行 字段与属性之间的映射。
不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需 要定义;反过来,每一个表也必然有一个与之对应。
sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。
不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。
不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字 段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需 要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
4.服务部署规范
在线上生产环境,JVM的Xms和Xmx设置一样大小的内存容量,避免在GC 后调整堆 大小带来的压力。
给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。
引用自:https:
Java后台开发规范-版本二
一、命名规范
二、Java代码规范(注释规范、异常与日志、代码逻辑规范)
三、Mybatis与SQL规范
四、结果检查(单元测试及代码扫描)
五、安全规范
一、命名规范
1、命名使用英文词组合,严禁使用中文拼音或拼音首字母组合命名(专有名词例外) - OrganizationTreeNode, OrganizationVO ; 不推荐使用PSTree , Tlogs
2、groupId,package包名前缀统一为: com.wiwj
3、包名第三位为产品分类名,如com.wiwj.cbs
4、常量命名全大写,单词间下划线分隔。如: DEFAULT_PAGE_SIZE
5、其他命名遵循驼峰式命名法:
(1)类名:首字母大写的UpperCamelCase,如: Organization
(2)方法名、变量名:首字母小写的lowerCamelCase,如: orgName
6、特定标识命名:
(1)领域模型增加类型后缀标识,如xxVO, yyDAO
(2)基类/抽象类使用Base/Abstract等前缀标识
(3)设计模式类添加Factory,Builder,Proxy等标识
(4)Controller, Service, Mapper统一添加到对应分层目录
(5)接口实现类添加Impl后缀标识
(6)枚举类添加Enum后缀标识
(7)CRUD接口采用统一前缀: get, count, create, delete, update, batchCreate …
二、Java代码规范
1、注释规范
(1)Java文件统一添加固定Header,通过IDE统一配置(code templates)
(2)接口和方法统一添加Java Doc标准注释
<T> boolean addList(String key, List<T> valueList);
(3)需暂留的弃用类/方法添加 @Deprecated 废弃标记 和 @see 链接指向新接口
@Deprecated
public class JedisUtils {…}
2、异常与日志
(1)调用外部服务等可能异常的代码块,用 try/catch 代码块捕获并在catch中记录异常跟踪日志及业务逻辑处理
(2)禁止吞掉异常信息
禁止catch里不做任何记录和处理,吞掉异常及其堆栈信息
禁止: logger.error(“XXX操作异常”) 或 logger.error(“XXX操作异常”+e) 或 e.printStackTrace()
正确: logger.error("XXX操作异常", e)
(3)对于非预期的条件,尽量增加else记录跟踪日志
(4)禁止通过System.*.out()打印日志(单元测试例外)
(5)日志记录logger需使用Slf4J代理声明,禁止绑死具体日志系统的API,避免后期更换日志组件导致代码的大量改动
private static final Logger log = LoggerFactory.getLogger(OrganizationServiceImpl.class);
如采用了lombok,可用 @Slf4j 注解替代以上声明。
(6)对 trace/debug/info 级别的日志输出,必须使用占位符形式,避免直接String拼接异常信息(即使日志级别不匹配也会执行拼接操作空耗资源)。
正确写法如:
log.debug("当前用户id: {} ,操作对象: {}=>{} ", userId, objectType, objectId);
或条件输出形式如:
if(log.isDebugEnabled()){
log.debug("当前用户id: “+id+” ,操作对象: “+ objectType +”=> “+ objectId);
}
3、逻辑代码规范
废弃的/无用的代码一律直接删除,禁止以注释等方式保留。如需查看历史代码,通过SVN/Git的history找回,(无用的代码会干扰团队成员的阅读/或被误调,越积越多会导致代码维护成本增高)
接口类中的方法不需添加 public 修饰符
需要序列化的Bean类统一实现Serializable接口并用IDE生成serialVersionUID
public class MyEntity implements Serializable {
private static final long serialVersionUID = 123456L;
...
}
4、常用字符串统一定义在常量类里,如: “utf-8”, “yyyyMMdd”
5、避免数字类型比较的坑: 统一采用equals进行比较其值,不用==进行比较,避免踩坑。
6、if/else/for/while语句后必须使用大括号,即使只有一行代码。(需求总是变化的,一行是暂时的)
7、嵌套层次过多的代码块利用反向思维缩减层次
8、方法单一职责: 单个方法代码行数控制在100行以内,超长的需要拆分(拆分成多个方法或类)
9、避免NPE(NullPointException)的一些建议:
(1)equals比较将非空对象前置:如 "true".equals(request.getParameter("isXx")),即使后者为空也不会导致NPE。
(2)数据库字段可空的映射属性使用包装类型定义:如基本数据类型的int映射到数据库的null值将产生NPE,而用吧包装类型 Integer 则不会。
(3)可能为空的变量进行必要判空,并在非预期条件下打印必要的跟踪日志,不但避免NPE,还非常便于跟踪调试。如:
(4)级联调用 obj.getA().getB().getC() 易产生 NPE,先进行判空或使用 JDK8 的 Optional 类包装。
(5)调用Dubbo接口拿到返回值时,进行判空。
(6)封装统一的判空类用于常用类型的判空,代码需要判空时统一调用即可。如 XX.isEmpty(), XX.isNotEmpty()
10、遵循: Don’t Repeat Yourself,即 DRY 原则。避免进行简单的复制粘贴修改,当出现重复代码时思考是否封装
当代码中存在大量重复代码时,一旦代码逻辑变动将很容易导致顾此失彼,产生bug,非常不利于维护。
11、Bean属性拷贝推荐用Spring BeanCopier或者Mapstruct,避免Apache BeanUtils或调用setter
禁止在循环中执行耗时的操作,如在循环中执行SQL语句/调用外部服务等
// 错误的示例:
for(Long id : idList){
// 循环执行SQL查询或调用外部系统接口,产生性能问题
Entity entity = xxService.getEntityById(id);
...
}
// 此案例的更优方案是 通过idList一次性查询获取到Entity集合,然后转换为Map<Id, Entity>供后续获取。
12、需要多次使用的可复用对象将对象单独定义,禁止多次调用取不同属性。如:
String name = userService.getUser(id).getName();
Long deptId = userService.getUser(id).getDepeId();
替换为:
User user = userService.getUser(id); String name = user.getName(), ….
13、可异步执行的耗时操作采用异步处理:使用Spring @Async 或 MQ,或夜间Timer定时
14、常用数据考虑缓存,存入Redis,设置缓存过期时间
15、需要保证写一致性的逻辑,在外层方法上添加事务 @Transactional(rollbackFor = Exception.class)
三、Mybatis与SQL规范
1、表名、字段名、索引等数据结构定义大小写: Oracle大写, MySQL小写。名称使用英文+下划线,并控制总长度,如 user_name。
2、表名建议采用“模块标识_”前缀,如 bas_user(如果模块库独立可省略模块名标识)
3、禁止程序中的SQL使用并行计算 /*+parallel(t,n)*/
4、SQL使用标准SQL,避免出现数据库特定的语法
5、未经评审不可直接使用视图、触发器、存储过程 SQL JOIN表数量不超过3张,超过3张表需要经过评审 (拆分成多次单表查询、主表冗余、程序绑定id-name映射、根据条件动态JOIN等)。
(1)合理创建索引,并尽量避免不走索引的情况: 如
(2)LIKE右/任意匹配(‘%xx’, ‘%xx%’)不走索引, 换为“精确匹配=”或固定前缀的左匹配’张%’
(3)不等条件(!=、<>、NOT)不走索引,应尽量避免(转换成IN/BETWEEN等)
(4)IS (NOT) NULL 不走索引,应尽量避免(如字段给定默认值,避免NULL)
(5)索引列使用函数或隐式转换都将导致索引失效,如 to_char(create_date,'yyyymmdd') = '20190102'
6、禁止手动拼接SQL语句,利用Mybatis等ORM框架的动态SQL实现。 参数使用#{} (避免${}产生SQL注入问题)。
7、禁止使用数据库处理函数 decode(),改为Java枚举或Map定义,通过id进行绑定 decode(client.TYPE, 1, '私客', 2, '店组公客', 3, '组团公客‘)
8、禁止动态拼接时强加 1=1 之类的写法,如WHERE 1=1。使用Mybatis动态SQL标签实现,如<where>,<set>,<trim>
9、SQL中的参数类型确保与列定义一致,避免数据库隐式转换开销且无法使用索引,如:
(1)列定义为日期类型,参数要转换为Date日期类型进行比较:
CREATE_TIME <= '2019-04-14 23:59:59’
CREATE_TIME <= to_date('2019-04-14 00:00:00','yyyy-MM-dd HH24:mi:ss’)
(2)列定义为数字类型,参数不用String DEPT_ID = '123’
10、ID主键自增的情况下,按create_time排序改为按ID排序,效果一样效率更高
四、检查结果
1、后端服务及其他需要自测的代码,编写对应的单元测试类,统一采用Junit,禁止直接在原Java类中写main()方法自测。
单元测试会在打包前统一运行,可及时发现受影响的代码问题(比如新代码导致了之前的代码逻辑产生问题,如果有单元测试可在打包时及时发现)
Junit单元测试类示例:
public class TestApollo {
@Test // 标记为单元测试方法
public void testApolloConfig(){
String appId = Foundation.app().getAppId();
// 预期结果断言
Assert.assertNotNull(appId);
}
}
2、IDE中安装代码质量检查插件: FindBugs 及 Alibaba Java Coding Guidelines
五、安全规范
1、用户敏感数据禁止列表批量展示/导出,必须进行脱敏,如客户手机号,身份证号
2、禁止直接写死账号登录账号密码,给系统造成巨大的安全风险
3、禁止代码/配置文件中出现生产环境相关的明文密码(改为加密存放)
引用自:https://www.cnblogs.com/zlt-blog/p/10839955.html