Java开发规范
一、编码
编码主要参考阿里规范,详情见附件
1.1 编码质量
1.1.1 指标
● 编译告警数,大部分程序员基本上忽略 warning,但是编译器出现了告警是一种不好的体现,意味着软件可能工作,但是存在不好的实践,而这种不确定性,会带来不确定的 bug 最终让人一头雾水。编译过程中的告警,尽量消除掉,编译告警的值推荐消除到 0。
● 平均函数代码行数,过大的函数会导致阅读困难,而且往往过大的函数职责不够单一,一般将一个方法代码行数控制到 30 - 50 行。
● 平均文件代码行,和平均函数代码行一样,过长的文件一样难以维护,一般一个文件10多个方法,因此文件的代码行数一般控制到 300 - 500 行。
● 冗余代码,有时候我们代码中可能存在未使用的方法、变量等代码,这让维护者一头雾水,通常需要清零。
● 总文件重复率,出现重复文件的次数。除了编写单元测试的情况下,业务代码不应该出现重复代码,推荐值为 0。
● 总代码重复度,代码的重复度检查,限于扫描工具的识别模式,需要有一定的容忍度,推荐值在 5% - 10%
● 平均函数圈复杂度,圈复杂度用来衡量一个模块判定结构的复杂程度。如果一个方法内部有大量的 if 语句嵌套,意味着这个方法的实现质量低下,且程序复杂度高不利于维护,推荐值小于 5%。
● 安全告警,如果配置了安全扫描工具,例如 Fortify,安全威胁应该被清零。
● 代码缺陷,如果配置了缺陷扫描工具,例如 Findbugs,需要清零。
1.1.2 检查项
检查项 | 工具 | 目标 | 当前 |
---|---|---|---|
代码风格检查 | checkstyle | 0 | |
编译告警数 | checkstyle | 0 | |
平均方法代码行数 | checkstyle | 30-50 | |
平均文件代码行数 | checkstyle | 300-500 | |
冗余代码 | checkstyle | 0 | |
总文件重复率 | checkstyle | 0 | |
总代码重复率 | checkstyle | 5%-10% | |
平均方法圈复杂度 | checkstyle | <=5% | |
安全高级 | sonarqube | 0 | |
代码缺陷 | sonarqube findbugs | 0 |
1.1.3 使用
mvn checkstyle:checkstyle
1.1.4 checkstyle检查规则
如有补充调整, 请修改项目中的checkstyle.xml
文件
请严格按照
https://checkstyle.sourceforge.io/checks.html 官方 module name 及 parent位置添加规则模块, 否则xml文件加载会报错
类别 | 检查项 | 规则 |
---|---|---|
注释检查 | ||
命名检查 | (AbstractClassName)抽象类名称 | ^Abstract.+$, 抽象类名必须由Abstract 开头 |
(ConstantName)全局常量名称(static final) | ^ [A-Z][A-Z0-9](_[A-Z0-9]+)$ , 全部大写加下划线 | |
(LocalFinalVariableName)局部final变量名称 | ^ [A-Z][A-Z0-9](_[A-Z0-9]+)$ , 全部大写加下划线 | |
(LocalVariableName)局部变量名称 | ^ [a-z][a-zA-Z0-9]*$, 驼峰命名 | |
(MemberName)成员变量(非静态字段)名称 | ^ [a-z][a-zA-Z0-9]*$, 驼峰命名 | |
(MethodName)方法名称 | ^ [a-z][a-zA-Z0-9]*$, 驼峰命名 | |
(PackageName)包名称 | ^ [a-z]+(.[a-z][a-z0-9])$, 全部小写 | |
(ParameterName)参数名称 | ^ [a-z][a-zA-Z0-9]*$, 驼峰命名 | |
(StaticVariableName)静态变量名称(用static修饰,但没用final修饰的字段) | ^ [a-z][a-zA-Z0-9]*$, 驼峰命名 | |
(TypeName)类(接口)名称 | ^ [A-Z][a-zA-Z0-9]*$, 驼峰命名, 首字母大写 | |
导入检查 | (IllegalImport)非法包导入 | 默认: 拒绝所有 sun.* 包 |
(RedundantImport)冗余导入 | 当一个类被导入不止一次时。非静态导入的类来自java.lang 包,例如 importing java.lang.String。非静态导入的类与当前包来自同一个包。 | |
(UnusedImports)未使用的导入 | 和冗余导入校验类似, 主要处理导入类型与声明名称相同的情况 | |
长度检查 | (FileLength)源码文件的长度 | 一个文件所能容许的行数 最多500行 |
(LineLength)源码每行的长度 | 一行最多200个字符, 排除注释行的限制 | |
(AnonInnerLength)匿名内部类的长度 | 匿名内部类最多20行 | |
(MethodLength)方法的长度 | 每个方法最多50行(包含空行) | |
(ParameterNumber)方法参数数量 | 每个方法最多10个参数 | |
空格检查(可有可无, IDEA标准格式化即可) | (EmptyForInitializerPad)for循环初始化语句填充符 | for(int i = 0; i < 100; i++) 正确for(int i=0; i<100;i++) 错误 |
(EmptyForIteratorPad)for iterator语句是否使用空格 | IDEA标准格式化即可 | |
(NoWhitespaceAfter) | 指定标记之后有没有空格, IDEA标准格式化即可 | |
(NoWhitespaceBefore) | 指定标记之前有没有空格, IDEA标准格式化即可 | |
(MethodParamPad)方法定义、构造器定义、方法调用、构造器调用的标识符和参数列表的左圆括号之间的填充符 | IDEA标准格式化即可 | |
(ParenPad)圆括号的填充符策略 | IDEA标准格式化即可 | |
(TypecastParenPad)类型转换的圆括号的填充符策略 | IDEA标准格式化即可 | |
(WhitespaceAfter)指定标记之后是否紧跟了空格 | IDEA标准格式化即可 | |
(WhitespaceAround)指定标记的周围是否有空格 | IDEA标准格式化即可 | |
修饰符检查 | (ModifierOrder) 修饰符顺序 | 正确的顺序应当如下:1. public 2. protected 3. private 4. abstract 5. static 6. final 7. transient 8. volatile 9. synchronized 10. native 11. strictfp |
(RedundantModifier) 冗余的修饰符 | 1. 接口和注解的定义;2. final类的方法的final修饰符;3. 被声明为static的内部接口声明 | |
代码块检查 (括号位置检查可有可无, IDEA标准格式化即可) | (AvoidNestedBlocks) 嵌套代码块 | 找到嵌套代码块,也就是在代码中无节制使用的代码块 |
(EmptyBlock) 空代码块 | 查找代码块中不包含内容的 | |
(LeftCurly) 代码块的左花括号的放置位置 | 左括号位置和名称定义在同一行, 不换行(IDEA标准格式化即可) | |
(NeedBraces)代码块周围是否有大括号 | 检查do、else、if、for、while等关键字所控制的代码块 禁止: if (obj.isValid()) return true; | |
(RightCurly)代码块的右花括号的放置位置 | IDEA标准格式化即可 | |
编码检查 | (CovariantEquals) 重载equals 方法 | 重载equals方法时, 需要重写覆盖equals(Object o); 方法 |
(DefaultComesLast) switch case代码块中default的位置 | default应放在所有的case分支之后 | |
(MissingSwitchDefault) switch 中 default语句 | switch case 中必须要有default语句 | |
(EmptyStatement) 空语句 | 禁止出现独立的 “;” | |
(EqualsHashCode) 重写equals 方法 | 重写equals方法时, 需要重写 hashCode 方法 | |
(FallThrough) switch case代码块中的跳出语句 | case 分支 必须存在 break、return、throw或continue语句 | |
(HiddenField) 变量名冲突 | 检查局部变量是否会遮蔽在相同类中定义的字段 | |
(IllegalInstantiation) 不必要的实例化操作 | 目前只针对 Boolean 和 Integer 检查是否存在不必要的实例化操作, 使用工厂更合适, 例: new Integer(int i); -> Integer.valueOf(int i); | |
(InnerAssignment) 子表达式赋值 | 禁止子表达式中的赋值操作, 提高可读性, 例: String s = Integer.toString(i = 2) | |
(MagicNumber) 幻数 | 禁止出现未定义成常量的数字, (排除0, 1, -1和注解上的数字) | |
(MultipleStringLiterals) 相同字符串控制 | 当一个文件中出现3次以上相同的字符串时, 应定义为常量 | |
(MultipleVariableDeclarations) 变量的声明 | 禁止在同一行声明多个变量, 例: int lower, higher; int place = mid, number = high; | |
(NestedIfDepth) if else 嵌套层数 | 禁止if else 嵌套超过3层, 超过请重构 | |
(NestedTryDepth) try catch 嵌套层数 | 禁止try catch 嵌套超过2层, 超过请重构 | |
(ParameterAssignment) 方法参数赋值 | 禁止对方法的参数进行重新赋值, 如需必要, 请重新定义变量, 修改变量值 | |
(ReturnCount) 方法中return数量 | 限制方法、构造方法、lambda表达式中return 个数, 最多10个 | |
(SimplifyBooleanExpression) 复杂布尔表达式 | 检查是否存在复杂布尔表达式, 例: if (b == true)、b | |
(SimplifyBooleanReturn) 复杂布尔返回 | 检查是否存在复杂布尔返回, 例: if (valid()) return false; else return true; 改为: return !valid(); | |
(StringLiteralEquality) 比较字符串相等 | 比较两个字符串相等必须使用 equales 方法, 禁止使用 “==” | |
(TodoComment) TODO | 请及时更新补充TODO内容 | |
(LambdaBodyLength) lambda 表达式长度 | 提高可读性, lambda表达式长度不能超过10行, 超过请提取方法 | |
(LambdaParameterName) lambda 参数 | ^ [a-z][a-zA-Z0-9]*$, 驼峰命名 | |
类设计检查 | (InterfaceIsType) 空接口 | 禁止一个接口中只定义变量 或 是一个空接口 |
(ThrowsCount) throw 数量 | 限定一个方法可抛出的异常数量最大为5个 | |
度量检查 | (CyclomaticComplexity) 循环复杂度 | 一个方法出现if、while、do、for、?:、catch、switch、case等语句,以及&&和 |
其他检查 | (Indentation) 代码缩进 | IDEA标准格式化即可 |
(UpperEll) long类型的常量定义 | 小写的 “l” 和 数字 “1” 很相似, 应定义为 “L” |
1.1.5 强制规范
- mybatis-plus sql 字段名不能直接使用字符串,使用 LamdaWrapper
- 返回数据格式统一用 Response,不可以直接返回 map、void、string、object
- 数据传输禁止使用map交互, 定义VO或者DTO
- dao层不能含有大量的业务处理逻辑, 放到领域层(domain)
- 不能直接在代码中拼接字符串sql, 如有必要 定义在xml中
- 异常尽量抛出交给全局异常处理, 如必须捕获处理, 需将完整堆栈信息打印出来, 不允许私吞
- 命名符合驼峰规则及尽量有意义, 不用 XXX_1, XXX_2, XXX_3 数字区分
- 接受统一用json协议,参数不能直接 getParamter 获取
- 尽量单表操作, 不使用 left join
- 统一分页规范, 使用公共 PageUtils, PO继承公共PageQry
- 方法开头不需要打印参数日志, 统一交给切面打印
- 入参校验统一使用JSR303
- 对象空校验阻断业务流程时统一使用AssertUtil工具类抛出异常
- 多状态多类型字段需强制使用Enum
二、日志
2.1 使用
● 统一使用 lombok @Slf4j 注解。
● 必须使用参数化信息的方式,不要进行字符串拼接,那样会产生很多String对象,占用空间,影响性能。
log.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);
三、异常 & 监控
3.1 异常 & 错误码
BizException
业务提示性的异常,比如:当前用户未登录,权限不足等。
不需要catch打印,直接抛出BizException,系统统一捕获并打印具体错误信息。
SystemException
影响到程序正常运行,需要开发处理的异常。
不需要catch打印,直接抛出SystemException,系统统一捕获并打印具体错误信息。
3.2 监控
对 controller做切面拦截,统一输出日志
3.2.1 告警规则
系统异常(SystemException)一定告警
3.2.2 监控指标
3.2.2.1 系统指标
接口请求量
活跃用户
高频接口
TPS
接口耗时
系统异常
3.2.2.2 业务指标
四、分层 & 分包
4.1 模块
scrm-api
作为被依赖服务,提供给其他服务的接口
scrm-common
公共模块,放置util等
scrm-web
核心服务
4.2 分层
参考:https://gitee.com/chyj77/ddd-cargo
interfaces(表现层)
查询数据:Qry(读)
命令:Cmd(写)
业务层之间的数据对象:VO(Value Object)
application (应用层)
标准参数:Specification
domain (领域层)
DDD 中的实体 DO(Domain Object)
infrastructure (基础设施层)
访问数据库的:DAO (Data Access Object数据访问对象)、Mapper
与数据库表结构对应:PO
4.3 DDD规范
4.3.1 基本分层引用链
情形一:业务简单
interfaces -> application -> infrastructure
情形二:业务复杂
interfaces -> application -> domain -> infrastructure
4.3.2 各层类文件规范
interfaces :controller
application: VO 、qry、cmd、service
调用规则:application调用domain/infrastructure 只允许传递DO/Specification/aggregate/context 后缀的类 不允许传递Qry/Cmd后缀的类
domain: domainService、context、aggregate、factory、event
调用规则:domain调用infrastructure 只允许传递DO/Specification 后缀的类或者单参数形式
infrastructure: PO、DO、Specification
调用规则:所有sql查询处理必须在dao层 不允许出现在其他层 查询出来的对像统一需要返回DO出去
具体调用细则 查看示例类 com.jb.scrm.interfaces.web.demo.DemoController
五、代码提交
必须经过 2次 code review,第一次主要检查编码规范,第二次主要检查业务逻辑。
六、单元测试
unit test 覆盖率大于 20%