目录
- [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 目的
- 约束编码行为;
- 作为基本编码规范之外的补充;
- 提供常见编码场景的解决方法;
2 基本原则
2.1 六大原则-单一职责原则
- 含义:只负责一件事情
- 描述:单一职责原则很简单,方法 、类、组件、模块、项目,只负责一个职责,各个职责的程序改动,不影响其它程序。
- 优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。
2.2 六大原则-依赖倒置原则
- 含义:高层次的模块不依赖于低层模块;高层依赖于抽象,抽象不依赖于具体实现,具体实现应该依赖于抽象。
- 描述:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。在具体业务中,通过高层代码搭建业务框架,低层实现具体业务逻辑,未来修改需求时,大概率只修改低层逻辑。
- 优点:可以减少需求变化带来的工作量,降低对核心逻辑的影响。
2.3 六大原则-接口隔离原则
- 含义:类和类之间应该通过接口进行依赖。
- 描述:类A通过接口(B*)依赖B;当变更B内部逻辑时,只要保持B接口不变,B的实现逻辑变化对A无任何影响;单测A类时,也可以通过对B做mock很容易做单测。
- 优点:提高程序的灵活度,提高内聚,减少对外交互,使得最小的接口做最多的事情。
2.4 六大原则-开放封闭原则
- 含义:尽量通过新增代码解决需求变化,而不是通过修改已有的代码解决变化。
- 描述:一个软件产品在生命周期内,都会发生变化,变化是一个既定的事实,在设计的时候要适应变化,以提高项目的稳定性和灵活性。
- 优点:当需求变化时,可以通过新增代码实现,对原有实现影响少,引入的问题会变少。
2.5 六大原则-里氏替换原则
- 含义:使用的基类(接口)的地方,都可以直接使用任意的子类或接口实现类。
- 描述:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
- 优点:通过子类可扩展多种能力;可以直接替换基类(接口),提高了代码复用率。
2.6 六大原则-迪米特法则
- 含义:一个对象应当对其他对象有尽可能少地了解,简称类间解耦。
- 描述:一个类尽量减少自己对其他对象的依赖;依赖越多,就越难于理解和修改;依赖减少,就会越符合单一职责,越提升代码纯粹性,进一步提升可读性和复用性。
- 优点:低耦合,高内聚。
2.7 接口、继承与组合
- 接口:服务间行为约定,修改时需:保持兼容、频率很低、修改慎重。
- 继承:可以提高父类中某些方法的复用,但也在多个子类中耦合,修改父类变得困难,要求尽量少用继承,尤其在业务系统中。
- 组合:当有复用某逻辑需求时,应该在调用方使用组合方式。
正确做法:
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 常量定义
- 有含义,有意义;
- 管理常量要分类,不同模块、不同项目、不同功能等;
- 常量类列表,建议不超过10个;
3.2 map、set的使用
- 传参数、接受参数,除非是系统组件,业务组件禁止;
- map在使用时,如果是局部变量使用普通HashMap;
- 如果是并发调用时,建议需用ConcurrentHashMap;如果使用普通Map需加锁或同步块;
3.3 函数式编程
- 函数式连续调用,要写清楚注释;
- 过长、难于理解的,建议使用普通编码;
- 不建议使用函数式的线程模式(Lists.parallelStream().map(xx).collect(yyy));
3.4 异常处理
- 要求统一使用异常处理组件,定义异常;
- try代码中,只放最小化的业务逻辑,满足单一职责;
- catch中,要明确处理
- 向上抛出原始异常或抛出自定义异常
- 根据业务需求,处理撤销、重试等
3.5 pom编写&mvn
- 按分类临近编写;
- 无用依赖删除;
- 测试用依赖,scope设置为test;
- 编译使用:mvn package;
- 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 禁止直接启动线程,应使用线程池
- 线程池设置合适大小;
- 线程池创建线程,要有名字;
- 创建非定时任务线程,要求使用TtlRunnable;
- scheduleWithFixedDelay这种调用,不能使用TtlRunnable;
- TtlRunnable主要目的是解决父子线程参数更好的传递;
示范
java
Runnable task = new Runnable() {
@Override
public void run() {
//some work
}
};
executor3.submit(TtlRunnable.get(task, true));
4.1.3 并发下修改变量
- 本地变量,增加线程锁;
- 修改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 多环境
- 禁止代码内嵌入环境判断
- 禁止根据环境嵌入用户