记录一次代码优化

112 阅读7分钟

1. 记录内容

本篇文章记录了在工作中写代码的时候对代码优化的一次过程,主要是对公共逻辑的提取,涉及到优化方向和函数式接口Function

2. 场景描述

工作中碰到了一些场景,提取之后大致如下:有不同的角色,每种角色对应的工作量不一样,现在需要统计每种角色里面每个人的工作量,并且按照工作量进行排序

3. 代码实现

这里模拟几个角色:厨师、保洁、服务员。对应的类如下:

3.1 po

厨师

package org.example.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.Date;

/**
 * 厨师
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Chef {

    /**
     * 厨师姓名
     */
    private String chefName;

    /**
     * 厨师工号
     */
    private String chefCode;

    /**
     * 下毒时间
     */
    private Date cookingTime;

}

保洁

package org.example.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.Date;

/**
 * 保洁
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Purifier {

    /**
     * 保洁姓名
     */
    private String purifierName;

    /**
     * 保洁工号
     */
    private String purifierCode;

    /**
     * 清洁时间
     */
    private Date cleanTime;
}

服务员

package org.example.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.Date;

/**
 * 服务员
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Waiter {

    /**
     * 服务员姓名
     */
    private String waiterName;

    /**
     * 服务员工号
     */
    private String waiterCode;

    /**
     * 服务时间
     */
    private Date serviceTime;

}

3.2 vo

既然需要统计工作量,那就需要对应的vo类

package org.example.entity.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class WorkLoadDetailVO {

    /**
     * 姓名
     */
    private String name;

    /**
     * 工作量
     */
    private Long workLoad;

}
package org.example.entity.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class WorkLoadVO {

    /**
     * 角色
     */
    private String role;

    /**
     * 工作量
     */
    private List<WorkLoadDetailVO> list;

}

3.3 构造数据

/**
 * 获取厨师工作记录,3个1号,2个2号,其他都是1个
 *
 * @return 厨师工作记录集合
 */
public static List<Chef> selectAllChefList() {
    Chef chef = Chef.builder().chefName("李桂香0号").chefCode("138").cookingTime(new Date()).build();
    Chef chef1 = Chef.builder().chefName("李桂香1号").chefCode("137").cookingTime(new Date()).build();
    Chef chef11 = Chef.builder().chefName("李桂香1号").chefCode("137").cookingTime(new Date()).build();
    Chef chef111 = Chef.builder().chefName("李桂香1号").chefCode("137").cookingTime(new Date()).build();
    Chef chef2 = Chef.builder().chefName("李桂香2号").chefCode("139").cookingTime(new Date()).build();
    Chef chef22 = Chef.builder().chefName("李桂香2号").chefCode("139").cookingTime(new Date()).build();
    Chef chef3 = Chef.builder().chefName("李桂香3号").chefCode("140").cookingTime(new Date()).build();
    Chef chef4 = Chef.builder().chefName("李桂香4号").chefCode("141").cookingTime(new Date()).build();
    Chef chef5 = Chef.builder().chefName("李桂香5号").chefCode("142").cookingTime(new Date()).build();
    Chef chef6 = Chef.builder().chefName("李桂香6号").chefCode("143").cookingTime(new Date()).build();
    Chef chef7 = Chef.builder().chefName("李桂香7号").chefCode("144").cookingTime(new Date()).build();
    Chef chef8 = Chef.builder().chefName("李桂香8号").chefCode("145").cookingTime(new Date()).build();
    Chef chef9 = Chef.builder().chefName("李桂香9号").chefCode("146").cookingTime(new Date()).build();
    return Arrays.asList(chef, chef1, chef2, chef3, chef4, chef11, chef111, chef22, chef5, chef6, chef7, chef8, chef9);
}


/**
 * 获取保洁工作记录,3个1号,2个2号,其他都是1个
 *
 * @return 厨师工作记录集合
 */
public static List<Purifier> selectAllPurifierList() {
    Purifier purifier = Purifier.builder().purifierName("何星福0号").purifierCode("238").cleanTime(new Date()).build();
    Purifier purifier1 = Purifier.builder().purifierName("何星福1号").purifierCode("237").cleanTime(new Date()).build();
    Purifier purifier11 = Purifier.builder().purifierName("何星福1号").purifierCode("237").cleanTime(new Date()).build();
    Purifier purifier111 = Purifier.builder().purifierName("何星福1号").purifierCode("237").cleanTime(new Date()).build();
    Purifier purifier2 = Purifier.builder().purifierName("何星福2号").purifierCode("239").cleanTime(new Date()).build();
    Purifier purifier22 = Purifier.builder().purifierName("何星福2号").purifierCode("239").cleanTime(new Date()).build();
    Purifier purifier3 = Purifier.builder().purifierName("何星福3号").purifierCode("240").cleanTime(new Date()).build();
    Purifier purifier8 = Purifier.builder().purifierName("何星福8号").purifierCode("245").cleanTime(new Date()).build();
    Purifier purifier9 = Purifier.builder().purifierName("何星福9号").purifierCode("246").cleanTime(new Date()).build();
    return Arrays.asList(purifier, purifier1, purifier2, purifier3, purifier11, purifier111, purifier22,
            purifier8, purifier9);
}

