45个小技巧让你的代码写出来更美丽!

打印 上一主题 下一主题

主题 988|帖子 988|积分 2964

不知道各人有没有履历过维护一个已经去职的人的代码的痛苦,一个方法写老长,另有很多的if else ,根本无法阅读,更不知道代码背后的含义,最紧张的是没有人可以问,此时只能心里冷静地问候这个留坑的兄弟。。
实在造成这些原因的很大一部分原因是由于代码规范的题目,如果写的规范,表明好,实在很多题目也就办理了。以是本文我就从代码的编写规范,格式的优化,计划原则和一些常见的代码优化的技巧等方面总结了了45个小技巧分享给各人,如果不足,欢迎指正。




1、规范定名

定名是写代码中最频繁的操作,比如类、属性、方法、参数等。好的名字应当能遵循以下几点:
见名知意

比如需要界说一个变量需要来计数
  1. int i = 0; 复制代码
复制代码
名称 i 没有任何的现实意义,没有表现出数目的意思,以是我们应当指明数目的名称
  1. int count = 0; 复制代码
复制代码
可以或许读的出来

如下代码:
  1. private String sfzh; private String dhhm; 复制代码
复制代码
这些变量的名称,根本读不出来,更别说现实意义了。
以是我们可以使用正确的可以读出来的英文来定名
  1. private String idCardNo; private String phone; 复制代码
复制代码
2、规范代码格式

好的代码格式可以或许让人感觉看起来代码更加舒适。
好的代码格式应当服从以下几点:


  • 合适的空格
  • 代码对齐,比如大括号要对齐
  • 实时换行,一行不要写太多代码
好在现在开辟工具支持一键格式化,可以帮助美化代码格式。
3、写好代码表明

