工作三年后, 我作为Java后端开发的一些心得

27,305 阅读12分钟

今年进入我工作的第四个年头, 三年多的时间里, 算上实习, 经历过3家公司. 见过了各种各样的同事, 也算见过各式各样的代码. 这篇文章主要是想分享一下一个java后端三年的一些感悟, 关于编程上的, 工作上的, 和同事相处上的.

一: 关于开发

我把关于编程的写在最前面, 我觉得对于开发人员来讲, 编程能力才是混饭的手艺, 它也一定程度上也决定了你的钱包和获得工作的筹码.

1. 敢于和善于使用package

对于Java后端开发来讲, 在长时间的web开发中. 大家已经熟悉了MVC架构, 也被这套结构所束缚. 导致创建出来的包也一直都是controller, manager, service, dao. 也将各种各样的类文件都放入其中. 这并不是一种好的做法.

其实我们可以大胆的创建相关的package, 只要让结构更合理, 可读性更高.

比如可以把对接前端的类写到request, response包; 把一些处理器提取出来放到handler中, 把一些定时任务放到schedule包; 把参数构造相关的放到generator; 把校验相关的放到validator下等等. 

2. 合理的提取业务逻辑, 让方法只做和它相关的事情.

不知道大家是否看到过下面这种代码.  例如在一个单据的创建方法中, 做一系列的事情, 比如一坨校验参数逻辑, 一堆取价逻辑, 一堆扣减库存逻辑, 再加上创建单据本身的逻辑. 写下来一个方法上百行不止.

public class OrderManager {

  @Autowired 
  private OrderService orderService;

  public String createOrder(Request request){
    // 1. 检查单据创建参数是否合法
    if(Objects.isNull(request)) {
      throw ....;
    }
    if(CollectionUtils.isEmpty(request.getGoodsList())){
      throw ...;
    }
    ... 

    // 获取商品的价格
    List<Goods> goodsList = request.getGoods();
    goodsList.forEach(goods -> {
      // 查询商品价格

      // 校验价格
      
      // 对价格进行填充
      
    });
     
    // 构造单据逻辑
    
    // 创建单据

    return orderCode;
  }
} 

这个方法里做的事, 没有多余的事情, 但是没有合理的进行业务逻辑的提取. 导致代码看起来非常的杂乱.我们可以对一些业务逻辑做提取封装. 来的到更好的可读性和解耦. 这只是一个简单的例子, 在复杂的业务逻辑里更需要合理提取, 否则屎山就出现了.

public class OrderManager {

  @Autowired 
  private OrderService orderService;

  @Autowired
  private OrderValidator orderValidator;

  @Autowired
  private OrderGenerator orderGenerator;
  
  @Autowired
  private PriceService priceService;

  @Autowired
  private InventoryService inventoryService;

  public String createOrder(Request request){
    // 1. 检查单据创建参数是否合法
    this.orderValidator(request);

    // 获取商品的价格
    this.priceService(request.getGoods());
     
    // 构造单据逻辑
    Order order = this.orderGenerator.generator(request);
    
    // 创建单据
    this.orderService.create(order);
    return order.getOrderCode();
  }
} 

3. 合理的方法命名和方法定义

方法名的定义很令人苦恼, 常常思前想后想不到好名字. 我曾经因为方法命名不好, 被疯狂的comments

方法命名的好坏受个人主观影响, 所以只说几个共同点:

  1. 言简意赅 准确表达方法内容
  2. 方法名与方法内容匹配
  3. 尽量别生僻单词...

对于方法的参数, 参数过多的时候, 对方法进行拆解或者抽象出对象去传参:

// 错误的案例
public int setParam(int xxx, String xxx, String xxx, String xxx, String xxx, int xxx, String xxx, String xxx){
  ... 
}

// 合理的写法
public int setParam(XxxRequest request) {

}

4. 控制方法的圈复杂度, 让代码更有层次感

我一直觉得, 好的代码读起来应该像故事一样, 有前因, 有后果, 中间娓娓道来.  简单举个例子, 我们可能会遇到在方法中去for循环处理数据的情况. 比如在一个方法中, 套了三层循环.

List<String> orderCodeList = request.getOrderCodes();

// 第一层循环
for(String orderCode : orderCodeList){

  // 查询单号对应的单据明细
  List<OrderItem> items = this.orderItemService.getByCode(orderCode);
  // 第二层循环
  for(OrderItem item : items) {
    // 执行操作1
    // 执行操作2
    // 执行操作3
    
    // 第三层循环
    for()
  }
}

可以把每一层for循环都提取出来, 成为单独的一个方法, 来降低圈复杂度, 提高可读性

List<String> orderCodeList = request.getOrderCodes();