/**
 * 获取服务员工作记录,3个1号,2个2号,其他都是1个
 *
 * @return 服务员工作记录集合
 */
public static List<Waiter> selectAllWaiterList() {
    Waiter waiter = Waiter.builder().waiterName("水蒸气").waiterCode("238").serviceTime(new Date()).build();
    Waiter waiter1 = Waiter.builder().waiterName("水蒸气1号").waiterCode("237").serviceTime(new Date()).build();
    Waiter waiter11 = Waiter.builder().waiterName("水蒸气1号").waiterCode("237").serviceTime(new Date()).build();
    Waiter waiter111 = Waiter.builder().waiterName("水蒸气1号").waiterCode("237").serviceTime(new Date()).build();
    Waiter waiter2 = Waiter.builder().waiterName("水蒸气2号").waiterCode("239").serviceTime(new Date()).build();
    Waiter waiter22 = Waiter.builder().waiterName("水蒸气2号").waiterCode("239").serviceTime(new Date()).build();
    Waiter waiter6 = Waiter.builder().waiterName("水蒸气6号").waiterCode("243").serviceTime(new Date()).build();
    Waiter waiter7 = Waiter.builder().waiterName("水蒸气7号").waiterCode("244").serviceTime(new Date()).build();
    Waiter waiter8 = Waiter.builder().waiterName("水蒸气8号").waiterCode("245").serviceTime(new Date()).build();
    Waiter waiter9 = Waiter.builder().waiterName("水蒸气9号").waiterCode("246").serviceTime(new Date()).build();
    return Arrays.asList(waiter, waiter1, waiter2, waiter11, waiter111, waiter22,
            waiter6, waiter7, waiter8, waiter9);
}

3.4 实现功能

如果要实现按照角色分类之后再对每个人的工作量排序之后返回给前端,以下代码就可以实现:

@Test
public void getWorkloadByChef() {
    // 获取不同角色下 每个人的工作量
    List<Chef> chefs = selectAllChefList();
    // 获取工号姓名,对应关系
    Map<String, String> codeNameMap = new HashMap<>(16);
    chefs.forEach(chef -> codeNameMap.putIfAbsent(chef.getChefCode(), chef.getChefName()));
    // 先工号分组,再统计每个工号对应多少条记录
    Map<String, List<Chef>> collect = chefs.stream().collect(Collectors.groupingBy(Chef::getChefCode));
    List<WorkLoadDetailVO> workLoadDetailList = collect.entrySet().stream().map(entry -> {
        String code = entry.getKey();
        List<Chef> list = entry.getValue();
        return new WorkLoadDetailVO().setName(codeNameMap.get(code)).setWorkLoad((long) list.size());
    }).sorted(Comparator.comparingLong(WorkLoadDetailVO::getWorkLoad).reversed()).collect(Collectors.toList());
    WorkLoadVO workLoadVO = WorkLoadVO.builder().role("厨师").list(workLoadDetailList).build();
    System.out.println("workLoadVO = " + workLoadVO);
}

@Test
public void getWorkloadByPurifier() {
    // 获取不同角色下 每个人的工作量
    List<Purifier> purifierList = selectAllPurifierList();
    // 获取工号姓名,对应关系
    Map<String, String> codeNameMap = new HashMap<>(16);
    purifierList.forEach(chef -> codeNameMap.putIfAbsent(chef.getPurifierCode(), chef.getPurifierName()));
    // 先工号分组,再统计每个工号对应多少条记录
    Map<String, List<Purifier>> collect = purifierList.stream().collect(Collectors.groupingBy(Purifier::getPurifierCode));
    List<WorkLoadDetailVO> workLoadDetailList = collect.entrySet().stream().map(entry -> {
        String code = entry.getKey();
        List<Purifier> list = entry.getValue();
        return new WorkLoadDetailVO().setName(codeNameMap.get(code)).setWorkLoad((long) list.size());
    }).sorted(Comparator.comparingLong(WorkLoadDetailVO::getWorkLoad).reversed()).collect(Collectors.toList());
    WorkLoadVO workLoadVO = WorkLoadVO.builder().role("保洁").list(workLoadDetailList).build();
    System.out.println("workLoadVO = " + workLoadVO);
}

