Java 开发规范

好的代码规范有助于减少软件实现的复杂度,降低沟通成本,本规范主要涵盖了软件设计、工程结构、编码、异常日志、单元测试、安全等方面的规范。

1.软件设计规范

场景 Level 备注
文档 需要沉淀文档的:存储方案、底层数据结构 强制
用例图 需求分析阶段,如果与系统交互的 User 超过 1 类,并且相关 User Case 超过 5 个,那么使用用例图来表达结构化需求。 强制
状态图 某个业务对象的状态超过 3 个,那么应该使用状态图来表达并且明确状态变化的各个触发条件。 强制
时序图 某个功能的调用链路上涉及的对象超过 3 个,则应使用时序图来表达并且明确各调用环节的输入与输出。 强制
类图 如果系统中模型类超过 5 个,并且存在复杂的依赖关系,则应使用类图来表达并且明确类之间的关系。 强制
活动图 如果系统中超过 2 个对象之间存在协作关系,并且需要表示复杂的处理流程,则使用活动图来表示。 强制
敏捷开发 敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但需要核心关键点上的必要设计和文档沉淀。 参考

2.工程结构规范

1.应用分层

层级依赖关系 如下(默认上层依赖于上层,箭头关系表示可直接依赖):

Java 开发规范_第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)