// 第一层循环
for(String orderCode : orderCodeList){
  this.processSingleOrder(orderCode);
}

public void processSingleOrder(String orderCode){
  // 查询单号对应的单据明细
  List<OrderItem> items = this.orderItemService.getByCode(orderCode);
  this.processItemsData(items);
}

public void processItemsData(){
  for(OrderItem item : items) {
    // 执行操作1
    // 执行操作2
    // 执行操作3
  }
}

5. 不知道的知识可以去问问Google, 不要自己编

不知道标题是否贴切, 但是大家看了例子就会明白我的意思.

其中最为典型的例子我认为 是对Obejct和集合的判空 和 创建集合

// 对于判断, 很多人喜欢这样写
if( list == null || list.size == 0) 
或者 
if(order == null)

// 对于创建集合, 去创建空集合, 再添加
List<String> list = new ArrList();
list.add("xxx");
list.add("bbb");

其实只要我们Google以下, Java下如何对集合判空, 就能看到apache.commons 或者google.common等很多类库已经包含这些内容, 并且实现的更严谨, 更优美. 要请善于使用搜索引擎去填补自己不了解的知识.

CollectionUtils.isEmpty(list);

List<String> list = Lists.newArrayList("xxx", "bbb");

6. 不要for循环请求数据库和外部系统接口

慢请求的分析, 可能不需要要先去看有没有复杂的关联查询, 或者是不是数据库查询有没有命中索引, 而是先去看是不是有大哥在for循环去请求数据库和dubbo接口. 循环了1w次. 我曾经遇到过多次请求超时都是因为有人在代码里for循环去select , update. 

对于这种问题的解决, 将循环调用改成单次的批量接口就可以解决问题. 对于mysql的化in操作就可以解决, 对于外部系统对接的, 双方提供批量接口就可以了.

7. 没有意义的注释不要写

我很反感在类上要先写上 @Author @Date @Description 一大串内容表明这是你的杰作, 这不是JDK 也不是什么开源项目!!! 除了你和你的同事没人去看.

再或者像下面这样在方法上直白翻译了一堆废话, 注释不是这么用的...

/**
 * 查询用户名称
 *
 * @Param name:用户名
 * @Return 用户
 */
public User getByName(String name);

8. 不要忽视UnitTest

写过单元测试的会发现, 编写完善的单元测试会占用大量的时间, 一般都会超过需求的开发时间, 但是我还是认为单元测试是必须且重要的. 因为作为研发人员, 才是最了解代码中哪里容易出问题的. 更容易写出发现问题的测试用例. 并且代码迭代或者修改后, 也能更快速的发现问题, 将问题停留在研发阶段去解决, 提高整体的进度.

9.善于使用AI编程工具

在我使用了Github copilot和ChatGPT半年后, 我发现我的摸鱼时间变多了... 因为AI编程工具帮我完成了一定量的工作. 例如最常用的代码补全, 代码自动生成, 自动生成单元测试等等

在当今, 熟练掌握AI编程工具, 是提高自己工作效率的极佳的方法. 在未来, AI也一定会代替掉一部分程序员的工作. 1b48670f07e84e188917094af9f05120~tplv-k3u1fbpfcp-watermark.png

10. 利用IDE的工具来完成代码和优化代码

  1. IDEA自带功能扫描代码无用引用, 重复代码等坏味道 : Code → Inspect Code
  2. SonarLint
  3. MyBatis Plus
  4. Lombok
  5. Alibaba Java Coding Guidelines
  6. CheckStyle-IDEA
  7. ...

11. 拥抱新技术

  可能随着工作的时间变长, 大家对新鲜技术的兴趣并不像之前感兴趣. 或者认为目前的技术足够, 远不会过时, 即使过时了, 也会有公司使用.

  技术是不断迭代更新的, 使用技术的人也要随之更新. 当大家都去开始了解和使用云服务, 容器化, 使用JDK 17的新特性, 开始用云原生框架去替换现有技术时, 咱总不能一直玩转jdk 1.8吧.

  了解一些新技术并不是什么值得炫耀的, 不知道也不一定影响你工作和赚钱, 但是当互联网红利已经逐渐褪去, 内卷在越来越重的今天, 机会也变得弥足珍贵. 更好的知识储备, 也能让你能获得下一份工作, 在人才市场获得更多青睐.

  我也一直认为, 开发对很多人来说不光是工作, 也有着一份热爱.

二. 关于处理工作和人际关系

