Awkward Refactoring

Trust the process as long as you have one
Published on 2024/06/29

In "Refactoring" Book Club - Episode 1 we had our first feel for the book. Some of the questions we had last time we discussed it, were answered pretty soon after. One of the points of confusion was the nested functions approach. This is just one example of "awkward" refactoring. I might have missed the point the first time they were introduced. To freshen up the concept, let's assume you start with a large function doing a good amount of work. In it, you identify logic that can be grouped. Let's see an example:

function processOrder(order) {
  console.log("Processing order for:", order.customerName);

  let total = 0;
  for (let item of order.items) {
    total += item.price * item.quantity;
  }

  console.log("Order details:");
  for (let item of order.items) {
    console.log(`Item: ${item.name}, Quantity: ${item.quantity}, Price: ${item.price}`);
  }

  logOrderDetails();
  console.log("Total price:", total);

  // ...
}

This is a trivial example just to get a point across. This function does more than process an order, one of them is logging. An initial refactoring step could be to extract the logging into its function like so:

function processOrder(order) {
  console.log("Processing order for:", order.customerName);

  let total = 0;
  for (let item of order.items) {
    total += item.price * item.quantity;
  }

  // Extracted function for logging
  function logOrderDetails() {
    console.log("Order details:");
    for (let item of order.items) {
      console.log(`Item: ${item.name}, Quantity: ${item.quantity}, Price: ${item.price}`);
    }
  }

  logOrderDetails();
  console.log("Total price:", total);

  // ...
}

This step felt odd to me. It's not immediate what the benefit is but I realized later that this is ONE STEP to prove that the logic for logging can be extracted. This breaks no test and is a step in the right direction. Eventually, we'll get to the point where this can be pulled out of the processOrder function and either be a class method or an independent piece of logic. I wasn't trusting the process, but I'm skeptical by nature so I was happy to see how it evolved. At first, I thought this could only work if you had a vision. Then I realized that a methodical approach to refactoring like this seems to guide you in the right direction. As you make non-breaking changes, some patterns will arise that can guide you into the next refactoring idea.

Thoughts

It takes good discipline to approach refactoring the way Martin Fowler does. It's a very methodical way that requires trusting the process. The big surprise was the dedication to make sure every change doesn't alter the observable behavior. It is purely perceived as a way to modify the internal structure only, while your program behaves the same. As soon as this definition of refactoring came up in Chapter 2, all the "awkward" steps made sense. As I look at how I review PRs I realize I do something along these lines pretty often. If a portion of the logic is hard to understand (or simply it's something I'm unfamiliar with), I tend to extract logic into its function, rename variables, and so on. Sometimes those small changes add up to a proposal in the review, other times they just live locally to facilitate my review. I want to give Martin's approach a try and see how that goes. There are a few more gems that came out of Episode 2 of the book club, but they would require a much larger conversation that I won't be able to fit into this 5-minute read-time post.

0
← Go Back