# 代码优化技巧

## 1.controller层只负责流程，实现放入service

## 2.方法别太长

单一性原则保证了一个功能一个方法，那么一个方法不能超过500行，超过之后肯定可以再进行拆分

## 3.优雅地参数校验

针对参数校验这个问题，有第三方库已经封装好了，比如 hibernate-validator 框架，只需要拿来用即可。

```java
@Data
@ToString
private class AddPersonRequest {

    @NotBlank(message = "人员姓名不能为空")
    private String name;
    @NotBlank(message = "身份证号不能为空")
    private String idCardNo;

    //忽略
}
```

此时 Controller 接口就需要方法上就需要加上 `@Valid` 注解

```
@PostMapping
public void addPerson(@RequestBody @Valid AddPersonRequest addPersonRequest) {
    // 处理新增逻辑
}
```

## 4.统一返回值

后端在设计接口的时候，需要统一返回值

```
{
    "code":0,
    "message":"成功",
    "data":"返回数据"
}
```

不仅是给前端参数，也包括提供给第三方的接口等，这样接口调用方法可以按照固定的格式解析代码，不用进行判断。如果不一样，相信我，前端半夜都一定会来找你。

Spring 中很多方法可以做到统一返回值，而不用每个方法都返回，比如基于 AOP，或者可以自定义 HandlerMethodReturnValueHandler 来实现统一返回值。

## 5.统一异常处理

当你没有统一异常处理的时候，那么所有的接口避免不了 try catch 操作。

```
@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
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 值。

```
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、尽量使用工具类**

比如在对集合判空的时候，可以这么写

```
public void updatePersons(List<Person> persons) {
    if (persons != null && persons.size() > 0) {

    }
}
```

但是一般不推荐这么写，可以通过一些判断的工具类来写

```
public void updatePersons(List<Person> persons) {
    if (!CollectionUtils.isEmpty(persons)) {

    }
}
```

不仅集合，比如字符串的判断等等，就使用工具类，不要手动判断。

## **17、尽量不要重复造轮子**

就拿格式化日期来来说，我们一般封装成一个工具类来调用，比如如下代码

```
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

![图片](https://mmbiz.qpic.cn/mmbiz_png/iaIdQfEric9TwZnibN07UrZSdx5VroUSGZJH2cYKWNukB1BhdR0ZPib6MHX6vNkiaofXFwWKu03Fnyxdkib6YQeG0gmw/640?wx_fmt=png\&wxfrom=5\&wx_lazy=1\&wx_co=1)

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

当其它的地方需要向 Nacos 服务端发送请求时，只需要通过这个接口的实现，传入参数就可以发送请求了，而不需要关心如何携带服务端鉴权参数、http 请求参数如何组装等问题。

## **19、尽量使用聚合/组合代替继承**

继承的弊端：

* 灵活性低。java 语言是单继承的，无法同时继承很多类，并且继承容易导致代码层次太深，不易于维护
* 耦合性高。一旦父类的代码修改，可能会影响到子类的行为

所以一般推荐使用聚合/组合代替继承。

聚合/组合的意思就是通过成员变量的方式来使用类。

比如说，OrderService 需要使用 UserService，可以注入一个 UserService 而非通过继承 UserService。

聚合和组合的区别就是，组合是当对象一创建的时候，就直接给属性赋值，而聚合的方式可以通过 set 方式来设置。

组合：

```
public class OrderService {

    private UserService userService = new UserService();

}
```

聚合：

```
public class OrderService {

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}
```

## **20、使用设计模式优化代码**

在平时开发中，使用设计模式可以增加代码的扩展性。

比如说，当你需要做一个可以根据不同的平台做不同消息推送的功能时，就可以使用策略模式的方式来优化。

设计一个接口：

```
public interface MessageNotifier {

    /**
     * 是否支持改类型的通知的方式
     *
     * @param type 0:短信 1:app
     * @return
     */
    boolean support(int type);

    /**
     * 通知
     *
     * @param user
     * @param content
     */
    void notify(User user, String content);

}
```

短信通知实现：

```
@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 通知实现：

```
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，传入相应的参数就行。

```
@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、不滥用设计模式**

用好设计模式可以增加代码的扩展性，但是滥用设计模式确是不可取的。

```
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，又或者是七牛云，存储服务器等等。所以对于存储图片这个功能来说，这些具体的实现是可以相互替换的。

所以在项目中，我们不应当在代码中耦合一个具体的实现，而是可以提供一个存储接口

```
public interface FileStorage {

