How much Refactoring?

Learn to what extent a developer should refactor their code.

Too much refactoring could lead to some other unforeseen implications. Let’s look at these implications with the help of an example.

Extract the code that calculates the total weighting of all matches:

Press + to interact
public boolean matches(Criteria criteria) {
calculateScore(criteria);
boolean kill = false;
for (Criterion criterion: criteria) {
boolean match = criterion.matches(answerMatching(criterion));
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
}
if (kill)
return false;
return anyMatches(criteria);
}
private void calculateScore(Criteria criteria) {
score = 0;
for (Criterion criterion: criteria)
if (criterion.matches(answerMatching(criterion)))
score += criterion.getWeight().getValue();
}

It might seem like we’re headed toward trouble.

Extract the logic that determines whether or not there are any must-meet criteria that aren’t a match:

Press + to interact
public boolean matches(Criteria criteria) {
calculateScore(criteria);
if (doesNotMeetAnyMustMatchCriterion(criteria))
return false;
return anyMatches(criteria);
}
private boolean doesNotMeetAnyMustMatchCriterion(Criteria criteria) {
for (Criterion criterion: criteria) {
boolean match = criterion.matches(answerMatching(criterion));
if (!match && criterion.getWeight() == Weight.MustMatch)
return true;
}
return false;
}

Shoot! We have new methods and new loops to deal with.

Run the Profile class below to verify the changes:

package iloveyouboss;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class Profile {
   private Map<String,Answer> answers = new HashMap<>();

   private int score;
   private String name;

   public Profile(String name) {
      this.name = name;
   }
   
   public String getName() {
      return name;
   }

   public void add(Answer answer) {
      answers.put(answer.getQuestionText(), answer);
   }

   public boolean matches(Criteria criteria) {
      calculateScore(criteria);
      if (doesNotMeetAnyMustMatchCriterion(criteria))
         return false;
      return anyMatches(criteria);
   }

   private boolean doesNotMeetAnyMustMatchCriterion(Criteria criteria) {
      for (Criterion criterion: criteria) {
         boolean match = criterion.matches(answerMatching(criterion));
         if (!match && criterion.getWeight() == Weight.MustMatch) 
            return true;
      }
      return false;
   }

   private void calculateScore(Criteria criteria) {
      score = 0;
      for (Criterion criterion: criteria) 
         if (criterion.matches(answerMatching(criterion))) 
            score += criterion.getWeight().getValue();
   }

   private boolean anyMatches(Criteria criteria) {
      boolean anyMatches = false;
      for (Criterion criterion: criteria) 
         anyMatches |= criterion.matches(answerMatching(criterion));
      return anyMatches;
   }

   private Answer answerMatching(Criterion criterion) {
      return answers.get(criterion.getAnswer().getQuestionText());
   }

   public int score() {
      return score;
   }

   public List<Answer> classicFind(Predicate<Answer> pred) {
      List<Answer> results = new ArrayList<Answer>();
      for (Answer answer: answers.values())
         if (pred.test(answer))
            results.add(answer);
      return results;
   }
   
   @Override
   public String toString() {
     return name;
   }

   public List<Answer> find(Predicate<Answer> pred) {
      return answers.values().stream()
            .filter(pred)
            .collect(Collectors.toList());
   }
}
Testing modified profile class

Congratulations! All the tests are passing.

We will discuss the performance implications, but first, let’s see what benefits we gain by having three methods.

The reward: precise, testable units

The ...