开发规范详解
Posted GeorgeLin98
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开发规范详解相关的知识,希望对你有一定的参考价值。
命名规范
命名规范:
- 不能以下划线或美元符号开始,也不能以下划线或美元符号结束
- 反例: _name / __name / O b j e c t / n a m e _ / n a m e Object / name\\_ / name Object/name_/name / Object$
类名规范:
- 类名使用 UpperCamelCase 风格,必须遵从驼峰形式。
- 但以下情形例外: (领域模型的相关命名 )DO / BO / AO / PO / DTO / VO / DAO等。
①正例: MarcoPolo / UserVO /
②反例: macroPolo / UserVo/ - Service /DAO接口类名必须以字母 I 开头,实现类必须 Impl 结尾
①正例: IUserDAO / UserDAOImpl /
②反例: UserDAO / UserDAO / - 根据类命名时体现它用途、设计模式。(推荐)
方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式:
- 正例: localValue / getHttpMessage() / inputUserId
- 反例: localvalue / gethttpmessage() / inputuserid
- 禁止使用xxPO作为临时变量。
常量命名全部大写,单词间用下划线隔开:
- 正例: MAX_STOCK_COUNT
- 反例: max_stock_count
- DAO中sql的常量必须为:SQL +下划线+对应方法名的大写下划线分割单词的形式 例如:private static final String SQL_FIND_USER_INFO="";
包名package都统一使用小写:
- 正例:package cn.com.do1.compoment.form.queryreport
- 反例:package cn.com.do1.compoment.form.queryReport
代码和注释中都要避免使用任何语言的种族歧视性词语:
- 正例:日本人 / 印度人 / blockList / allowList / secondary
- 反例:RIBENGUIZI / Asan / blackList / whiteList / slave
编码规范
使用MAP和实体类作为参数的优缺点:
- map的优点:
1、灵活性强于javabean,易扩展,耦合度低。
2、写起来简单,代码量少。
3、mybatis 查询的返回结果本身就是MAP,可能会比返回javabean快 - map的缺点:
1、javabean在数据输入编译期就会对一些数据类型进行校验,如果出错会直接提示。而map的数据类型则需要到sql层,才会进行处理判断。
2、map的参数名称如果写错,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言javabean会在编译期间发现错误
3、map的参数值如果多传、乱传,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言javabean会在编译期间发现错误
4、仅仅看方法签名,你不清楚Map中所拥有的参数个数、类型、每个参数代表的含义。 后期人员去维护,例如需要加一个参数等,如果项目
层次较多,就需要把每一层的代码都了解清楚才能知道传递了哪些参数 - Javabean的优点:
1、面向对象的良好诠释、
2、数据结构清晰,便于团队开发 & 后期维护。
3、代码足够健壮,可以排除掉编译期错误 - javabean的缺点:
1、代码量增多,大量时间去封装用到的表对象。
2、可能会影响开发效率。 - 总结:权衡利弊,如果团队开发还是javabean比较好,个人项目就无所谓了。追求高效开发,可以使用Map
状态、类型等常量规范:
- 状态类型等常量数据,使用枚举或者全局变量统一管理,并在注释中描述清楚意义,方便二次开发人员理解语义。
- 正例:contactService.getContactSyncCorpIdList(ContactSyncStatus.STATUS_WAIT)
- 反例:
TbQyExperienceAgentHistoryPO history = new TbQyExperienceAgentHistoryPO();
//这里没人知道1表示什么意思
history.setIsRangeAll(1);
- 常量类:
public class ConstantUtil {
//已发布
public static final int IS_PUBLISH = 1;
//未发布
public static final int IS_NOT_PUBLISH = 2;
//草稿
public static final int DRAFT = 2;
方法规范:
- 方法长度不大于100行。
- 方法、接口、全局变量(常量)必须有规范的注释。
- IDEA开发工具产生如下提示,证明该段代码再其它地方也有出现,应将此代码块封装成公用方法。
equals等前使用已知的不为空的对象:
- 正例:“user”.equals(tips)
- 反例:tips.equals(“user”)
代码结构优化:
- 时刻保持代码的简洁,清晰,可阅读。
- 不能出现4层及4层以上的if判断语句,出现4层以内无法完成的判断要有结构说明。
即最多三层if判断语句。
可以考虑用短路法。(卫语句) - 正例:
if (po == null || po.getStatus() == null) {
// 直接return,break或continue等等
return;
}
if (po.getStatus() != ConstantUtil.IS_PUBLISH && !AssertUtil.isEmpty(po.getName())) {
// 一些代码
}
// 一些代码
- 反例:
if(po != null) {
if(po.getStatus() != null) {
if(po.getStatus().intValue() != ConstantUtil.IS_PUBLISH ) {
if(!AssertUtil.isEmpty(po.getName())) {
// 一些代码
}
}
}
}
废弃类和方法的要加上注解@Deprecated:
- 有问题,或有局限性、性能差的类和方法,但又不能当场删除的,以免报错,需要加上注解@Deprecated,对调用的同事有提示作用,同时标注优化后的代码。并且要在两个月迭代版本以内彻底删除。
- 正例:
// 两个日期相隔一定范围,会超出int的最大范围,优化为compareDateLong
@Deprecated
public static int compareDate(Date start, Date end)
- 反例:
public static int compareDate(Date start, Date end)
if和else条件后面就算是单行也要用大括号:
- 正例:
if (condition){
a++;
}
else {
b++;
}
- 反例:
if (condition)a++;
else b++;
方法形参不能超过7个,大于7个使用VO传参:
- 形参过多容易造成类型一样的参数位置错乱。而且必须折行,可阅读性差。
- 正例:
ParamVO vo = new ParamVO();
vo.setId(id);
vo.setName(name);
vo.setGender(gender);
vo.setHeight(height);
vo.setWeight(weight);
vo.setPhoto(photo);
vo.setHobby(hobby);
vo.setDescription(description);
Util.process(vo);
- 反例:
Util.process(id,name,gender,height,weight,photo,hobby,description);
传参确定的情形使用VO,不用Map:
- 使用map一来不确定里面参数和类型,二来访问效率比VO慢。
- 正例:
ParamVO vo = new ParamVO();
vo.setId(id);
vo.setName(name);
Util.process(vo);
- 反例:
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
Util.process(map);
禁止使用import * :
-
全部import指定class,可以减少合并冲突。
-
IDEA设置方法:Settings -> Editor -> Code Style -> Java -> Imports 两个count
to use import with ‘*’ 设置成999。
-
正例:
import cn.com.do1.common.exception.BaseException;
import cn.com.do1.common.exception.ExceptionCenter;
import cn.com.do1.common.exception.NonePrintException;
- 反例:
import cn.com.do1.common.exception.*;
及时关闭流,连接等AutoCloseable和Closeable的子类:
- 流对象都是JVM的大对象,用完就关。
- 关闭对象必须写在finally,或者采用jdk7的try-with-resources。
- 正例:
File file = new File("1.txt");
FileInputStream fileInputStream = null;
try{
fileInputStream = new FileInputStream(file);
// some code
}finally{
if(fileInputStream != null){
fileInputStream.close();
}
}
- 推荐:
File file = new File("1.txt");
try (FileInputStream fileInputStream
= new FileInputStream(file)) {
// some code
}
- 反例:
File file = new File("1.txt");
FileInputStream fileInputStream = null;
fileInputStream = new FileInputStream(file);
// some code
fileInputStream.close();
-
关闭顺序:
①一般情况:先打开的后关闭,后打开的先关闭
②另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。
③可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。如果将节点流关闭以后再关闭处理流,会抛出IO异常。如果关闭了处理流,在关闭与之相关的节点流,也可能出现IO异常。继承了Filter开头FilterInputStream,FilterOutputStream,FilterReader,FilterWriter,还有InputStreamReader,OutputStreamWriter和Buffered开头的BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter都是处理流。 -
分开close,能尽最大努力关闭所有流。想减少代码块?推荐使用IOUtils.closeQuietly(closeable)。
-
正例:
InputStream inputStream = null;
OutputStream outputStream = null;
// someCode
try {
inputStream.close();
}catch (IOException e){
}
try {
outputStream.close();
}catch (IOException e){
}
- 推荐:
InputStream inputStream = null;
OutputStream outputStream = null;
// someCode
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
- 反例:
InputStream inputStream = null;
OutputStream outputStream = null;
// someCode
try {
inputStream.close();
outputStream.close();
}catch (IOException e){
}
- 尽量使用Buffered开头的缓冲流BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,原理是内存中提供一个字节数组来做缓冲,能提高读写效率和避免频繁读写物理硬件。
禁止提交的代码.iml文件:
- 使用gitingore
注释规范
注释格式:
- 接口注释必须写清楚接口的用途、传参、返回数据、创建人、创建日期等信息
- 类注释要写明类的用途
- 在方法内的注释,首先要写明方法的主要功能,将功能分成123…步骤,并将这些步骤写在注释用,方便接盘侠更好的读懂代码,了解写代码人的思路。
/**
- 获取该用户所在部门下的用户(不包括子部门的用户)
- @param orgId 机构ID
- @param userId 当前登录用户ID
- @return
- @throws Exception
- @throws BaseException
- @author Xiang Dejun
- @2014-12-26
*/
注释中不能包含TODO XXX FIXME的代码,适用于前后端:
- IDEA自动生成的TODO XXX FIXME不可提交。
- 自己手动编写的TODO XXX FIXME ,必须描述清楚还差什么任务,完成或修复后必须删除。
①TODO 表示代办。
②FIXME 表示有bug。 - 正例:
// TODO chenhandong:这段代码存在性能问题,将在2017-12-01优化。
- 反例:
// TODO: handle exception
// TODO 自动生成的 catch 块
// TODO 自动生成的方法存根
// TODO Auto-generated catch block
// TODO Auto-generated method stub
// TODO 自动生成的构造函数存根
代码中特殊的注释 //TODO //FIXME的用处:
TODO: + 说明:
如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会简略说明FIXME: + 说明:
如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明。
禁止使用行尾注释:
- 行尾注释会导致单行过长。
性能规范
禁止循环中while/for操作数据库:
-
反例:
-
优化方案,可以通过for循环获得查询条件中的关键信息,然后在查询sql中使用in来批量查询,注意批量查询时控制批量查询的数量,建议控制在2000条内,如果无法提前预估查询的数量大小,可以通过程序分批执行。例如
while(true)使用规范:
- 特殊情况下需要使用while(true)或者不能明确循环次数的情况下,在使用while时需要特别注意,防止死循环,为了避免进入到死循环状态,可以先预估一个while循环不会超过的次数,以此作为额外的循环结束条件,即可避免无限循环。
// 循环计数
int time = 0;
while(true){
time++;
// 当达到条件时,跳出循环
if(condition){
break;
}
// 死循环防范代码
if(time > 1000){
break;
}
}
双层for循环对两个对象比较:
- 如果允许的情况下,应避免此类的写法出现,如果需要两层for循环对两个队列进行比较时,请使用临时的Map作为比较对象,先循环一个队列,将数据转为Map数据,然后在循环另外一个数据,通过Map获取对应于第一个队列内的数据,以此可以大大降低时间复杂度。
mysql数据库规范
长度最小原则:
- 更少的磁盘,CPU缓存,IO开销
- 用uuid32不用uuid36
- TINY优先于SMALL优先于MEDIUM优先于BIG
- 金额可用BIGINT存(分做单位),要好于DECIMAL。
- 一级主键(存在页面上访问参数的id)使用uuid32 CHAR(32) latin1(注意:如果一级主键已经使用utf8,并且在无法修改为latin1的情况下,那么二级表里一级主键外键(被迫)仍然采用utf8),因为连表查询只有字段类型一样才不会出问题。
- 二级主键(不存在页面上访问参数的id)使用INT UNSIGNED AUTO_INCREMENT
- 类别状态码使用TINYINT(2)
选择最好的类型:
- 长度固定或差不多位数使用char而不是varchar。
- 能用整型就不用varchar,能用date/datetime也不用varchar。
- TEXT字段不要建立索引。
- 禁止使用TIMESTAMP。
索引规范:
- 通过索引扫描的记录数超过30%,变成全表扫描。
- 索引不是越多越好。
- 根据查询条件决定建立哪些索引。
- 建立联合索引时将最常用的查询条件放在最前面。
- 表关联字段类型,长度和字符编码保持一致。
- 查询条件上索引列避免使用函数。
- NULL的索引很复杂,避免在存在NULL的字段建立索引。
- 严禁sql中使用函数操作列。
- http://imysql.com/
建表严格要求加上org_id:
- org_id varchar(36) DEFAULT NULL COMMENT ‘机构id’
- 方便用户迁移数据,除非你的表是系统类的。
- 推荐:org_id建立索引。
提交sql语句规范:
- 提交配置类sql,如配置项:tb_dqdp_config,静态字典:dictionary,定时任务:tb_dqdp_job,权限配置:tb_dqdp_permission,主键uuid必须本地生成,以保证测试与生产环境一致。
- 提交sql语句必须分号结尾。
- 同表多个字段变更要合并成一条sql语句。
- sql语句必须写在对应数据库和日期线以内(看图)。
- 禁止提交报错的sql。
- sql要注释作者、日期和需求说明。
- 分库sql提交到对应的文件里(看表)。
git分支规范
代码开发和提交步骤:
- 从master分支检出自己的分支(需求/补丁分支);
- 每天更新自己的代码;
- 提交前一定要merge远程master分支的代码到自己分支(除了merge远程master分支外不允许merge其它任何分支代码);
- 将自己的分支push到远程
Git分支说明:
master - 主分支,各需求分支都必须从此分支检出,也只能从master分支merge代码到自己的分支;
- develop - 开发分支,用于需求在测试环境自动发布测试的分支,可将近期需要上线的需求merge到此分支进行测试环境测试;
- release - 测试人员测试分支,需求单或者补丁单在【小组负责人确认】后自动将相应分支代码合并到release分支,以保证测试人员拥有一个相对稳定的测试环境。
- prod - 发布分支,用于下个版本或者补丁正式发布的打包分支,【合并请求】通过后会将代码merge到此恩智;
- fixbug - 备用发布分支,目前作为bug备用发布分支存在,当prod分支无法打包发布的情况下使用此分支发布。
- 其它分支 - 分支名称使用需求单里的“git分支名称”,需求上线后将自己的需求分支删除并push到git服务器。
测试环境和提交代码说明:
- 所有功能(需求/补丁)必须有自己分支,一个需求单有且只有一个git分支名,且前后端一致;
- 必须由需求负责人(或在需求负责人的审查下)将自己的分支代码合并到develop分支;
- 开发人员在develop分支测试通过后,
- 测试负责人根据需求或补丁的测试结果来审批各需求负责人提交的合并申请;
- 合并成功后,所合并代码会自动发布到测试环境的prod分支。
操作(type):优先级由高到低:
- feat:新功能(feature),含性能优化
- fix:修补bug
- test:单元测试
- style: 仅仅修改了空格、格式缩进、逗号等等(不影响代码运行的变动)
- docs:仅仅修改了文档,比如README,CHANGELOG,CONTRIBUTED等等
- revert:撤销,版本回退
- 若同时提交多个操作,只需标识以最高级操作进行备注
代码研发:
研发时:
① 从master分支切一条出来作为自己的本地开发分支,根据需求单的给的分支名来命名
② 按照预定的解决方案进行研发,提交代码到自己的远程分支,提交前需要找负责人进行代码审查
③ 提供单测报告,压测脚本
④ 后端git提交代码注释规范:(不要超过50字)提交到dev:
① 从develop切一条分支,将自己开发分支合并进去,解决冲突
② 后端人员关注jenkins打包是否成功
③ 通知后端负责人部署到dev提交到release:
①过单,合并代码至release分支,若表单消息显示有冲突
<1>可将纯净的开发分支A先备份为A1
<2>从release切一条分支作为临时分支temp,合并自己的开发分支并解决冲突
<3> 删除远程A分支
<4> 将本地temp推到远程,命名为A
<5> 过单
<6> 删除远程A分支
<7>将原来纯净的A1分支重新推到远程,命名为A
②后端人员收到合并通知后,关注jenkins打包是否成功
③通知后端负责人部署到release提交到压测环境:
①研发人员跟测试人员确认准备压测
②测试人员向运维人员报备,得知第二天压测排期时间
③第二天测试人员进行压测
备注:压测脚本应由研发人员提供,若无,至少提供接口文档给测试人员提交到灰度:
①班车评审通过后,合并一下master
②后端负责人合并到小组总分支中,若表单消息显示有冲突,同release中的①
③测试人员合到灰度
④发布成功后,测试人员将灰度合到master
总结
总结:
- 对于每天开发时,都需要更新代码(git pull),而且每次更新完都需要刷新一下maven。
以上是关于开发规范详解的主要内容,如果未能解决你的问题,请参考以下文章