如何重构重复代码?

how to refactor the duplicate code?

我知道重复的是气味

但是如何重构代码?

public List<HighWay> updateAllNewHighWays(HighWayRepository repository)
            throws IOException {
        List<HighWay> highWays = new ArrayList<HighWay>();
        for (RoadCode code : RoadCode.values()) {
            try {
                pageParam.setRoadName(code);
                highWays.addAll(getAndSaveNewHighWay(repository));
            } catch (IOException e) {
                IOException exception = dealException(e, code);
                throw exception;
            }
        }
        return highWays;
    }

    public List<HighWay> getAllNewHighWays(HighWayRepository repository)
            throws IOException {
        List<HighWay> highWays = new ArrayList<HighWay>();
        for (RoadCode code : RoadCode.values()) {
            try {
                pageParam.setRoadName(code);
                highWays.addAll(getNewHighWay(repository));
            } catch (IOException e) {
                IOException exception = dealException(e, code);
                throw exception;
            }
        }
        return highWays;
    }

由于唯一变化的部分是循环内部,您可以重构循环部分,任何只有循环内部变化的部分。

如果您使用 Java 8,您可以将 getAndSaveNewHighWay(repository)getNewHighWay(repository) 作为方法传入,方法引用作为 Function<HighWayRepository, List<HighWay>> 实现

public List<HighWay> handleHighways(HighWayRepository repository, Function<HighWayRepository, List<HighWay>> function){
  List<HighWay> highWays = new ArrayList<HighWay>();
        for (RoadCode code : RoadCode.values()) {
            try {

                pageParam.setRoadName(code);
                //call our method 
                highWays.addAll(function.apply(repository));
            } catch (IOException e) {
                IOException exception = dealException(e, code);
                throw exception;
            }
        }
        return highWays;
}

然后在你的调用代码中:

 List<HighWay> highways = handleHighways(repository, MyClass::getAndSaveNewHighWay);

List<HighWay> highways = handleHighways(repository, MyClass::getNewHighWay);

如果没有 Java 8,您可以通过创建自己的接口来实现类似的功能,该接口具有采用 HighWayRepository 和 return 的方法,然后编写 List<HighWay> 2 种不同的实现方式

为了去除重复,我使用了下一个算法:

  1. 确保你有验证这些功能是否有效的测试(在重构之前你的所有测试应该是 运行 绿色)

  2. 将重复的逻辑复制并粘贴到新方法中(例如 tobeReused,稍后您可以将其重命名为更好的方法)

  3. 参数化使用 lambda 的变化(Java 支持下一种类型的 lambda:Runnable、Consumer、Predicate、Function)

  4. 重命名方法 1. 一个更好的名字。为了不卡在分析-麻痹,理解了再重命名是个好主意


  1. 一个好的测试套件应该

    1. 专注于行为测试

    2. 涵盖正面和负面场景

    3. 覆盖边缘案例

    4. 覆盖异常

    5. 测试名称应该描述什么将被测试而不是如何

    6. 每个测试的格式应遵循 Given-When-Then 或 Arrange-Act-Assert。

    可能的测试套件的示例代码结构:

    public class HighwaysTest {
      //Repository is valid
      @Test
      public void whenHighWayRepositoryIsValidThenHighWaysShouldBeSaved() {
      }
    
      //Repository is invalid
      @Test
      public void whenHighWayRepositoryIsInvalidThenHighWaysShouldBeSaved() {
      }
    
      //Wrong road code
      @Test
      public void whenRoadCodeIsInvalidThenPageParamIsNotUpdated() {
      }
    
      //Function getAndSaveNewHighWay fails
      @Test
      public void whenGetAndSaveNewHighWayFailsThenExceptionIsThrown() {
    
      }
    
      //Function getNewHighWay fails
      @Test
      public void whenGetNewHighWayFailsThenExceptionIsThrown() {
    
      }
    }
    
  2. 将重复的逻辑复制并粘贴到新方法中

     private List<HighWay> tobeReused(HighWayRepository repository) throws IOException {
            List<HighWay> highWays = new ArrayList<HighWay>();
            for (RoadCode code : RoadCode.values()) {
                try {
                    pageParam.setRoadName(code);
                    highWays.addAll(getAndSaveNewHighWay(repository));
                } catch (IOException e) {
                    IOException exception = dealException(e, code);
                    throw exception;
                }
            }
            return highWays;
        }
    
  3. 参数化使用 lambda 的变化(在本例中 Function<ParameterType, ReturnType>):

        private <P extends  HighWayRepository, 
                 R extends  Collection> List<HighWay> tobeReused(P repository, Function<P, R> lambda) throws IOException {
    List<HighWay> highWays = new ArrayList<HighWay>();
                for (RoadCode code : RoadCode.values()) {
                    try {
                        pageParam.setRoadName(code);
                        highWays.addAll(lambda.apply(repository));
                    } catch (IOException e) {
                        IOException exception = dealException(e, code);
                        throw exception;
                    }
                }
                return highWays;
            }
    
  4. 因为找到一个好名字可能很棘手,所以最好在你很好地掌握业务领域之后再命名。然后函数 tobeReused 可以重命名为 updatePageParamAndReturnHighways:

您的代码将如下所示:

public List<HighWay> updateAllNewHighWays(HighWayRepository repository) throws IOException {
        Function<HighWayRepository, Collection> lambda = (repo) -> getAndSaveNewHighWay(repo);
        List<HighWay> highWays = updatePageParamAndReturnHighways(repository, lambda);
        return highWays;
    }

public List<HighWay> getAllNewHighWays(HighWayRepository repository) throws IOException {
        Function<HighWayRepository, Collection> lambda = (repo) -> getNewHighWay(repo);
        List<HighWay> highWays = updatePageParamAndReturnHighways(repository, lambda);
        return highWays;
    }

单一职责原则 (SRP)

您可能需要考虑重构您的函数 updateAllNewHighWaysgetAllNewHighWays 因为他们有不止一项责任(更新 pageParam 和 return 高速公路)。