开发规范详解
- 命名规范
- 编码规范
- 注释规范
- 性能规范
- mysql数据库规范
- git分支规范
- Redis规范
- 阿里巴巴规范
- SpringBoot规范
- Maven规范
- 总结
命名规范
命名规范:
- 不能以下划线或美元符号开始,也不能以下划线或美元符号结束
- 反例: _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
- java包各模块命名规则:
①action,ui,controller为控制层:有时也会命名为“controller”即MVC中充当C角色,用来分配哪个业务来处理用户请求。
②biz,service业务层:存放好多处理业务的代码,现实中面向接口编程,一般这里定义都是业务接口,通常会有一个biz.impl这个包用来写实现类. 当然针对架构意义实现类不用说。
③dao持久层:数据库操作都写在这里。
④util功能包:针对本项目工具类。
⑤common通用工具包:一般一个公司会有固定的jar,好几个项目通用的,例如远程调用等。
⑥domian,model,po,vo存放实体:通常一些对应数据表的实体类放在此,如果用Mybatis框架,一般mapping创建在它下面,当然只针对架构mapping也是无意义。
⑦包名除了以上的类名对应啥就起啥,例如:filter,listener等等,因为不是所有模块都用到过滤器或者监听器。
代码和注释中都要避免使用任何语言的种族歧视性词语:
- 正例:日本人 / 印度人 / blockList / allowList / secondary
- 反例:RIBENGUIZI / Asan / blackList / whiteList / slave
编码规范
调用第三方接口:
- 在使用时要设置超时时长,建议接口超时时长在1-3秒之间
权限、角色、用户:
- 页面权限简单优化,同一个实例表三个权限:
①XX功能查阅(概括列表详情,导出),
②XX功能编辑(导入、新增、编辑,修改xxx,编辑xxx、启用禁用),
③XX功能删除。
- 权限命名规范:名词+动词
前后端的null与空串与不传:
前端:
①前端不传某个字段,后端不会调用对应字段的get/set方法,后端为空。
②前端传了某个字段,但只写参数名,不写参数值,后端调用对应字段的get/set方法,后端设置为空,因此也为空。
③前端传了某个字段,写了参数名,但参数值写为空串(“”),后端调用对应字段的get/set方法,后端设置为双引号字符串(“”“”)。
④前端传了某个字段,写了参数名和对应的参数值,后端调用对应字段的get/set方法,后端设置为对应的参数值。
数据库:
①后端不传某个字段,jdbc不会调用对应字段的get/set方法,数据Bean对应的参数默认就为空,mysql不处理该参数。
②后端传了某个字段,写了参数名,但参数值为空,jdbc调用对应字段的get/set方法,jdbc设置为空,因此数据Bean也为空,MySQL不处理该参数。
③后端传了某个字段,写了参数名,但参数值写为空串(“”),jdbc调用对应字段的get/set方法,jdbc设置为空串,数据Bean设置该参数为空串,mysql处理该参数为空串。
④后端传了某个字段,写了参数名和对应的参数值,jdbc调用对应字段的get/set方法,jdbc设置为对应的参数值,数据bean和mysql都设置相应的参数和参数值。
- 注意:
①mysql图形化客户端中的某个字段没有显示值就是该值为null。
- 总结:
①因此空串没有啥意义,传入时应该避免空串,因此判断时需要增加判断是否空串。此外对于集合的空集合和空串是一样的,在判断时需要增加判断是否空集合。
②其实开发时按照一个规范更好,但为什么不能完全实施呢,因为不能保证公司中其他开发人员是否严格按照开发规范来开发,因此需要防止。
Dao层和Service层使用:
除了service谁都不可以直接调用DAO,因为事务处理在Service,必须经过Service。
- Action,Util都不可以调用。DAO所有方法应该逻辑很简单。
A功能的DAO,B功能的service不能直接调用,要经过A功能的service,防止业务混乱。
java三元运算符优缺点:
- 三元元素的格式:【条件控制语句】 ? 【表达式1】 : 【表达式2】。
- 优点:一些简单的逻辑判断三元运算符可以简化代码,去除多余的 if-else 语句。
- 缺点:三元运算符使用时必须有返回值,没有返回值的表达式是不可以使用的。
- 注意点:
表达式一和表达式二的值类型不一致,会强制拆箱升级成范围更大的那个表达式的类型。
- 链接:java三元运算符缺点_《java基础》我踩过三元运算符的坑
java中变量的判空场景:
- 引用类型变量(包括String)一定要判空。(看情况)
①通过方法得到的引用变量,看方法内部处理,若方法不可能返回null,则不用判空,若方法会返回null,则需要在调用方法的地方进行判空。
②若调用方法得到的引用变量后,不使用引用变量调用方法(从而不报空指针),也可以不判空。
- 总结:
①判不判空不是死的,是活的。
②根据方法内部逻辑,以及调用方法处的逻辑综合判断。
判空时,不是单单判断null,在集合或字符串时可能还需判断集合的长度和是否空串。
静态变量、枚举的使用场景:
- 枚举的实现原理就是定义一个类,然后实例化几个由final修饰的这个类的对象,每个实例都带有自己的元信息。而常量相比之下,没有这一层封装,只占用最基本的内存,包括引用,和它的值本身,要简单轻巧很多。如果值可以使用基本类型而不是包装类型,那更不用说了。
- 不过话又说回来,通常情况下我们没必要在意这种区别。如果用枚举可读性、可扩展性更好,用就是了,枚举占那点内存,沧海一粟。在性能与代码维护性之间,除个别情况,优先选后者。高级编程语言的诞生本身就是硬件提升的背景下,牺牲某些性能来降低开发门槛,提高开发效率的,相对于微小的性能损耗,人力成本更值钱。
- 链接:静态变量、枚举、以及静态代码块的使用场景
- 当在和前台传过来的数据或者在逻辑操作的代码里面需要去用到这个常量值去做比较的时候,就是使用枚举类型的时候。一般形式是: 类名.枚举类型名.单个枚举类型。
- 就是在自己的代码里面,要是想使代码很规范,不被吊打,那么写出来的逻辑代码里面是不应该出现常量字符串和常量数字之类的东西。例如代码里面出现数字:
100,8,或者其他的数字,字符串如:只要是在逻辑代码里面带引号的。
这些代码,你写出来虽然在功能上是没有问题的,但是,这些都是隐藏的炸弹。好的代码,是不会出现这个问题的。这些东西都应该被定义成一个常量,然后再在其他地方使用。此外具有具体意义的要写成常量 。
- 怎么循环一个枚举类型。枚举有一个方法,values(),使用形式如: int length = XXXX.values().length 。
- 链接:java 枚举 循环遍历以及一些简单常见的使用
- 总结:
①循环使用到的地方最好使用枚举,因为枚举类有方法可以调用。
②1,"|"等等这些符号是用静态常量。
③其他随意。
JAVA当中变量什么时候需要初始化:
- 对于类的成员变量,不管程序有没有显式的进行初始化,Java虚拟机都会先自动给它初始化为默认值。
- 局部变量声明之后,Java虚拟机就不会自动给它初始化为默认值,因此局部变量的使用必须先经过显式的初始化。
但是需要声明的是:对于只负责接收一个表达式的值的局部变量可以不初始化,参与运算和直接输出等其它情况的局部变量需要初始化。
java的static静态变量与静态方法何时使用:
对于静态变量来说,多线程会使用它,对于变量来说,如果类是单例的,多线程也会使用它。
- 就是我们平常web开发中,很少会使用主动使用多线程,但是还有一种多线程情况就是每个用户都是一个线程,当访问量很大的时候是不是也就是多线程了呢,可是这种时候你使用非静态成员变量是没问题的,因为每个用户都是不同的实例,每个用户都是new的新的类对象,所以类中的变量也都是互相不干扰的,也就是线程安全的,但如果是静态变量,就是多用户共享,也就是多线程共享,所以多个用户同时修改数据时就会出现问题,难道我们平常开发中每次使用静态变量都要考虑线程安全问题吗。
- 这是线程安全的问题,建议了解一下线程安全机制。线程安全性比较关键的两个点:内存可见性和操作原子性。
- 如果你不修改值,可以使用private static final int ,final可以保证内存可见性语义。对于原生变量,final修饰后不可更改,从而也不存在操作原子性的问题。
- 如果你只是想单纯地进行赋值,而不进行复合操作,如i++,i=i+1之类的。那么可以使用volatile int。volatile可以确保内存可见性,但是无法确保原子性,所以不支持复合操作的线程安全性。
- 如果你想进行复合操作,可以使用AtomicInteger这个原子类,支持CAS操作,可确保内存可见性和操作原子性。
- 为什么会存在线程安全性问题?
①简要说一下,java中所有的线程都共享jvm进程的虚拟内存地址空间。一般的共享变量存放在这共享区域中。但是每个线程还有自己的本地缓存,在线程读取变量时,会在其本地拷贝一份副本进行操作,这样就会导致共享内存和本地副本存在不一致的情况。
②如果想深入理解,可以看一下cpu的缓存机制.
- 链接:java的static静态变量是不是不安全的?应该如何正确的使用他呢
- 在多线程中使用静态方法是否有线程安全问题?
①在多线程中使用同一个静态方法时,每个线程使用各自的实例字段(instance field)的副本,而共享一个静态字段(static field)。所以说,如果该静态方法不去操作一个静态成员,只在方法内部使用实例字段(instance field),不会引起安全性问题。但是,如果该静态方法操作了一个静态字段,则需要静态方法中采用互斥访问的方式进行安全处理。
- 何时使用静态?何时不使用静态?
①看这个方法或字段需不需要类共享。
使用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();
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开发工具产生如下提示,证明该段代码再其它地方也有出现,应将此代码块封装成公用方法。
- 建的方法若是在工具类中要有明确意义 。
- Service方法应该尽量写得通用点,明白点。因为可能会被别人调用。
equals等前使用已知的不为空的对象:
- 正例:“user”.equals(tips)
- 反例:tips.equals(“user”)
代码结构优化:
- 时刻保持代码的简洁,清晰,可阅读。
- 不能出现4层及4层以上的if判断语句,出现4层以内无法完成的判断要有结构说明。
即最多三层if判断语句。
可以考虑用短路法。(卫语句)
- 正例:
if (po == null || po.getStatus() == null) {
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())) {
}
}
}
}
把if-else代码重构成高质量代码实现的手段有:
减少嵌套、移除临时变量、条件取反判断、合并条件表达式等。
①链接:6个实例详解如何把if-else代码重构成高质量代码
废弃类和方法的要加上注解@Deprecated:
- 有问题,或有局限性、性能差的类和方法,但又不能当场删除的,以免报错,需要加上注解@Deprecated,对调用的同事有提示作用,同时标注优化后的代码。并且要在两个月迭代版本以内彻底删除。
- 正例:
@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 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);
}finally{
if(fileInputStream != null){
fileInputStream.close();
}
}
File file = new File("1.txt");
try (FileInputStream fileInputStream
= new FileInputStream(file)) {
}
File file = new File("1.txt");
FileInputStream fileInputStream = null;
fileInputStream = new FileInputStream(file);
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;
try {
inputStream.close();
}catch (IOException e){
}
try {
outputStream.close();
}catch (IOException e){
}
InputStream inputStream = null;
OutputStream outputStream = null;
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream.close();
outputStream.close();
}catch (IOException e){
}
- 尽量使用Buffered开头的缓冲流BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,原理是内存中提供一个字节数组来做缓冲,能提高读写效率和避免频繁读写物理硬件。
禁止提交的代码.iml文件:
注释规范
注释格式:
- 接口注释必须写清楚接口的用途、传参、返回数据、创建人、创建日期等信息
- 类注释要写明类的用途
- 在方法内的注释,首先要写明方法的主要功能,将功能分成123…步骤,并将这些步骤写在注释用,方便接盘侠更好的读懂代码,了解写代码人的思路。
注释中不能包含TODO XXX FIXME的代码,适用于前后端:
- IDEA自动生成的TODO XXX FIXME不可提交。
- 自己手动编写的TODO XXX FIXME ,必须描述清楚还差什么任务,完成或修复后必须删除。
①TODO 表示代办。
②FIXME 表示有bug。
- 正例:
代码中特殊的注释 //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获取对应于第一个队列内的数据,以此可以大大降低时间复杂度。
业务逻辑优化:
- 循环调用:一般都循环调用同一段代码,每次循环的逻辑一致,前后不关联。比如说:我们要初始化一个列表,预置12个月的数据给前端。这种显然每个月的数据计算相互都是独立的,我们完全可以采用多线程方式进行。
- 顺序调用:如果不是类似上面循环调用,而是一次次的顺序调用,而且调用之间没有结果上的依赖,那么也可以用多线程的方式进行。
锁设计不合理:
- 锁设计不合理一般有两种:锁类型使用不合理 or 锁过粗。
- 锁类型使用不合理的典型场景就是读写锁。也就是说,读是可以共享的,但是读的时候不能对共享变量写;而在写的时候,读写都不能进行。在可以加读写锁的时候,如果我们加成了互斥锁,那么在读远远多于写的场景下,效率会极大降低。
- 锁过粗则是另一种常见的锁设计不合理的情况,如果我们把锁包裹的范围过大,则加锁时间会过长。
优化思路总结:
- 缓存与索引。(主键索引和缓存性能差不多)
- 多线程,异步。
- 接口中涉及的bean的数据量,业务逻辑(java单纯逻辑的优化无意义,看具体的数据量和是否涉及io)。
- 拆接口,加限流。
- 链接:
①性能优化之接口优化(spring/java/http接口)
②性能优化案例之:如何将TPS从60提升到2000?
③接口响应时组装响应json_接口响应时间长的调优经验
④接口性能优化技巧,干掉慢代码!
⑤RT 过长,排查思路
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提交到对应的文件里(看表)。
单表查询or联表查询:
- 在实际开发中,我们不可避免的要关联几张数据表来合成最终的展示数据。 常见的做法一般有两种:
①联表查询
②单表查询+业务层组装
- 对比:
表面上看联表查询这种方式只需要查一次数据库就能把需要的值查出来,而单表查询需要更多的查询数据库次数。
但实际上:
①单表查询更利于后续的维护:
在实际开发场景中,在代码初步开发阶段,业务发生变动,某张表的结构发生变动,很可能整个join查询都变得不可用,复杂的关联查询,在修改时,基本等于推倒重来。但是如果我们使用了单表查询,拆成上诉例子中的三个步骤,我们可能只需要修改其中的一个步骤即可,比较利于维护。
②代码可复用性高:
join联表的SQL,基本不太可能被复用,但是拆分后的单表查询,比如上面例子中,我查询出用户数据,任何地方组装需要用户数据,我都不需要再次做相关查询,直接使用。
③效率问题:
join联表查询,小表驱动大表,通过索引字段进行关联。如果表记录比较少的话,效率还是OK的,有时效率超过单表查询。但是如果数据量上去,多表查询是笛卡尔乘积方式,需要检索的数据是几何倍上升的。另外多表查询索引设计上也考验开发者的功底,索引设计不合理,大数据量下的多表查询,很可能把数据库拖垮。相比而言,拆分成单表查询+代码上组装,业务逻辑更清晰,优化更方便,单个表的索引设计上也更简单。用多几行代码,多几次数据库查询换取这些优点,还是很值得的。
④减少冗余字段的查询
:在很多业务中,我们可能对某条记录只需要查询一次,此时如何使用关联查询,则不可避免的需要重复地访问一部分数据,从而可能会加剧网络和内存的消耗。
⑤其他:在Alibaba开发手册中写道:
- 总结:
数据库资源比较宝贵,很多系统的瓶颈就在数据库上,很多复杂的逻辑我们在Service做,不在数据库处理会更好。
总结:
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:撤销,版本回退
- refactor:重构
①扩展:
<1>重构是对软件内部结构的一种调整,目的是在不改变软件行为的前提下,提高其可理解性,降低其修改成本。开发人员可以使用一系列重构准则,在不改变软件行为的前提下,调整软件的结构。
<2>性能的优化不宜过早,因为很多性能优化其实没有对系统有明显的提升。
<3>而重构主要指的是修正代码中不好的味道,提高代码的可读性和可扩展性
- 若同时提交多个操作,只需标识以最高级操作进行备注
代码研发:
研发时:
① 从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
Redis规范
Redis基本使用:
- redis作为缓存的作用就是减少对数据库的访问压力,当我们访问一个数据的时候,首先我们从redis中查看是否有该数据,如果没有,则从数据库中读取,将从数据库中读取的数据存放到缓存中,下次再访问同样的数据的是,还是先判断redis中是否存在该数据,如果有,则从缓存中读取,不访问数据库了。
- 当我们后台数据库中内容修改之后,因为缓存中的内容没有修改,我们访问的时候都是先访问缓存,所以即使数据库中的内容修改了,但是页面的显示还是不会改变的。因为缓存没有更新,所以这就涉及到缓存同步的问题:即数据库修改了内容与缓存中对应的内容同步。
- 缓存同步的原理:就是将redis中的key进行删除,下次访问的时候,redis中没有改数据,则从DB进行查询,再次更新到redis中。
- 缓存同步除了查询是没有涉及到同步问题,增加删除修改都会涉及到同步问题。
哪些数据可以放进缓存?
- 字典表、配置类的数据:
①这一类的数据是最适合放在缓存中的,因为更新频率特别低,甚至有时候 insert 了之后就再也不做 update ,如果这类数据的调用量比较大,是一定要放到 Redis 中的;
- 有一类数据,很明显就是热点数据;
- 其余数据的评估:
①查询和处理逻辑比较复杂。
②基础数据量太大,无法把所有数据都放入 Redis 中。
③无法把基础数据直接放入 Redis 中,因为有多重查询维度(条件)。
④加的缓存是否共用的(即用的地方多)
- 链接:哪些数据可以放进缓存?
阿里巴巴规范
简介:
- 阿里巴巴java开发规范:阿里巴巴java开发规范
常见容错机制:
- Failover 失败自动切换:当出现失败,重试其它服务器,通常用于读操作(推荐使用)。 重试会带来更长延迟。
- Failfast 快速失败:只发起一次调用,失败立即报错,通常用于非幂等性的写操作。 如果有机器正在重启,可能会出现调用失败 。
- Failsafe 失败安全:出现异常时,直接忽略,通常用于写入审计日志等操作。 调用信息丢失 可用于生产环境 Monitor。
- Failback 失败自动恢复:后台记录失败请求,定时重发。通常用于消息通知操作 不可靠,重启丢失。 可用于生产环境 Registry。
- Forking 并行调用多个服务器:只要一个成功即返回,通常用于实时性要求较高的读操作。 需要浪费更多服务资源 。
- Broadcast:广播调用,所有提供逐个调用,任意一台报错则报错。通常用于更新提供方本地状态 速度慢,任意一台报错则报错 。
SpringBoot规范
Java中Controller层和Service层具体怎么区分?
- 涉及请求、参数、序列化之类的逻辑可以放在controller。而具体到数据的操作逻辑,也就是增删改查的操作都应该完整封装到service中。
- 一方面是对于事务的要求,比如一个请求过来,后台需要多步增删改查,一般都要求放到一个service方法中也就是同一段事务里。另一方面也是为了提供统一的接口,有的业务除了本地的controller还会需要服务间通信,比如restapi或者rpc什么的,最后他们都应该从同一个service拿到一致的基础数据,然后由各自的controller按各自的需求加工后返回。
Java中Controller什么时候需要将Service方法写在同一个事务中?
- 调用Redis,MongoDB,ES,MQ,第三方系统API以及Mysql中不同库时不需要同一个事务。
- 其他时候在Mysql同一个数据库时需要同一个事务。
具体情况看具体业务要求实现。
Maven规范
Maven多模块如何划分:
- 按业务划分:
适合大型复杂应用体系
。
①基础应用:认证应用,授权应用。
②业务应用:下单应用,新闻应用,会议应用。
- 按架构划分:
适合单个应用系统
。
①一个model包:实体类。
②一个core包:service层和dao层。
③一个web前台(client暴露接口,给予微服务调用)(admin后台)(gateway 网关):controller层。
④一个global全局(common公共)(basic基础)(util工具):全局,公共,基础的东西。
⑤一个服务包(cache缓存)(search ES搜索包)(picture 上传图片视频工具)(sms 短信发送服务)(mq 消息服务):第三方调用服务。
总结
每日开发总结:
对于每天开发时都需要更新代码(git pull),而且每次更新完都需要刷新一下maven。
对于每个新项目时都需要确保IDEA是否配置好,例如:maven路径,文件编码等等。
对于每个需求理解到位后,写好解决方案(包括数据库设计,sql编写,接口api以及以及影响的地方的改造)。
对于每个缺陷理解到位后,在前端复现一遍然后由接口一步一步往里分析。
处理数据时要思考清楚前后的处理维度,处理业务时要思考清楚业务的特性。
- 入手项目时对于配置文件:
①spring和springMVC和jpa和mybatis等配置文件放在Resource或classes中。
②对于扫描都是规定好XXX/XXX/*
这种,只要把对应的文件放在对应的组织.公司.模块名中即可。
- 人力成本,优化,代码维护性,安全,开发时四者取其二或其三即可。
编程数理逻辑:
- 编程需求的逻辑要求:
①把各种情况、会出现的问题都想全了。
②要你能抽丝剥茧,然后做得井井有条。
- 集关系: