Java 开发规范
Posted 一叶知秋V
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 开发规范相关的知识,希望对你有一定的参考价值。
好的代码规范有助于减少软件实现的复杂度,降低沟通成本,本规范主要涵盖了软件设计、工程结构、编码、异常日志、单元测试、安全等方面的规范。
1.软件设计规范
场景 | Level | 备注 | |
---|---|---|---|
文档 | 需要沉淀文档的:存储方案、底层数据结构 | 强制 | |
用例图 | 需求分析阶段,如果与系统交互的 User 超过 1 类,并且相关 User Case 超过 5 个,那么使用用例图来表达结构化需求。 | 强制 | |
状态图 | 某个业务对象的状态超过 3 个,那么应该使用状态图来表达并且明确状态变化的各个触发条件。 | 强制 | |
时序图 | 某个功能的调用链路上涉及的对象超过 3 个,则应使用时序图来表达并且明确各调用环节的输入与输出。 | 强制 | |
类图 | 如果系统中模型类超过 5 个,并且存在复杂的依赖关系,则应使用类图来表达并且明确类之间的关系。 | 强制 | |
活动图 | 如果系统中超过 2 个对象之间存在协作关系,并且需要表示复杂的处理流程,则使用活动图来表示。 | 强制 | |
敏捷开发 | 敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但需要核心关键点上的必要设计和文档沉淀。 | 参考 |
2.工程结构规范
1.应用分层
层级依赖关系 如下(默认上层依赖于上层,箭头关系表示可直接依赖):
说明 | |
---|---|
终端显示层 | 各个端的模板渲染并执行显示的层,当前主要包括:Freemarker、Thymeleaf、Velocity、JS、JSP、移动端展示等。 |
开发接口层 | 目前主要包括:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装为 HTTP 接口;进行网关安全控制、流量控制等。 |
Web层 | 主要是对访问控制进行转发,对各类基本参数校验,或者对不复用的业务简单处理等。 |
Service层 | 具体业务逻辑服务层。 |
Manager层 | 通用业务处理层。对第三方平台封装的层,预处理返回结果及转化异常信息;对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;与 DAO 交互,对多个 DAO 的组合复用。 |
DAO层 | 数据库访问层,与底层 mysql、Oracle、HBase 等进行数据交互。 |
外部接口或第三方平台 | 包括其他部门的 RPC 开放接口、基础平台、其他公司的 HTTP 接口。 |
分层领域模型规范如下:
说明 | |
---|---|
DO | Data Object。与数据库表结果一一对应,通过 DAO 层向上传输数据源对象。 |
DTO | Data Transfer Object,数据传输对象。Service 或 Manager 向外传输的对象。 |
BO | Business Object,业务对象。由 Service 层输出的封装业务逻辑的对象。 |
AO | Application Object,应用对象。在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。 |
VO | View Object,显示层对象。通常是 Web 向模板渲染引擎传输的对象。 |
Query | 数据查询对象。各层接受上层的查询请求。如果是超过 2 个参数的查询封装,则禁止使用 Map 类来传输。 |
2.二方库依赖
二方库依赖的规范:
规范 | Level | 备注 | |
---|---|---|---|
GroupID | 格式: com.公司/BU.业务线.[子业务线],最多五级,子业务线可选。 | 强制 | |
ArtifactID | 格式: 产品线名-模块名。语义不重复不遗漏。 | 强制 | |
Version | 格式: 主版本号.次版本号.修订号。主版本号,产品方向改变,或大规模 API 不兼容,或架构不兼容升级;次版本号,保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改;修订号,保持完全兼容性,修复 bug、新增次要功能特性等。起始版本号必须为 1.0.0。 | 强制 | |
SNAPSHOT 版本 | 线上应用不要依赖 SNAPSHOT 版本 (安全包除外),不依赖 SNAPSHOT 版本是保证应用发布的幂等性。另外,也可以加快编译时的打包构建。 | 强制 | |
二方库的新增、升级 | 需要保证除功能之外的其他 jar 包仲裁结果不变。如果有改变,需要明确评估与验证,建议进行 dependency:resolve 前后信息对比,如果仲裁结果完全不一致,则通过 dependency:tree 命令找出差异点,进行< excludes> 排除 jar 包。 | 强制 | |
二方库中枚举 | 二方库中允许定义枚举,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象。 | 强制 | |
二方库群 | 依赖于一个二方库群时,必须定义一个统一的版本变量 (例如 $spring.version,定义 Spring 依赖的时候引用该版本),避免版本号不一致。 | 强制 | |
子项目pom | 禁止子项目的pom依赖中出现相同的 GroupID、ArtifactID,不同的 Version。 | 强制 |
二方库发布者应当遵守以下规范:
规范 | Level | 备注 |
---|---|---|
精简可控制,移除一切不必要的 API 和依赖,只包含 Service API、必要的领域模型对象、Utils 类、常量、枚举等。如果依赖其他二方库,尽量是 provided 引入,让二方库使用者依赖具体版本号;无 log 具体实现,只依赖日志框架。 | 参考 | |
稳定可追溯原则,每个版本的变化都应该被记录,二方库由谁维护,源码在哪里,都需要能方便地查到。除非用户主动升级版本,否则公共二方库的行为不应该发生变化。 | 参考 |
3.高并发服务器
规范 | Level | 备注 | |
---|---|---|---|
高并发服务器建议调小 TCP 协议的 time_wait 超时时间。 | 操作系统默认 240s 后,才会关闭处于 time_wait 状态的连接。在高并发访问下,服务器会因为处于 time_wait 的连接数过多,而无法建立新的连接,所以需要在服务器上调小这个值。在 Linux 服务器上可通过变更 /etc/sysctl.conf 文件去修改该默认值(s):net.ipv4.tcp_fin_timeout = 30 | 推荐 | |
调大服务器所支持的最大文件句柄数(fd) | 主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式管理,即一个连接对应一个 fd。Linux 服务器默认支持最大的 fd 数量为 1024,当并发连接数很大时很容易因为 fd 不足而出现 “open too many files” 错误,导致新的连接无法建立。建议将 Linux 服务器所支持的最大句柄数调高数倍 (与服务器的内存数量相关)。 | 推荐 | |
JVM 参数 | JVM 的堆最小值 Xms 和堆最大值 Xmx 参数设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。 | 推荐 | |
给 JVM 设置 -XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 遇到 OOM 场景时输出 dump 信息。 | 推荐 |
3.编码规范
1.命名规范
规范 | Level | 备注 | |
---|---|---|---|
类名 | 使用 UpperCamelCase 风格,但 DO/BO/DTO/VO/AO/PO 等情形除外(例如 UserService、UserDO)。 | 强制 | |
方法名、参数名、成员变量、局部变量 | 驼峰命名法。 | 强制 | |
常量命名 | 全部大写,单词间用下画线隔开。 | 强制 | |
抽象类 | 使用 Abstract 或 Base 开头 | 强制 | |
异常类 | 使用 Exception 结尾。 | 强制 | |
枚举类 | 使用 Enum 结尾。枚举成员名称需要全大写,单词间用下画线隔开。 | 强制 | |
测试类 | 使用测试的类名开始,以 Test 结尾。 | 强制 | |
定义数组 | 类型与中括号之间无空格相连定义数组,例如 int[] arrayDemo; | 强制 | |
Service/DAO 层方法命名 | 获取单个对象的方法用 get 作为前缀,获取多个对象用 list;获取统计值的方法用 count 作为前缀;插入的方法用 save/insert 作为前缀;删除的方法用 remove/delete 作为前缀;修改的方法用 update 作为前缀。 | 参考 |
2.代码规范
规范 | Level | 备注 |
---|---|---|
if/for/while/switch/do 等保留字与括号之间必须加空格。 | 强制 | |
采用 4 个空格缩进,禁止使用 Tab 控制符。 | 强制 | |
注释的双斜线与注释内容之间有且仅有一个空格。例如: // 这是注释。 | 强制 | |
所有覆写方法,必须加 @Override 注解。 | 强制 | |
接口过时,必须加 @Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。 | 强制 | |
不能使用过时的类或方法。 | 强制 | |
POJO 类必须写 toString 方法,便于排查问题。 | 强制 | |
对于集合,只要重写 equals 就必须重写 hashCode。 | 强制 | |
获取当前毫秒数用 System.currentTimeMillis(); | 强制 | |
在循环体内,字符串的连接方式使用 StringBuilder 的 append 方法进行扩展。 | 推荐 | |
任何数据结构的构造或初始化,都应指定大小,避免因数据结构无限增长而耗尽内存。 | 推荐 |
3.并发处理规范
规范 | Level | 备注 |
---|---|---|
获取单例对象时需要保证线程安全,其中的方法也要保证线程安全。 | 强制 | |
在创建线程或线程池时,请指定有意义的线程名称,方便出错时回溯。 | 强制 | |
线程资源必须通过线程池提供,不允许在应用中自己显式创建线程。 | 强制 | |
线程池不允许使用 Executors 创建,而是通过 ThreadPoolExecutor 的方式创建,这样的处理方式能让编写代码的工程师更加明确线程池的运行规则,避免资源耗尽的风险。 | 强制 | |
SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。 | 强制 | |
在高并发场景中,加锁的代码块工作量尽可能小,避免在锁代码块中调用 RPC 方法。在对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,避免死锁。 | 强制 | |
在并发修改同一记录时,为避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存层加锁,要么在数据库层使用乐观锁(使用 version 作为更新依据,每次访问冲突概率小于 20% 推荐乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次)。 | 强制 | |
避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但是因竞争同一 seed 导致性能下降。在 JDK1.7 之后可以直接使用 ThreadLocalRandom,而在 JDK1.7 之前,需要编码保证每个线程持有一个实例。 | 推荐 | |
在并发场景下,通过双重检查锁(double-checked-locking)实现延迟初始化的优化问题隐患,推荐解决方案是较为简单的一种,即目标属性声明为 volatile 型。 | 推荐 | |
volatile 解决了多线程内存不可见问题。对于一写多读,可以解决变量同步问题。但是,如果多写,同样无法解决线程安全问题。如果是 count++ 操作,推荐使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK1.8,推荐使用 LongAdder 对象,它比 AtomicLong 性能更好(减少了乐观锁的重试次数)。 | 参考 | |
HashMap 在容量不够时会进行 resize 操作,由于高并发可能出现死链,导致 CPU 占用飙升,在开发过程中可以使用其他数据结构或加锁来避免此风险。 | 参考 | |
ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的。 | 参考 |
4.异常日志规范
1.异常处理
规范 | Level | 备注 | |
---|---|---|---|
DAO 层 | 分层异常处理,在 DAO 层产生的异常类型有很多,无法细粒度进行 catch,应该使用 catch 方式,并 throw new DAOException(e),不需要打印日志。 | 参考 | |
Service 层 | 日志在 Service 层,必须要捕获并写到日志文件中去,记录到磁盘,尽可能带上参数信息,保护案发现场。 | 参考 | |
Web 层 | Web 层绝不应该继续往上抛异常。如果该异常将导致页面无法正常渲染,可直接跳转到错误页面;开放接口层需要将异常处理成错误码和错误信息方式返回。 | 参考 |
2.日志规范
规范 | Level | 备注 |
---|---|---|
应用中不可直接使用日志框架 (Log4j、Logback) 中的 API,而应依赖使用日志框架 SLF4J 中的 API。使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。 | 强制 | |
日志文件推荐保存 15 天。 | 强制 | |
应用中的扩展日志 (如打点、临时监控、访问日志等) 命名方式: appName_logType_logName.log,logType 为日志类型,推荐分类有 stats/monitor/visit 等;logName 为日志描述。 | 强制 | |
避免重复打印日志,否则浪费磁盘空间。在日志配置文件中设置 additivity=“false”。 | 强制 | |
异常信息应该包括两类: 案发现场信息和异常堆栈信息。如果不处理,那么通过 throws 往上抛出。例如: logger.error(各类参数或者对象toString + “_” + e.getMessage(), e); | 强制 | |
生产环境禁止输出 debug 日志;有选择的输出 info 日志。 | 推荐 |
5.安全规范
规范 | Level | 备注 |
---|---|---|
隶属于用户个人的页面或者功能,必须进行权限控制校验。 | 强制 | |
敏感数据禁止直接展示 | 强制 | |
用户请求传入的任何参数必须做有效性校验。 | 强制 | |
禁止向 html 页面输出未经安全过滤或未正确转义的用户数据。 | 强制 | |
表单、Ajax 提交必须执行 CSRF 安全过滤。 | 强制 | |
在使用平台资源时,如短信、邮件、电话、下单、支付等,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。 | 强制 | |
针对发帖、评论、发送即时消息等用户生成内容的场景,必须实现防刷、文本内容违禁词过滤等风控策略。 | 推荐 |
以上是关于Java 开发规范的主要内容,如果未能解决你的问题,请参考以下文章