@Test
public void getWorkloadByWaiter() {
    // 获取不同角色下 每个人的工作量
    List<Waiter> waiterList = selectAllWaiterList();
    // 获取工号姓名,对应关系
    Map<String, String> codeNameMap = new HashMap<>(16);
    waiterList.forEach(chef -> codeNameMap.putIfAbsent(chef.getWaiterCode(), chef.getWaiterName()));
    // 先工号分组,再统计每个工号对应多少条记录
    Map<String, List<Waiter>> collect = waiterList.stream().collect(Collectors.groupingBy(Waiter::getWaiterCode));
    List<WorkLoadDetailVO> workLoadDetailList = collect.entrySet().stream().map(entry -> {
        String code = entry.getKey();
        List<Waiter> list = entry.getValue();
        return new WorkLoadDetailVO().setName(codeNameMap.get(code)).setWorkLoad((long) list.size());
    }).sorted(Comparator.comparingLong(WorkLoadDetailVO::getWorkLoad).reversed()).collect(Collectors.toList());
    WorkLoadVO workLoadVO = WorkLoadVO.builder().role("服务员").list(workLoadDetailList).build();
    System.out.println("workLoadVO = " + workLoadVO);
}

4. 代码优化

4.1 优化方案

在上述代码中,已经可以实现基本功能了,但是写完之后发现代码里有很多类似的地方,甚至逻辑都是一样的,不同的地方只是少数,所以就可以考虑是否代码需要进一步优化。通过观察,可以看到上述代码中不同的地方有如下:

  1. 每次需要操作的list不同
  2. list中元素的类型不同
  3. 构造codeNameMapgetCodegetName 方法不同
Map<String, String> codeNameMap = new HashMap<>(16);
chefs.forEach(chef -> codeNameMap.putIfAbsent(chef.getChefCode(), chef.getChefName()));
Map<String, List<Chef>> collect = chefs.stream().collect(Collectors.groupingBy(Chef::getChefCode));
  1. role不同
WorkLoadVO workLoadVO = WorkLoadVO.builder().role("厨师").list(workLoadDetailList).build();

4.2 代码实现

有了上述的思路,接下来就可以动手优化了,这里就是优化之后的代码,代码都不难,只是不容易一开始就想到

private static <T> void addWorkLoad(List<T> list, Function<T, String> getCode, Function<T, String> getName,
                                    String roleName, List<WorkLoadVO> result) {
    Map<String, String> codeNameMap = list.stream().collect(Collectors.toMap(getCode, getName, (existing, replacement) -> existing));
    Map<String, List<T>> collect = list.stream().collect(Collectors.groupingBy(getCode));
    List<WorkLoadDetailVO> workLoadDetailList = collect.entrySet().stream()
            .map(entry -> new WorkLoadDetailVO().setName(codeNameMap.get(entry.getKey())).setWorkLoad((long) entry.getValue().size()))
            .sorted(Comparator.comparingLong(WorkLoadDetailVO::getWorkLoad).reversed())
            .collect(Collectors.toList());
    WorkLoadVO workLoadVO = WorkLoadVO.builder().role(roleName).list(workLoadDetailList).build();
    result.add(workLoadVO);
}

整合之后的代码如下,可以看到上面3个重复的代码,经过优化之后整合到了2个方法里面,就实现了功能

public static List<WorkLoadVO> getWorkloadByRoles() {
    List<WorkLoadVO> result = new ArrayList<>();

    List<Chef> chefs = selectAllChefList();
    addWorkLoad(chefs, Chef::getChefCode, Chef::getChefName, RoleConstants.CHEF_NAME, result);

    List<Purifier> purifierList = selectAllPurifierList();
    addWorkLoad(purifierList, Purifier::getPurifierCode, Purifier::getPurifierName, RoleConstants.PURIFIER_NAME, result);

    List<Waiter> waiterList = selectAllWaiterList();
    addWorkLoad(waiterList, Waiter::getWaiterCode, Waiter::getWaiterName, RoleConstants.WAITER_NAME, result);
    return result;
}

5. 总结

5.1 知识点总结

这上面看似是一个简单的功能,但是实际工作中如果没有相关经验,可能一下子很难想到上述一步到位的方案(个人认为最大的原因是缺乏把方法提取为参数的经验),上述代码中使用到了泛型Function接口,把重复性的逻辑都提取到了一个方法里面

5.2 优化改进

本次优化也为后续功能的开发提供了一些参考经验,比如可以把函数作为方法参数,本次用的是Function接口,后续在开发的时候可以考虑其他的函数式接口,简化开发

6. 源码链接

gitee.com/szq2021/met…