在《代码整齐之道》这本书中作者提到了一个观点,表明的适当用法是用来弥补我们在用代码表达意图时的失败。换句话说,当无法通过读代码来相识代码所表达的意思的时间,就需要用表明来说明。
作者之以是这么说,是由于作者觉得随着时间的推移,代码可能会变动,如果不实时更新表明,那么表明就轻易产生误导,偏离代码的现实意义。而不实时更新表明的原因是,步伐员不喜欢写表明。(作者很懂啊)
但是这不意味着可以不写表明,当通过代码如果无法表达意思的时间,就需要表明,比如如下代码
  1. for (Integer id : ids) { if (id == 0) { continue; } //做其他事 } 复制代码
复制代码
为什么 id == 0 需要跳过,代码是无法看出来了,就需要表明了。
好的表明应当满足一下几点:


  • 表明代码的意图,说明为什么这么写,用来做什么
  • 对参数和返回值表明,入参代表什么,出参代表什么
  • 有警示作用,比如说入参不能为空,大概代码是不是有坑
  • 当代码还未完成时可以使用 todo 表明来表明
4、try catch 内部代码抽成一个方法

try catch代码有时会干扰我们阅读核心的代码逻辑,这时就可以把try catch内部主逻辑抽离成一个单独的方法
如下图是Eureka服务端源码中服务下线的实现中的一段代码

​整个方法非常长,try中代码是真正的服务下线的代码实现,finally可以保证读锁最终肯定可以开释。

以是这段代码实在就可以对核心的逻辑举行抽取。
  1. protected boolean internalCancel(String appName, String id, boolean isReplication) { try { read.lock(); doInternalCancel(appName, id, isReplication); } finally { read.unlock(); } // 剩余代码 } private boolean doInternalCancel(String appName, String id, boolean isReplication) { //真正处理下线的逻辑 } 复制代码
复制代码
5、方法别太长

方法别太长就是字面的意思。一旦代码太长,给人的第一眼感觉就很复杂,让人不想读下去;同时方法太长的代码可能读起来轻易让人摸不着头脑,不知道哪一些代码是同一个业务的功能。
我曾经就遇到过一个方法写了2000+行,各种if else判断,我光理清代码思路就用了很久,最终理清之后,就用计谋模式给重构了。
以是一旦方法过长,可以实验将相同业务功能的代码单独抽取一个方法,末了在主方法中调用即可。
6、抽取重复代码

当一份代码重复出现在步伐的多处地方,就会造成步伐又臭又长,当这份代码的布局要修改时,每一处出现这份代码的地方都得修改,导致步伐的扩展性很差。
以是一般遇到这种情况,可以抽取成一个工具类,还可以抽成一个公共的父类。
7、多用return

在有时我们平时写代码的情况可能会出现if条件套if的情况,当if条件过多的时间可能会出现如下情况:
  1. if (条件1) { if (条件2) { if (条件3) { if (条件4) { if (条件5) { System.out.println("三友的java日记"); } } } } } 复制代码
复制代码
面临这种情况,可以换种思路,使用return来优化
  1. if (!条件1) { return; } if (!条件2) { return; } if (!条件3) { return; } if (!条件4) { return; } if (!条件5) { return; } System.out.println("三友的java日记"); 复制代码
复制代码
这样优化就感觉看起来更加直观
8、if条件表达式不要太复杂

比如在如下代码:
  1. if (((StringUtils.isBlank(person.getName()) || "三友的java日记".equals(person.getName())) && (person.getAge() != null && person.getAge() > 10)) && "汉".equals(person.getNational())) { // 处理逻辑 } 复制代码
复制代码
这段逻辑,这种条件表达式乍一看不知道是什么,仔细一看还是不知道是什么,这时就可以这么优化
  1. boolean sanyouOrBlank = StringUtils.isBlank(person.getName()) || "三友的java日记".equals(person.getName()); boolean ageGreaterThanTen = person.getAge() != null && person.getAge() > 10; boolean isHanNational = "汉".equals(person.getNational()); if (sanyouOrBlank && ageGreaterThanTen && isHanNational) { // 处理逻辑 } 复制代码
复制代码
此时就很轻易看懂if的逻辑了
9、优雅地参数校验

当前端传递给后端参数的时间,通常需要对参数进场查验,一般可能会这么写
  1. @PostMapping public void addPerson(@RequestBody AddPersonRequest addPersonRequest) { if (StringUtils.isBlank(addPersonRequest.getName())) { throw new BizException("人员姓名不能为空"); } if (StringUtils.isBlank(addPersonRequest.getIdCardNo())) { throw new BizException("身份证号不能为空"); } // 处理新增逻辑 } 复制代码
复制代码
这种写固然可以,但是当字段的多的时间,光校验就占据了很长的代码,不够优雅。
针对参数校验这个题目,有第三方库已经封装好了,比如hibernate-validator框架,只需要拿来用即可。
以是就在实体类上加@NotBlank、@NotNull注解来举行校验
  1. @Data @ToString private class AddPersonRequest { @NotBlank(message = "人员姓名不能为空") private String name; @NotBlank(message = "身份证号不能为空") private String idCardNo; //忽略 } 复制代码
复制代码
此时Controller接口就需要方法上就需要加上@Valid注解
  1. @PostMapping public void addPerson(@RequestBody @Valid AddPersonRequest addPersonRequest) { // 处理新增逻辑 } 复制代码
复制代码
10、同一返回值

后端在计划接口的时间,需要同一返回值
  1. { "code":0, "message":"成功", "data":"返回数据" } 复制代码
复制代码
不但是给前端参数,也包括提供给第三方的接口等,这样接口调用方法可以按照固定的格式解析代码,不消举行判断。如果不一样,相信我,前端半夜都肯定会来找你。
Spring中很多方法可以做到同一返回值,而不消每个方法都返回,比如基于AOP,大概可以自界说HandlerMethodReturnValueHandler来实现同一返回值。
11、同一异常处理

当你没有同一异常处理的时间,那么所有的接口制止不了try catch操作。
  1. @GetMapping("/{id}") public Result<T> selectPerson(@PathVariable("id") Long personId) { try { PersonVO vo = personService.selectById(personId); return Result.success(vo); } catch (Exception e) { //打印日志 return Result.error("系统异常"); } } 复制代码
复制代码
每个接口都得这么玩,那不得满屏的try catch。
以是可以基于Spring提供的同一异常处理机制来完成。
12、尽量不传递null值

这个很好理解,不传null值可以制止方法不支持为null入参时产生的空指针题目。
固然为了更好的表明该方法是不是可以传null值,可以通过@NonNull和@Nullable注解来标记。@NonNull就表现不能传null值,@Nullable就是可以传null值。
  1. //示例1 public void updatePerson(@Nullable Person person) { if (person == null) { return; } personService.updateById(person); } //示例2 public void updatePerson(@NonNull Person person) { personService.updateById(person); } 复制代码
复制代码
13、尽量不返回null值

尽量不返回null值是为了减少调用者对返回值的为null判断,如果无法制止返回null值,可以通过返回Optional来取代null值。
  1. public Optional<Person> getPersonById(Long personId) { return Optional.ofNullable(personService.selectById(personId)); } 复制代码
复制代码
如果不想这么写,也可以通过@NonNull和@Nullable表现方法会不会返回null值。
14、日志打印规范

好的日志打印能帮助我们快速定位题目
好的日志应该遵循以下几点:


  • 可搜索性,要有明确的关键字信息
  • 异常日志需要打印出堆栈信息
  • 合适的日志级别,比如异常使用error,正常使用info
  • 日志内容太大不打印,比如有时需要将图片转成Base64,那么这个Base64就可以不消打印
15、同一类库

在一个项目中,可能会由于引入的依赖不同导致引入了很多相似功能的类库,比如常见的json类库,又大概是一些常用的工具类,当遇到这种情况下,应当规范在项目中到底应该使用什么类库,而不是一会用Fastjson,一会使用Gson。
16、尽量使用工具类

比如在对集合判空的时间,可以这么写
  1. public void updatePersons(List<Person> persons) { if (persons != null && persons.size() > 0) { } }复制代码
复制代码
但是一般不推荐这么写,可以通过一些判断的工具类来写
  1. public void updatePersons(List<Person> persons) { if (!CollectionUtils.isEmpty(persons)) { } } 复制代码
复制代码
不但集合,比如字符串的判断等等,就使用工具类,不要手动判断。
17、尽量不要重复造轮子

就拿格式化日期来来说,我们一般封装成一个工具类来调用,比如如下代码
  1. private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDateTime(Date date) { return DATE_TIME_FORMAT.format(date); } 复制代码
复制代码
这段代码看似没啥题目,但是却忽略了SimpleDateFormat是个线程不安全的类,以是这就会引起坑。
一般对于这种已经有开源的项目并且已经做得很好的时间,比如Hutool,就可以把轮子直接拿过来用了。
18、类和方法单一职责

单一职责原则是计划模式的七大计划原则之一,它的核心意思就是字面的意思,一个类大概一个方法只做单一的功能。
就拿Nacos来说,在Nacos1.x的版本中,有这么一个接口HttpAgent


​这个类只干了一件事,那就是封装http哀求参数,向Nacos服务端发送哀求,接收响应,这实在就是单一职责原则的表现。

当其它的地方需要向Nacos服务端发送哀求时,只需要通过这个接口的实现,传入参数就可以发送哀求了,而不需要关心怎样携带服务端鉴权参数、http哀求参数怎样组装等题目。
19、尽量使用聚合/组合取代继续

继续的弊端:


  • 机动性低。java语言是单继续的,无法同时继续很多类,并且继续轻易导致代码层次太深,不易于维护
  • 耦合性高。一旦父类的代码修改,可能会影响到子类的活动
以是一般推荐使用聚合/组合取代继续。
聚合/组合的意思就是通过成员变量的方式来使用类。
比如说,OrderService需要使用UserService,可以注入一个UserService而非通过继续UserService。
聚合和组合的区别就是,组合是当对象一创建的时间,就直接给属性赋值,而聚合的方式可以通过set方式来设置。
组合:
  1. public class OrderService { private UserService userService = new UserService(); } 复制代码
复制代码
聚合:
  1. public class OrderService { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } } 复制代码
复制代码
20、使用计划模式优化代码

在平时开辟中,使用计划模式可以增加代码的扩展性。
比如说,当你需要做一个可以根据不同的平台做不同消息推送的功能时,就可以使用计谋模式的方式来优化。
计划一个接口:
  1. public interface MessageNotifier { /** * 是否支持改类型的通知的方式 * * @param type 0:短信 1:app * @return */ boolean support(int type); /** * 通知 * * @param user * @param content */ void notify(User user, String content); } 复制代码
复制代码
短信关照实现:
  1. @Component public class SMSMessageNotifier implements MessageNotifier { @Override public boolean support(int type) { return type == 0; } @Override public void notify(User user, String content) { //调用短信通知的api发送短信 } } 复制代码
复制代码
app关照实现:
  1. public class AppMessageNotifier implements MessageNotifier { @Override public boolean support(int type) { return type == 1; } @Override public void notify(User user, String content) { //调用通知app通知的api } } 复制代码
复制代码
末了提供一个方法,当需要举行消息关照时,调用notifyMessage,传入相应的参数就行。
  1. @Resource private List<MessageNotifier> messageNotifiers; public void notifyMessage(User user, String content, int notifyType) { for (MessageNotifier messageNotifier : messageNotifiers) { if (messageNotifier.support(notifyType)) { messageNotifier.notify(user, content); } } } 复制代码
复制代码
假设此时需要支持通过邮件关照,只需要有对应实现就行。
21、不滥用计划模式

用好计划模式可以增加代码的扩展性,但是滥用计划模式确是不可取的。
  1. public void printPerson(Person person) { StringBuilder sb = new StringBuilder(); if (StringUtils.isNotBlank(person.getName())) { sb.append("姓名:").append(person.getName()); } if (StringUtils.isNotBlank(person.getIdCardNo())) { sb.append("身份证号:").append(person.getIdCardNo()); } // 省略 System.out.println(sb.toString()); } 复制代码
复制代码
比如上面打印Person信息的代码,用if判断就可以或许做到效果,你说我要不消责任链大概什么计划模式来优化一下吧,没须要。
22、面向接口编程

在一些可替换的场景中,应该引用父类大概抽象,而非实现。
举个例子,在现实项目中可能需要对一些图片举行存储,但是存储的方式很多,比如可以选择阿里云的OSS,又大概是七牛云,存储服务器等等。以是对于存储图片这个功能来说,这些具体的实现是可以相互替换的。
以是在项目中,我们不应当在代码中耦合一个具体的实现,而是可以提供一个存储接口
  1. public interface FileStorage { String store(String fileName, byte[] bytes); } 复制代码
复制代码
如果选择了阿里云OSS作为存储服务器,那么就可以基于OSS实现一个FileStorage,在项目中那里需要存储的时间,只要实现注入这个接口就可以了。
  1. @Autowired private FileStorage fileStorage; 复制代码
复制代码
假设用了一段时间之后,发现阿里云的OSS比较贵,此时想换成七牛云的,那么此时只需要基于七牛云的接口实现FileStorage接口,然后注入到IOC,那么原有代码用到FileStorage根本不需要动,实现轻松的替换。
23、经常重构旧的代码

随着时间的推移,业务的增长,有的代码可能不再适用,大概有了更好的计划方式,那么可以实时的重构业务代码。
就拿上面的消息关照为例,在业务刚开始的时间可能只支持短信关照,于是在代码中就直接耦合了短信关照的代码。但是随着业务的增长,渐渐需要支持app、邮件之类的关照,那么此时就可以重构从前的代码,抽出一个计谋接口,举行代码优化。
24、null值判断

空指针是代码开辟中的一个难题,作为步伐员的基本修改,应该要防止空指针。
可能产生空指针的原因:


  • 数据返回对象为null
  • 主动拆箱导致空指针
  • rpc调用返回的对象可能为空格
以是在需要这些的时间,需要强制判断是否为null。前面也提到可以使用Optional来优雅地举行null值判断。
25、pojo类重写toString方法

pojo一般内部都有很多属性,重写toString方法可以方便在打印大概测试的时间查看内部的属性。
26、魔法值用常量表现

  1. public void sayHello(String province) { if ("广东省".equals(province)) { System.out.println("靓仔~~"); } else { System.out.println("帅哥~~"); } } 复制代码
复制代码
代码里,广东省就是一个魔法值,那么就可以将用一个常量来保存
  1. private static final String GUANG_DONG_PROVINCE = "广东省"; public void sayHello(String province) { if (GUANG_DONG_PROVINCE.equals(province)) { System.out.println("靓仔~~"); } else { System.out.println("帅哥~~"); } } 复制代码
复制代码
27、资源开释写到finally

比如在使用一个api类锁大概举行IO操作的时间,需要主动写代码需开释资源,为了可以或许保证资源可以或许被真正开释,那么就需要在finally中写代码保证资源开释。


​如图所示,就是CopyOnWriteArrayList的add方法的实现,最终是在finally中举行锁的开释。

28、使用线程池取代手动创建线程

使用线程池另有以下好处:


  • 低落资源消耗。通过重复使用已创建的线程低落线程创建和烧毁造成的消耗。
  • 进步响应速率。当使命到达时,使命可以不需要的等到线程创建就能立即执行。
  • 进步线程的可管理性。线程是稀缺资源,如果无穷制的创建,不但会消耗体系资源,还会低落体系 的稳固性,使用线程池可以举行同一的分配,调优和监控。
以是为了达到更好的使用资源,进步响应速率,就可以使用线程池的方式来取代手动创建线程。
如果对线程池不清楚的同学,可以看一下这篇文章: 7000字+24张图带你彻底弄懂线程池
29、线程设置名称

在日志打印的时间,日志是可以把线程的名字给打印出来。


​如上图,日志打印出来的就是tom猫的线程。

以是,设置线程的名称可以帮助我们更好的知道代码是通过哪个线程执行的,更轻易排查题目。
30、涉及线程间可见性加volatile

在RocketMQ源码中有这么一段代码


​在消耗者在从服务端拉取消息的时间,会单独开一个线程,执行while循环,只要stopped状态一直为false,那么就会一直循环下去,线程就一直会运行下去,拉取消息。

当消耗者客户端关闭的时间,就会将stopped状态设置为true,告诉拉取消息的线程需要停止了。但是由于并发编程中存在可见性的题目,以是固然客户端关闭线程将stopped状态设置为true,但是拉取消息的线程可能看不见,不能实时感知到数据的修改,还是认为stopped状态设置为false,那么就还会运行下去。
针对这种可见性的题目,java提供了一个volatile关键字来保证线程间的可见性。


​以是,源码中就加了volatile关键字。

加了volatile关键字之后,一旦客户端的线程将stopped状态设置为true时间,拉取消息的线程就能立马知道stopped已经是false了,那么再次执行while条件判断的时间,就不成立,线程就运行竣事了,然退却出。
31、思量线程安全题目

在平时开辟中,有时需要思量并发安全的题目。
举个例子来说,一般在调用第三方接口的时间,可能会有一个鉴权的机制,一般会携带一个哀求头token参数过去,而token也是调用第三方接口返回的,一般这种token都会有个逾期时间,比如24小时。
我们一般会将token缓存到Redis中,设置一个逾期时间。向第三方发送哀求时,会直接从缓存中查找,但是当从Redis中获取不到token的时间,我们都会重新哀求token接口,获取token,然后再设置到缓存中。
整个过程看起来是没什么题目,但是实则隐藏线程安全题目。
假设当出现并发的时间,同时来两个线程AB从缓存查找,发现没有,那么AB此时就会同时调用token获取接口。假设A先获取到token,B后获取到token,但是由于CPU调度题目,线程B固然后获取到token,但是先往Redis存数据,而线程A后存,覆盖了B哀求的token。
这下就会出现大题目,最新的token被覆盖了,那么之后肯定时间内token都是无效的,接口就哀求不通。
针对这种题目,可以使用double check机制来优化获取token的题目。
以是,在现实中,需要多思量思量业务是否有线程安全题目,有集合读写安全题目,那么就用线程安全的集合,业务有安全的题目,那么就可以通过加锁的手段来办理。
32、慎用异步

固然在使用多线程可以帮助我们进步接口的响应速率,但是也会带来很多题目。
事件题目

一旦使用了异步,就会导致两个线程不是同一个事件的,导致异常之后无法正常回滚数据。
cpu负载过高

之前有个小同伴遇到需要同时处理几万调数据的需求,每条数据都需要调用很多次接口,为了达到老板盼望的时间要求,使用了多线程跑,开了很多线程,此时会发现体系的cpu会飙升
意想不到的异常

还是上面的提到的例子,在测试的时间就发现,由于并发量激增,在哀求第三方接口的时间,返回了很多错误信息,导致有的数据没有处理乐成。
固然说慎用异步,但不代表不消,如果可以保证事件的题目,或是CPU负载不会高的话,那么还是可以使用的。
33、减小锁的范围

减小锁的范围就是给需要加锁的代码加锁,不需要加锁的代码不要加锁。这样就能减少加锁的时间,从而可以较少锁互斥的时间,进步效率。


​比如CopyOnWriteArrayList的addAll方法的实现,lock.lock(); 代码完全可以放到代码的第一行,但是作者并没有,由于前面判断的代码不会有线程安全的题目,不放到加锁代码中可以减少锁抢占和占据的时间。

34、有范例区分时界说好枚举

比如在项目中不同的范例的业务可能需要上传各种各样的附件,此时就可以界说好不同的一个附件的枚举,来区分不同业务的附件。
不要在代码中直接写死,不界说枚举,代码阅读起来非常困难,直接看到数字都是懵逼的。。
35、长途接口调用设置超时时间

比如在举行微服务之间举行rpc调用的时间,又大概在调用第三方提供的接口的时间,需要设置超时时间,防止由于各种原因,导致线程”卡死“在那。
我从前就遇到过线上就遇到过这种题目。当时的业务是订阅kafka的消息,然后向第三方上传数据。在某个周末,突然就接到电话,说数据无法上传了,通过排查线上的服务器才发现所有的线程都线程”卡死“了,末了定位到代码才发现原来是没有设置超时时间。
36、集合使用应当指明初始化大小

比如在写代码的时间,经常会用到List、Map来临时存储数据,其中最常用的就是ArrayList和HashMap。但是用欠好可能也会导致性能的题目。
比如说,在ArrayList中,底层是基于数组来存储的,数组是一旦确定大小是无法再改变容量的。但不断的往ArrayList中存储数据的时间,总有那么一刻会导致数组的容量满了,无法再存储其它元素,此时就需要对数组扩容。所谓的扩容就是新创建一个容量是原来1.5倍的数组,将原有的数据给拷贝到新的数组上,然后用新的数组替代原来的数组。
在扩容的过程中,由于涉及到数组的拷贝,就会导致性能消耗;同时HashMap也会由于扩容的题目,消耗性能。以是在使用这类集适时可以在构造的时间指定集合的容量大小。
37、尽量不要使用BeanUtils来拷贝属性

在开辟中经常需要对JavaBean举行转换,但是又不想一个一个手动set,比较贫苦,以是一般会使用属性拷贝的一些工具,比如说Spring提供的BeanUtils来拷贝。不得不说,使用BeanUtils来拷贝属性是真的舒服,使用一行代码可以取代几行甚至十几行代码,我也喜欢用。
但是喜欢归喜欢,但是会带来性能题目,由于底层是通过反射来的拷贝属性的,以是尽量不要用BeanUtils来拷贝属性。
比如你可以装个JavaBean转换的插件,帮你主动天生转换代码;又大概可以使用性能更高的MapStruct来举行JavaBean转换,MapStruct底层是通过调用(settter/getter)来实现的,而不是反射来快速执行。
38、使用StringBuilder举行字符串拼接

如下代码:
  1. String str1 = "123"; String str2 = "456"; String str3 = "789"; String str4 = str1 + str2 + str3; 复制代码
复制代码
使用 + 拼接字符串的时间,会创建一个StringBuilder,然后将要拼接的字符串追加到StringBuilder,再toString,这样如果多次拼接就会执行很多次的创建StringBuilder,z执行toString的操作。
以是可以手动通过StringBuilder拼接,这样只会创建一次StringBuilder,效率更高。
  1. StringBuilder sb = new StringBuilder(); String str = sb.append("123").append("456").append("789").toString(); 复制代码
复制代码
39、@Transactional应指定回滚的异常范例

平时在写代码的时间需要通过rollbackFor显示指定需要对什么异常回滚,原因在这:


​默认是只能回滚RuntimeException和Error异常,以是需要手动指定,比如指定成Expection等。

40、谨慎方法内部调用动态代理的方法

如下事件代码
  1. @Service public class PersonService { public void update(Person person) { // 处理 updatePerson(person); } @Transactional(rollbackFor = Exception.class) public void updatePerson(Person person) { // 处理 } } 复制代码
复制代码
update调用了加了@Transactional注解的updatePerson方法,那么此时updatePerson的事件就是失效。
实在失效的原因不是事件的锅,是由AOP机制决定的,由于事件是基于AOP实现的。AOP是基于对象的代理,当内部方法调用时,走的不是动态代理对象的方法,而是原有对象的方法调用,如此就走不到动态代理的代码,就会失效了。
如果实在需要让动态代理见效,可以注入自己的代理对象
  1. @Service public class PersonService { @Autowired private PersonService personService; public void update(Person person) { // 处理 personService.updatePerson(person); } @Transactional(rollbackFor = Exception.class) public void updatePerson(Person person) { // 处理 } } 复制代码
复制代码
41、需要什么字段select什么字段

查询全字段有以下几点弊端:
增加不须要的字段的网络传输

比如有些文本的字段,存储的数据非常长,但是本次业务使用不到,但是如果查了就会把这个数据返回给客户端,增加了网络传输的负担
会导致无法使用到覆盖索引

比如说,现在有身份证号和姓名做了联合索引,现在只需要根据身份证号查询姓名,如果直接select name 的话,那么在遍历索引的时间,发现要查询的字段在索引中已经存在,那么此时就会直接从索引中将name字段的数据查出来,返回,而不会继续去查找聚簇索引,减少回表的操作。
以是发起是需要使用什么字段查询什么字段。比如mp也支持在构建查询条件的时间,查询某个具体的字段。
  1. Wrappers.query().select("name"); 复制代码
复制代码
42、不循环调用数据库

不要在循环中访问数据库,这样会严重影响数据库性能。
比如需要查询一批人员的信息,人员的信息存在基本信息表和扩展表中,错误的代码如下:
  1. public List<PersonVO> selectPersons(List<Long> personIds) { List<PersonVO> persons = new ArrayList<>(personIds.size()); List<Person> personList = personMapper.selectByIds(personIds); for (Person person : personList) { PersonVO vo = new PersonVO(); PersonExt personExt = personExtMapper.selectById(person.getId()); // 组装数据 persons.add(vo); } return persons; } 复制代码
复制代码
遍历每个人员的基本信息,去数据库查找。
正确的方法应该先批量查出来,然后转成map:
  1. public List<PersonVO> selectPersons(List<Long> personIds) { List<PersonVO> persons = new ArrayList<>(personIds.size()); List<Person> personList = personMapper.selectByIds(personIds); //批量查询,转换成Map List<PersonExt> personExtList = personExtMapper.selectByIds(person.getId()); Map<String, PersonExt> personExtMap = personExtList.stream().collect(Collectors.toMap(PersonExt::getPersonId, Function.identity())); for (Person person : personList) { PersonVO vo = new PersonVO(); //直接从Map中查找 PersonExt personExt = personExtMap.get(person.getId()); // 组装数据 persons.add(vo); } return persons; } 复制代码
复制代码
43、用业务代码取代多表join

如上面代码所示,原来也可以将两张表根据人员的id举行关联查询。但是不推荐这么,阿里也禁止多表join的操作


​而之以是会禁用,是由于join的效率比较低。

MySQL是使用了嵌套循环的方式来实现关联查询的,也就是for循环会套for循环的意思。用第一张表做外循环,第二张表做内循环,外循环的每一条记录跟内循环中的记录作比较,符合条件的就输出,这种效率肯定低。
44、装上阿里代码查抄插件

我们平时写代码由于各种由于,比如什么领导啊,项目司理啊,会一直催进度,导致写代码都来不及思考,怎么快怎么来,cv大法上线,固然故意想写好代码,但是手确不听使唤。以是我发起装一个阿里的代码规范插件,如果有代码不规范,会有提醒,这样就可以知道哪些是可以优化的了。


如果你有强迫症,相信我,装了这款插件,你的代码会写的很美丽。

45、实时跟同事沟通

写代码的时间不能闭门造车,实时跟同事沟通,比如刚进入一个新的项目标,对项目工程不认识,一些技能方案不相识,如果上来就直接写代码,很有可能就会踩坑。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表