    String store(String fileName, byte[] bytes);

}
```

如果选择了阿里云 OSS 作为存储服务器，那么就可以基于 OSS 实现一个 FileStorage，在项目中哪里需要存储的时候，只要实现注入这个接口就可以了。

```
@Autowired
private FileStorage fileStorage;
```

假设用了一段时间之后，发现阿里云的 OSS 比较贵，此时想换成七牛云的，那么此时只需要基于七牛云的接口实现 FileStorage 接口，然后注入到 IOC，那么原有代码用到 FileStorage 根本不需要动，实现轻松的替换。

## **23、经常重构旧的代码**

随着时间的推移，业务的增长，有的代码可能不再适用，或者有了更好的设计方式，那么可以及时的重构业务代码。

就拿上面的消息通知为例，在业务刚开始的时候可能只支持短信通知，于是在代码中就直接耦合了短信通知的代码。但是随着业务的增长，逐渐需要支持 app、邮件之类的通知，那么此时就可以重构以前的代码，抽出一个策略接口，进行代码优化。

## **24、null 值判断**

空指针是代码开发中的一个难题，作为程序员的基本修改，应该要防止空指针。

可能产生空指针的原因：

* 数据返回对象为 null
* 自动拆箱导致空指针
* rpc 调用返回的对象可能为空格

所以在需要这些的时候，需要强制判断是否为 null。前面也提到可以使用 Optional 来优雅地进行 null 值判断。

## **25、pojo 类重写 toString 方法**

pojo 一般内部都有很多属性，重写 toString 方法可以方便在打印或者测试的时候查看内部的属性。

## **26、魔法值用常量表示**

```
public void sayHello(String province) {
    if ("广东省".equals(province)) {
        System.out.println("靓仔~~");
    } else {
        System.out.println("帅哥~~");
    }
}
```

代码里，广东省就是一个魔法值，那么就可以将用一个常量来保存

```
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 中写代码保证资源释放。

![图片](https://mmbiz.qpic.cn/mmbiz_png/iaIdQfEric9TwZnibN07UrZSdx5VroUSGZJiaOGWSjrL6Ec79kiaEybcQW18AKicTich5C1LOwnGqLyFx6DWnRSThSTmw/640?wx_fmt=png\&wxfrom=5\&wx_lazy=1\&wx_co=1)

如图所示，就是 CopyOnWriteArrayList 的 add 方法的实现，最终是在 finally 中进行锁的释放。

## **28、使用线程池代替手动创建线程**

使用线程池还有以下好处：

* 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
* 提高响应速度。当任务到达时，任务可以不需要的等到线程创建就能立即执行。
* 提高线程的可管理性。线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统 的稳定性，使用线程池可以进行统一的分配，调优和监控。

所以为了达到更好的利用资源，提高响应速度，就可以使用线程池的方式来代替手动创建线程。

如果对线程池不清楚的同学，可以看一下这篇文章: [7000 字+24 张图带你彻底弄懂线程池](https://mp.weixin.qq.com/s?__biz=Mzg5MDczNDI0Nw==\&mid=2247491081\&idx=1\&sn=4dddb33a76a4ee1f4c52b24059fb2eb8\&scene=21#wechat_redirect)

## **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 进行字符串拼接**

如下代码：

```
String str1 = "123";
String str2 = "456";
String str3 = "789";
String str4 = str1 + str2 + str3;
```

使用 + 拼接字符串的时候，会创建一个 StringBuilder，然后将要拼接的字符串追加到 StringBuilder，再 toString，这样如果多次拼接就会执行很多次的创建 StringBuilder，z 执行 toString 的操作。

所以可以手动通过 StringBuilder 拼接，这样只会创建一次 StringBuilder，效率更高。

```
StringBuilder sb = new StringBuilder();
String str = sb.append("123").append("456").append("789").toString();
```

## **39、@Transactional 应指定回滚的异常类型**

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

![图片](https://mmbiz.qpic.cn/mmbiz_png/iaIdQfEric9TwZnibN07UrZSdx5VroUSGZJYS5xpUJ7dUXmNzjD4qNLIOPM0vsibWxLnHI2ZpVkAflsEaoLf9LtiaYw/640?wx_fmt=png\&wxfrom=5\&wx_lazy=1\&wx_co=1)

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

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

如下事务代码

```
@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 是基于对象的代理，当内部方法调用时，走的不是动态代理对象的方法，而是原有对象的方法调用，如此就走不到动态代理的代码，就会失效了。

如果实在需要让动态代理生效，可以注入自己的代理对象

```
@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 也支持在构建查询条件的时候，查询某个具体的字段。

```
 Wrappers.query().select("name");
```

## **42、不循环调用数据库**

不要在循环中访问数据库，这样会严重影响数据库性能。

比如需要查询一批人员的信息，人员的信息存在基本信息表和扩展表中，错误的代码如下：

```
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：

```
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 的操作

![图片](https://mmbiz.qpic.cn/mmbiz_png/iaIdQfEric9TwZnibN07UrZSdx5VroUSGZJzAyHCWMu6EkIdGcMY7Zs8LJAibE5HLRd37X1k9O9ia4rAqjnHKxwZpVA/640?wx_fmt=png\&wxfrom=5\&wx_lazy=1\&wx_co=1)

而之所以会禁用，是因为 join 的效率比较低。

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

## **44、装上阿里代码检查插件**

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

![图片](https://mmbiz.qpic.cn/mmbiz_png/iaIdQfEric9TwZnibN07UrZSdx5VroUSGZJpibibUwpM137jeYbyDk5NFEq0FTZQWZiaibbYEP3y5GzBrmcvWGQ7PRu8w/640?wx_fmt=png\&wxfrom=5\&wx_lazy=1\&wx_co=1)

如果你有强迫症，相信我，装了这款插件，你的代码会写的很漂亮。

## **45、及时跟同事沟通**

写代码的时候不能闭门造车，及时跟同事沟通，比如刚进入一个新的项目的，对项目工程不熟悉，一些技术方案不了解，如果上来就直接写代码，很有可能就会踩坑。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://qiangrens-organization.gitbook.io/qkd90/kai-fa-kuang-jia-he-gong-ju/dai-ma-you-hua-ji-qiao.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
