团队内部代码规范
1. 制定该规范的目的: 码出高效,码出质量.结合自己的业务场景,整理出适合自己的规范.
2. 当前版本规范主要包含下列内容:
一. 代码编码规范
(一) 基本规范
A. 命名规范
通用:以英文为主导的驼峰格式, 不允许出现拼音命名和中文命名,非实际意义的数字不可使用。
【正例】expense,name
【反例】feiyong,name1
(1)【强制】常量以下划线拼接, 全大写格式。常量应抽离放在类级别定义或常量类中单独定义,不能在业务逻辑中直接使用。 因为常量重复在栈中创建影响性能。
【正例】EXPENSE_DETAIL = “expense_detail”
【反例】expense_detail = “expense_detail”、expenseDetail = “expense_detail”
(2)【推荐】命名推荐全称英文,建议不要出现缩写、业界公认的缩写名称可使用。以系统固有使用规则为主。
【正例】conditionDetail、mvc、statistic
【反例】conDetail、stat
(3)【强制】各模式命名以具体的后缀结尾。方便理解和协同开发。
【正例】DefaultException、ExpenseTest.
【反例】DefaultExceptions(异常类)、ExpenseDemo(测试类)
(4)【强制】包名统一用小写。不允许出现复数形式包名
【正例】com.bj58.coin.utils、com.bj58.coin.controller
【反例】com.bj58.coin.coinModel
(5)【强制】领域模型对象如DTO/BO/DO/VO/PO,模型类命名应加响应模型后缀,且后缀全为大写。
【正例】ExpenseDTO、ExpenseVO
【反例】ExpenseDto
(6)【推荐】Dao层命名规约。
l 获取单个对象方法。用get做方法名前缀。
【正例】getExpense
【反例】selectExpense、searchExpense、findExpense、queryExpense
l 获取集合对象方法。用list做方法名前缀。
【正例】listExpense
【反例】getExpense、getExpenses、selectExpenseList
l 获取统计值方法。用count做方法名前缀或后缀
【正例】countExpense
【反例】getExpenseCount、selectCountOfExpense
l 新增对象方法。用insert做方法名前缀。
【正例】addExpense
【反例】insertExpense
l 删除方法。用remove做方法名前缀。
【正例】removeExpense
【反例】updateExpense、clearExpense、delExpense、deleteExpense
l 更新方法。用modify做方法名前缀。逻辑删除亦然。
【正例】modifyExpense
【反例】changeExpense、updateExpense
B. 代码格式
(1)【推荐】大括号使用约定,若大括号内为空。简洁写成{}即可。否则:除左大括号左边不需要换行外,左大括号右边、右大括号左右两边均需要换行。
【正例】
if ( a == b) {
// TODO
}
【反例1】
if (a == b){ // TODO }
【反例2】
if (a == b)
{
// TODO
}
【反例3】
if (a == b)
{// TODO }
(2)【推荐】数学运算符左右两边均需要空格。例如:
【正例】if (a == b)
【反例】if (a==b)、if (a== b)、if (a ==b)
(3)【推荐】if/for/while/switch/do等保留字与括号之间都必须加空格。
【正例】if (a == b)、while (a == 1)
【反例】if(a==b)、while(a == 1)
(4)【推荐】注释符号与注释内容之间有且仅有一个空格。
【正例】// 注释
【反例】//注释
(5)【推荐】方法调用时,需要多个换行时,在点号或逗号前换行。
【正例】
CoinUtil.toggle(condition_one
, condition_two
, condition_three
, condition_four);
【反例】
CoinUtil.toggle(condition_one,
condition_two,
condition_three,
condition_four);
(6)【推荐】方法参数在定义或调用时,多个参数逗号后必须加空格。
【正例】CoinUtil.toggle(one, two, three);
【反例】CoinUtil.toggle(one,two,three);
(7)Object类的equals()方法容易抛空指针异常。使用常量或确定的值来调用equals。例如:
【正例】“test”.equals(object)
【反例】
String object = null;
// 空指针异常
object.equals(“test”);
(8)【推荐】所有的领域模型对象中,属性不允许使用基本类型定义。防止基本类型的默认值给业务使用上带来不必要的麻烦。分场景??
【正例】Integer a; Boolean deletedFlag;
【反例】int a; boolean isDeleted;
(9)【强制】领域模型对象不允许改造其getter或setter方法。亦不允许初始化属性值。分场景???网络交互的模型不允许编写逻辑(对外提供协议、数据库交互)。
【正例】
public void setPrjId(long prjId) {
this.prjId = prjId;
}
【反例】
public void setPrjId(Long prjId) {
this.prjId = (null == prjId) ? 0L : prjId;
}
(10)【强制】业务逻辑中查询产生的模型对象或集合对象都需要进行非空判断来避免空指针异常。若已对空指针异常已做兜底机制则可忽略。
【正例】
List<Expense> expenseList = coinService.listExpense();
if (CollectionUtils.isEmpty(expenseList)) {
// TODO
}
【反例】
List<Expense> expenseList = coinService.listExpense();
// TODO (直接使用expenseList可能产生空指针异常)
(11)【推荐】使用StringBuilder类的append()方法来代替String类的“+”号拼接字符串操作。
【正例】StringBuilder date = new StringBuilder(“2020”)
.append( “-”).append( “09”)
.append(“-”).append(“21”);
【反例】String date = a + b + c + “-” + “21”;
(13)【推荐】类中存在相似或相仿的方法时,应紧邻放在一起,例如getWord()和getWordForSomething()方法。
(14)【推荐】if/else/for/while/do语句中必须使用大括号,即使只有一行代码。
【正例】
if (a == b) {
a++;
}
【反例】
if (a == b) a++;
(15)【推荐】if…else嵌套语句不得超过三层。采用取反、短路结束逻辑
【正例】
if (a != c) {
return;
}
if (a == b) {
a++;
}
【反例】
if (a == b) {
if (a == c) {
if ( a == d) {
if (a == e) {
a++;
}
}
}
}
(16)【强制】外部传入的入参对象不能直接传入Dao层。应经过model转换后再传入底层。
【正例】
public void update(CoinDTO record) {
CoinModel coinModel = new CoinModel();
BeanUtils. copyProperties(record, coinModel);
coinService.update(coinModel);
}
【反例】
public void update(CoinDTO record) {
coinService.update(record);
}
C. 异常规范
(1)【推荐】try…catch保证最小原则,即拒绝将大量已知稳定的业务逻辑放入其中。因为try…catch影响性能。
(2)【强制】非兜底捕获时,使用具体的异常类,而非用Exception一概而全。
【正例】
【反例】
(4)【强制】禁止捕获异常后catch块中处理逻辑为空。
【正例】
【反例】
(6)【强制】try…catch应采用最上层处理原则,拒绝每一个调用层内均增加try…catch。底层应选择将异常抛出给上层处理。
【正例】
【反例】
(二) 注释规范
(1·)【强制】类、类属性、类方法的注释必须使用Javadoc规范。即使用“/** 内容 */”格式。不得使用“/*内容*/” 或“//内容”的格式。
【正例】
【反例】
(2)【推荐】类、接口、文件都需注明@author属性和@date属性。增加当前类描述说明。
【正例】
【反例】
(3)【强制】方法内部单行注释,在备注语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意对齐。方法若多层调用且相当复杂,斟酌加上实际逻辑对应的文字说明。
【正例】
【反例】
(4)【强制】所有的枚举字段、常量字段均需要注释说明。说明具体用途。
【正例】
【反例】
(5)【强制】代码修改后、相应的参数、返回值、异常、核心逻辑释义也要做相应修改。
(6)【强制】谨慎注释掉代码。做详细说明、如注释原因、后续是否会恢复。若永不再用,则直接删除。
(7)待后续完成的功能。可使用TODO标注。代码错误无法工作的可使用FIXME标注。
【正例】存在TODO的方法,协作开发使用前会检查、更谨慎选择使用。
【反例】
二. 依赖组件操作规范
(一) DB使用规范
A. 建表规范
(1)【强制】主键必须为“id”, 不允许自定义主键名,建表需有COMMENT属性,注明每个字段的含义, 表名必须加备注,说明当前表的用处;
(2)【强制】不允许使用外键约束。
(3)【强制】表名中的单词没有复数一说,因为表名只表达的是当前表的内容.
【反例】如表名 orders
(4)【推荐】表名一定要包含一定的业务含义,如果表名是关键字的时候最好加修饰字段.不要只使用单个字段来表示表信息。
【反例】如表名 order 【正例】pay_order,resource_order
(5)【强制】表名禁用保留字段: desc ,like,status等。
https://www.cnblogs.com/zhuyeshen/p/11496545.html
(6)【强制】表存储引擎仅仅支持InnoDB,且必须指明ENGINE=InnoDB.
(7)【推荐】 表必备三字段:id, created_at, modified_at.,创建人, id表示主键, create_time, modified_time建议设为Long类型,两个字段分别表示当前记录的创建时间和更新时间。
(8)【推荐】普通索引名称必须以idx_开头,唯一索引必须以uniq_开头,且索引名越简洁越好(越长占用磁盘空间越大)。主键索引不允许自定义索引名。示例:idx_a_id、idx_a_id_b_id。
【正例】给create_at,user_id建联合索引:idx_create_at_user_id
(9)【推荐】大字段Text,varchar字段超过128以上不建议建立索引,varchar字段长度设置为2^N次方。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
索引很长的字符列,会让索引变得大且慢.部分字段建索引,可以节约索引空间,从而提高索引效率
(10)【强制】小数类型使用decimal格式,按需要设置小数位。禁止使用float和double格式。
(11)【推荐】使用数字类型(Long,int)等尽量使用unsigned类型来标注,int类型建议统一使用11位长度。这是int的最大长度。
(12)【强制】Long、varchar使用2的倍数设置字段长度。
(13)【强制】字段均需设置非空属性,varchar字段类型默认值是””(不能设置为Null)
B. 改表规范
(1)【强制】新增字段时、不能固定化直接放在所有字段之后,适当运用after/before方法。
(2)【强制】新建字段时必须指定默认值。且默认值不能为null
(3)【强制】更新表字段名时、若注释已改、相应的COMMENT值也要随之变化。
(4)【强制】数据库操作规范(DDL):包括增加,修改,删除表字段,增加索引,清理表碎片等操作.所有的DDL操作强制遵循以下原则:
a. 在低峰期操作(高峰期的操作时容易引起主从数据不一致,进而影响交易)
b. 需和DBA沟通清楚,当前操作是否支持批量操作,是否会进行批量操作,当前操作的影响范围等信息
mongoDB的关于表的操作:同(4)
C. 数据操作规范
(1)【推荐】修复数据新增时、创建人应注明实际操作人。且时间为当前时间。
(2)【推荐】修复数据更新时、更新人应注明实际操作人。且时间为当前时间。
(3)【强制】删除数据时。自己做好数据备份。做好及时回滚的准备。
(4)【推荐】超过三表的联表查询需关注执行效率、谨慎使用。
(5)【强制】查询sql编写若非特定需求,禁止接口返回超过200条数据,需做分页。
(6)【推荐】查询SQL排序使用注意索引的顺序规则。
(7)【强制】sql条件里边的集合条件尽量用in选择确定值.
原始dao接口:
【反例】状态值直接取范围区间
【正例】状态值取确定的范围
(8) 【推荐】在表查询中,不要使用 * 作为查询的字段列表,需要哪些字段必须明确注明
说明:(1)增加查询分析器解析成本。
(2) 无用字段增加网络消耗,尤其是text类型的字段
(9)【推荐】统计数量时请使用count(*),count(*)是SQL92定义的标准统计行数的语法,跟数据库无关. count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行.
(10)【强制】当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题。
【正例】可以使用如下方式来避免sum的NPE问题:
SELECT IFNULL(SUM(column), 0) FROM table;
(11)【推荐】不要写一个大而全的数据更新接口。传入为POJO类,不管是不是自己的目标更新字段,都进行update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增加binlog存储。推荐使用mayatis的动态sql来设置更新值。
(12) 【推荐】Like查询时使用<bind name="bindeName" value="\'%\'+eName+\'%\'" />仅限于mybatis
(二) 内部RPC使用规范
(1)【强制】序列化目前不支持内部类。
(2)【强制】接口定义不需要加public 等访问权限控制符。
(3)【强制】序列化不支持map类型key为null。
(5)序列化不支持 File, InputStream, JSON 等没有加@SCFSerializable的java对象。
(6)序列化SCFV3不支持嵌套的泛型,如List<Map<String, Object>>, 这种类型SCFV4可以支持(SCFV4和SCFV3功能基本一致,升级不需要其他改动。)
(7)不支持JDK11,扫包会出现问题
(8)【推荐】对外和对内的SCF接口应单独拆分类使用。即使功能相同。
对外提供的接口粒度最小化.
(9)【强制】返回值使用固定包装类。拒绝数据裸奔。响应码,响应消息等信息。示例:
【正例】
【反例】
(10)【推荐】返回结果若为集合时,应返回空集合,而非null。
【正例】
【反例】
(三) Redis使用规范
(1)【强制】Redis不能随用随建,应建立合理的连接池使用。具体连接池参数需根据使用场景评估。
(2)【强制】Redis拒绝使用模糊查询。查询易使Redis服务宕机重启。
(3)【推荐】存储的Key必须设置过期时间。否则永久占用Redis内存,影响性能。权限类信息
(4)【强制】拒绝存储大文本(超过500字节)数据。
(5)【强制】Key命名规则:不同名称间使用“:”连接。Key名不易过长。固定最长64位。
【正例】
【反例】
(6)【强制】Redis对大集合的操作必须加上limit限制,limit200条,单条命令执行时间不宜过长,超过1ms可认为慢sql。
(7) redis必须做好数据备份,能快速恢复数据。
(四) 大数据平台使用规范
(1)【推荐】命名规范
Ø 大数据平台任务:{业务线}-{具体意义};若为全业务线的任务。则格式为{部门下的组名}-{具体意义}
比如云窗任务名称:
可以改为: 黄页线-积分订单指定时间数据统计
【正例】招聘-积分流水每日全量统计
【正例】积分-积分流水每日全量统计
Ø DP任务:{自定义前缀}{表名}{自定义后缀};主要是为了方便通过表名立刻定位具体任务。具体的自定义前缀和后缀为非必须值。
Ø 云窗表名:若为增量表固定格式:{自定义名}_daily_incr;全量表固定格式:{自定义名}_whole
(2)【强制】任务停用时,需在任务描述中注明具体停用原因。对应启用任务时删除相关停用描述。
(3)【强制】DP任务SQL中禁止使用 unix_timestamp()函数做日期转化操作。因为解析SQL时因使用了SQL的函数,导致Hive无法解析SQL进行优化,导致MySQL负载。
【正例】$bash{date -d\'${dateSuffix}\' +%s%3N}
【反例】unix_timestamp(\'${dateSuffix}\',\'yyyyMMdd\')*1000
(4)【强制】拉取MySQL数据库当且仅能使用ETL库。
(5)【推荐】DP拉取已增量拉取为主。若为特殊必要,谨慎使用全量拉取。并且需要设置定期删除策略。
三. 服务部署规范
(1) 【推荐】区分正式jar包和快照jar包两套规则,不能跳包。例如例如正式为包1.0.0,最新快照包为1.0.2-SNAPSHOT。打正式包时应为1.0.1。
(2) 【推荐】使用jdk版本1.8;
(3) 【推荐】Jvm参数设置:
设置metaspace: -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M
设置堆内存大小:最大值和最小值一样 -Xms4g -Xmx4g
设置测试环境远程debug端口:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5855
设置出现oom时自动dump内存文件快照:
-XX:+HeapDumpOnOutOfMemoryError
设置dump的文件目录 -XX:HeapDumpPath=${目录}
设置打印GC日志:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
四. 日志打印规范
(一) 日志规范
(1)【强制】日志需注明调用时间。(日志初始化配置应有相关配置)
(2)【推荐】日志进行具体分类。但不应过细。推荐日志分类不超过8个。
(3)【推荐】日志内容应正确使用占位符,而非字符串拼接方法。
【正例】
【反例】
(4)【推荐】对于集合类结果的打印需要考虑内存的承受,需要防止数据过大将内存击垮。
可以采用打印集合大小或者采用debug级别模式,在需要的时候再打印集合内容的方式
【反例】
(5)【强制】异常捕获后必须增加日志。且打印异常信息Exception。捕获后不处理是埋雷操作。
(6)【推荐】交易类接口需要打印业务的请求参数和响应参数,以方便排查业务调用相关的问题。
(7)【强制】日志打印内容需包括完整的类路径名称,以及交易的主标识信息,配置信息参考如下:
输出日志格式如下:
Error级别的日志按重要度可以考虑输出到邮件组,以供及时介入问题。
邮件信息配置如下:
五.服务架构
(1) 整体工程可以分为对外协议(contract),业务实现(service)
Service层用来进行具体的业务实现,可以划分为component,communication,dao,biz层等。
component:参数校验,模型转换
biz处理真正的业务逻辑
communication负责与第三方系统通信交互,比如操作日志,与摊销交互;
dao 负责mapper,sql操作底层db或redis
其中biz层还可根据不同的模块功能进行更细节的划分,外加其他模块的业务处理逻辑,共享一个工具类.
(2) 服务依赖中禁止进行直接/间接的循环依赖,需要让依赖保证线性的关系
【反例】A服务的pom中依赖B服务的接口,同时B服务中依赖A服务的接口
(3) 线上应用不要依赖SNAPSHOT版本;正式发布的类库必须先去中央仓库进行查证,使RELEASE版本号有延续性,且版本号不允许覆盖升级。
(4) 服务中依赖的外部服务,按场景做好降级
【正例】资源服务调用摊销接口时就做了降级,虽然是异步的调用,但是为了保证线程的复用和服务队列的堆积,防止超时带来的线程长时间阻塞而影响主服务;