Java编码规范

目录

  • [1 目的](#1 目的)
  • [2 基本原则](#2 基本原则)
    • [2.1 六大原则-单一职责原则](#2.1 六大原则-单一职责原则)
    • [2.2 六大原则-依赖倒置原则](#2.2 六大原则-依赖倒置原则)
    • [2.3 六大原则-接口隔离原则](#2.3 六大原则-接口隔离原则)
    • [2.4 六大原则-开放封闭原则](#2.4 六大原则-开放封闭原则)
    • [2.5 六大原则-里氏替换原则](#2.5 六大原则-里氏替换原则)
    • [2.6 六大原则-迪米特法则](#2.6 六大原则-迪米特法则)
    • [2.7 接口、继承与组合](#2.7 接口、继承与组合)
    • [2.8 就近原则](#2.8 就近原则)
    • [2.9 规模原则](#2.9 规模原则)
    • [2.10 命名原则](#2.10 命名原则)
  • [3 基本编码](#3 基本编码)
    • [3.1 常量定义](#3.1 常量定义)
    • [3.2 map、set的使用](#3.2 map、set的使用)
    • [3.3 函数式编程](#3.3 函数式编程)
    • [3.4 异常处理](#3.4 异常处理)
    • [3.5 pom编写&mvn](#3.5 pom编写&mvn)
  • [4 特殊场景编码](#4 特殊场景编码)
    • [4.1 多线程](#4.1 多线程)
      • [4.1.1 并发同步块范围最小](#4.1.1 并发同步块范围最小)
      • [4.1.2 禁止直接启动线程,应使用线程池](#4.1.2 禁止直接启动线程,应使用线程池)
      • [4.1.3 并发下修改变量](#4.1.3 并发下修改变量)
    • [4.2 读写文件](#4.2 读写文件)
    • [4.3 数据库](#4.3 数据库)
    • [4.4 事务处理](#4.4 事务处理)
    • [4.5 幂等访问](#4.5 幂等访问)
    • [4.6 文件上传](#4.6 文件上传)
    • [4.7 时间处理](#4.7 时间处理)
    • [4.8 缓存使用](#4.8 缓存使用)
    • [4.9 外部系统访问](#4.9 外部系统访问)
    • [4.10 多环境](#4.10 多环境)

1 目的

  1. 约束编码行为;
  2. 作为基本编码规范之外的补充;
  3. 提供常见编码场景的解决方法;

2 基本原则

2.1 六大原则-单一职责原则

  1. 含义:只负责一件事情
  2. 描述:单一职责原则很简单,方法 、类、组件、模块、项目,只负责一个职责,各个职责的程序改动,不影响其它程序。
  3. 优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。

2.2 六大原则-依赖倒置原则

  1. 含义:高层次的模块不依赖于低层模块;高层依赖于抽象,抽象不依赖于具体实现,具体实现应该依赖于抽象。
  2. 描述:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。在具体业务中,通过高层代码搭建业务框架,低层实现具体业务逻辑,未来修改需求时,大概率只修改低层逻辑。
  3. 优点:可以减少需求变化带来的工作量,降低对核心逻辑的影响。

2.3 六大原则-接口隔离原则

  1. 含义:类和类之间应该通过接口进行依赖。
  2. 描述:类A通过接口(B*)依赖B;当变更B内部逻辑时,只要保持B接口不变,B的实现逻辑变化对A无任何影响;单测A类时,也可以通过对B做mock很容易做单测。
  3. 优点:提高程序的灵活度,提高内聚,减少对外交互,使得最小的接口做最多的事情。

2.4 六大原则-开放封闭原则

  1. 含义:尽量通过新增代码解决需求变化,而不是通过修改已有的代码解决变化。
  2. 描述:一个软件产品在生命周期内,都会发生变化,变化是一个既定的事实,在设计的时候要适应变化,以提高项目的稳定性和灵活性。
  3. 优点:当需求变化时,可以通过新增代码实现,对原有实现影响少,引入的问题会变少。

2.5 六大原则-里氏替换原则

  1. 含义:使用的基类(接口)的地方,都可以直接使用任意的子类或接口实现类。
  2. 描述:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
  3. 优点:通过子类可扩展多种能力;可以直接替换基类(接口),提高了代码复用率。

2.6 六大原则-迪米特法则

  1. 含义:一个对象应当对其他对象有尽可能少地了解,简称类间解耦。
  2. 描述:一个类尽量减少自己对其他对象的依赖;依赖越多,就越难于理解和修改;依赖减少,就会越符合单一职责,越提升代码纯粹性,进一步提升可读性和复用性。
  3. 优点:低耦合,高内聚。

2.7 接口、继承与组合

  1. 接口:服务间行为约定,修改时需:保持兼容、频率很低、修改慎重。
  2. 继承:可以提高父类中某些方法的复用,但也在多个子类中耦合,修改父类变得困难,要求尽量少用继承,尤其在业务系统中。
  3. 组合:当有复用某逻辑需求时,应该在调用方使用组合方式。

正确做法:

java 复制代码
public interface ServiceA{
    void say();
}
public class MyServiceA implements ServiceA{
    @Override
    public void say(){
        //some work
    }
}
 
public class MyWork{
    private ServiceA serviceA;
    public void doSome(){
       serviceA.say();
       //some work
    }
}

错误做法:

java 复制代码
public class MyServiceA{
    public void say(){
        //some work
    }
}
 
public class MyWork extends MyServiceA{
     
    public void doSome(){
       say();
       //some work
    }
}

2.8 就近原则

  • 含义:代码编写功能目标一致的代码写在一起,离得越近,理解与阅读方便;修改代码时,容易;遵循单一职责;
  • 变量定义:在最近使用处定义;
  • 函数定义:功能近似、有调用关系的,同一个类中,顺序编写;
  • 类定义:多个类完成某一个目标(如用户操作、文件上传、离职流程等),放在一个包名;
  • 模块定义:多个模块完成某一系统,放在一个代码库中;

2.9 规模原则

  • 函数:建议不超过100行;
  • 类:建议不超过500行;
  • 接口:建议不超过20个函数;
  • 对象:建议不超过30个属性;
  • 包名:建议包含类不超过20个;建议包含子包名不超过10个;
  • 工程:建议不超过10个模块;
  • 对外接口:建议不使用通用接口(内部判断再执行不同功能),每功能独立接口;

2.10 命名原则

  • 变量命名:简明短小,含义清楚;
  • 函数命名:动词+名词;
  • 类命名:表征一类行为、一类数据、一类算法;
  • 包名:全小写字母,包名是对类进行的分类、分层,包名包含模块名、工程名;
  • 模块名:全小写,字母+中划线,包含工程名;
  • 工程名:含义明确,统一规划;

3 基本编码

3.1 常量定义

  1. 有含义,有意义;
  2. 管理常量要分类,不同模块、不同项目、不同功能等;
  3. 常量类列表,建议不超过10个;

3.2 map、set的使用

  1. 传参数、接受参数,除非是系统组件,业务组件禁止;
  2. map在使用时,如果是局部变量使用普通HashMap;
  3. 如果是并发调用时,建议需用ConcurrentHashMap;如果使用普通Map需加锁或同步块;

3.3 函数式编程

  1. 函数式连续调用,要写清楚注释;
  2. 过长、难于理解的,建议使用普通编码;
  3. 不建议使用函数式的线程模式(Lists.parallelStream().map(xx).collect(yyy));

3.4 异常处理

  1. 要求统一使用异常处理组件,定义异常;
  2. try代码中,只放最小化的业务逻辑,满足单一职责;
  3. catch中,要明确处理
    • 向上抛出原始异常或抛出自定义异常
    • 根据业务需求,处理撤销、重试等

3.5 pom编写&mvn

  1. 按分类临近编写;
  2. 无用依赖删除;
  3. 测试用依赖,scope设置为test;
  4. 编译使用:mvn package;
  5. mvn编译,必须使用-U参数;

4 特殊场景编码

4.1 多线程

4.1.1 并发同步块范围最小

正确写法:

java 复制代码
long currFileTime = file.lastModified();
if (currFileTime > lastUpdateTime) {
    synchronized (this) {
        if (currFileTime > lastUpdateTime) {
            try {
                this.cfg = CfgFactory.buildByStream(new FileInputStream(file));
                LOGGER.info("Load {} success.", APPKEY_MAPPING_FILE);
                lastUpdateTime = currFileTime;
            } catch (Exception e) {
                LOGGER.error("Load {} fail.", APPKEY_MAPPING_FILE, e);
            }
        }
    }
}

错误写法1:范围过大,不断获取锁,性能差

java 复制代码
synchronized (this) {
     long currFileTime = file.lastModified();
     if (currFileTime > lastUpdateTime) {
            try {
                this.cfg = CfgFactory.buildByStream(new FileInputStream(file));
                LOGGER.info("Load {} success.", APPKEY_MAPPING_FILE);
                lastUpdateTime = currFileTime;
            } catch (Exception e) {
                LOGGER.error("Load {} fail.", APPKEY_MAPPING_FILE, e);
            }
    }
}

错误写法2:并发错误,可能并发进入

java 复制代码
long currFileTime = file.lastModified();
if (currFileTime > lastUpdateTime) {
    synchronized (this) {
            try {
                this.cfg = CfgFactory.buildByStream(new FileInputStream(file));
                LOGGER.info("Load {} success.", APPKEY_MAPPING_FILE);
                lastUpdateTime = currFileTime;
            } catch (Exception e) {
                LOGGER.error("Load {} fail.", APPKEY_MAPPING_FILE, e);
            }
        }
}

4.1.2 禁止直接启动线程,应使用线程池

  1. 线程池设置合适大小;
  2. 线程池创建线程,要有名字;
  3. 创建非定时任务线程,要求使用TtlRunnable;
  4. scheduleWithFixedDelay这种调用,不能使用TtlRunnable;
  5. TtlRunnable主要目的是解决父子线程参数更好的传递;

示范

java 复制代码
Runnable task = new Runnable() {
 
    @Override
    public void run() {
       //some work
    }
};
executor3.submit(TtlRunnable.get(task, true));

4.1.3 并发下修改变量

  1. 本地变量,增加线程锁;
  2. 修改mysql、redis变量,分析并发风险,增加分布式锁;

4.2 读写文件

  • 非特殊原因,统一使用org.apache.commons.io.IOUtils;
  • 文件处理,统一使用org.apache.commons.io.FileUtils;

4.3 数据库

  • 统一使用mybatis
  • 连接池使用:com.alibaba.druid.pool.DruidDataSource
  • 默认禁止使用其他数据技术
java 复制代码
name : xxx
url : xxx
username : xxx
password : "xxx"
type : com.alibaba.druid.pool.DruidDataSource
driver-class-name : com.mysql.cj.jdbc.Driver
filters : stat
maxActive : 20
initialSize : 1
maxWait : 60000
minIdle : 1
timeBetweenEvictionRunsMillis : 60000
minEvictableIdleTimeMillis : 300000
validationQuery : select 'x'
testWhileIdle : true
testOnBorrow : true
testOnReturn : false
poolPreparedStatements : true
maxOpenPreparedStatements : 20

4.4 事务处理

  • 事务处理中,调用其他网路服务,要求本地保存数据;
  • 异步执行网络请求,成功或失败后更新数据状态;
  • 增加补偿机制,达到次数后触发人工;
  • 最小化事物处理逻辑;
  • 代码逻辑只跟写操作有关,其他逻辑不放在事务范围;
  • 尽量不访问网络,只包含数据库操作;
  • 事务注解,要过滤掉MySQLTransactionRollbackException;
java 复制代码
@Transactional(noRollbackFor = MySQLTransactionRollbackException.class)

4.5 幂等访问

  • 对外提供写服务,要求满足幂等性;
  • 访问第三方服务,对方未提供幂等性
    • 本地记录失败请求
    • 通过查询获取第三方状态
    • 转人工

4.6 文件上传

非特殊原因,统一使用http组件;

4.7 时间处理

统一时间处理组件

4.8 缓存使用

  • 默认禁止使用缓存;
  • 后续,有特殊原因(性能),统一使用缓存组件;

4.9 外部系统访问

  • 对外系统,统一包名:remote,与service同级;
  • 内部系统间,要求使用rpc方式;
  • 系统提供对外rpc服务,统一包名:rpc,与service同级;

4.10 多环境

  • 禁止代码内嵌入环境判断
  • 禁止根据环境嵌入用户
相关推荐
qq_353233538927 分钟前
【原创】java+ssm+mysql校园疫情防控管理系统设计与实现
java·mvc·javaweb·ssm框架·bs·疫情防控
谈谈叭3 小时前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
lx学习3 小时前
Python学习26天
开发语言·python·学习
代码调试3 小时前
Springboot校园失物招领平台
java·spring boot
大今野4 小时前
python习题练习
开发语言·python
爱编程的鱼4 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
camellias_4 小时前
SpringBoot(二十三)SpringBoot集成JWT
java·spring boot·后端
tebukaopu1484 小时前
springboot如何获取控制层get和Post入参
java·spring boot·后端
昔我往昔4 小时前
SpringBoot 创建对象常见的几种方式
java·spring boot·后端
q567315234 小时前
用 PHP或Python加密字符串,用iOS解密
java·python·ios·缓存·php·命令模式