1. 开发并不只是开发

  这个标题就是字面意思, 指的并不是光顾忌自己的开发任务. 同时也要关注公司的运营和公司业务或者说自己负责的项目的业务.

  我见过一些程序员只是单纯的根据产品的文档写需求, 你需求怎么写, 我功能就怎么写. 但是研发在看待需求时, 应该持有自己的见解, 观点和建议. 这也就是需求评审的目的.

  不要觉得需求是产品提的, 和研发没有任何关系. 但是你需要考虑到, 当需求存在问题,  后续的需求优化, bug修复, 甚至数据处理, 可都是要由研发来做的. 简单来说, 错在产品, 但引发问题由你处理.

  而公司的运营, 关系到了你在公司的生存和发展, 所以关注着公司的运营情况, 也大概你知道你明年的涨薪是否有希望, 年终奖是否能按时发放, 以及你是否应该考虑换一个公司去继续搬砖.

2. 合理的分配和安排自己的工作

  拿到需求不要急于开发, 不要急于开发, 不要急于开发.

  我见过一些开发, 在拿到需求后会马不停蹄的开始Coding,  然后就出现边写边改, 再写又发现哪里存在问题, 最后发现写不通,  推翻了之前的结构再写. 

  这个可能并不适合所有人, 但是我认为在开始Coding之前, 是需要构思一下再着手的. 花一些时间分析一下这个需求, 考虑下设计到的各个部分, 构思下自己的开发思路, 设想下其中可能遇到的问题, 当思路清晰后, 再去着手开发, 这样会让你能够流畅的完成开发工作, 并且让你的代码质量更高.

3. 对自己的工作要有Owner意识, 答应的事情要尽力去做到

  什么是工作的Owner意识,  简单来说, 就是这个工作分配给你, 你就是第一负责人.

  对于分配到自己手里的工作, 首先要有一个正确的评估. 可以简单的分为: 这个工作你能不能做, 能不能按时做完, 要怎么做, 最后能做到什么效果.    

  如果因为种种原因做不到, 需要提前预报风险, 不要等到最后一刻告诉大家, 你没做到. 任务分配给你, 是因为这是你的工作, 也有一部分信任在, 是相信你可以做好, 别去辜负别人的信任,  信任可能因为一件事就确立起来, 也可能因为一件事情就毁掉.

4. 我不管别人摸鱼, 但不要影响到我的工作

  工作难免偷懒, 大家都有想休息放松的时候. 我对这个事情的看法就是, 摸鱼可以, 但是不要影响别人的工作. 

  在整个项目或者需求的流程里, 产品, 后端开发, 前端开发, 测试人员都只是其中的一环. 对于各个环节的人员来说, 都是这样, 可以适当摸鱼, 但是不要压缩了别人安排好的时间.

5. 自己的问题勇于承认, 但不是我的锅我不背

  承认自己的问题并不是一个可耻的事情, 但是不承认被别人扒出来可是非常尴尬的. 

  如果你不能按时完成开发任务, 可以说明你的原因, 尽快的提出来, 别等到最后到了Deadline你说你做不完. 

  或者因为你的bug导致了线上事故, 也没必要遮遮掩掩. 快速的定位问题, 解决问题, 在会议上复盘问题, 最好下次发生同样的状况就好, 也没必要因此给自己很大的心理压力和负担. 常在河边走, 哪有不湿鞋. 

但是, 对于甩锅这种问题, 没有人不反感. 我不去讨论什么叫甩锅, 我只去讨论怎么避免甩锅这种事情的发生. 

  • 在对于需求, 会议, 形成良好的书面文档, 各方进行确认

  • 有问题避免天知地知你知我知, 有问题大家一起沟通, 沟通后形成相关的书面文档

  • 当出现这种问题的时候, 拿出自己的证据来证明自己, 不是老子的锅老子不背

6. 摆正自己和领导的位置

  对于领导, 你是他的下属, 不管你们是酒友还是烟友或者是pao友, 你对他最重要的是工作的能力和处理问题的能力. 认真对待分配的任务, 做好自己的分内工作, 让他看到你对他在工作上的价值, 才是建立你们工作关系的基础.

7. 合理的看待别人的反对和批评

  可能每一个参加工作的人都被批评过或者吐槽, 被领导也好, 被同事也好. 在面对批评时, 不要急于反驳. 大家作为成年人, 很少会有人毫无原因和根据的前提下去吐槽你的问题.     

  他提出的问题, 可能就是你切实存在的问题, 他不说, 下一个人也会说, 尽早了解自己的问题并及时改掉不是坏事. 不能不在意, 也不要太在意.

8. 如果你领导或者同事是sb

   能忍忍, 不能忍就滚. 你不能强迫别人走, 你忍不了, 你自己走.

三: 总结

以上只是我个人的一些经验和体会了, 工作三年相比很多大佬来比, 也只是个小毛孩. 但是希望能帮助到大家, 有问题也欢迎大家积极讨论.