1 Refactoring refactoring

published book

This chapter covers

  • Understanding the elements of refactoring
  • Incorporating refactoring into your daily work
  • The importance of safety for refactoring
  • Introducing the overarching example for part 1

It is well known that high code quality leads to cheaper maintenance, fewer errors, and happier developers. The most common way to get high code quality is through refactoring. However, the way refactoring is usually taught—with code smells and unit testing—imposes an unnecessarily high barrier to entry. I believe that anyone can execute simple refactoring patterns safely with a little practice.

In software development, we place problems somewhere on the diagram shown in figure 1.1, indicating a lack of sufficient skills, culture, tools, or a combination of those. Refactoring is a sophisticated endeavor and therefore lies right in the middle. It requires each component:

  • Skills—We need the skills to know what code is bad and needs refactoring. Experienced programmers can determine this through their knowledge of code smells. But the boundaries of code smells are blurry (requiring judgment and experience) or open to interpretation and therefore not easy to learn; and to a junior developer, understanding code smells can seem more like a sixth sense than a skill.
  • Culture—We need a culture and workflow that encourage taking the time to perform refactoring. In many cases, this culture is implemented through the famous red-green-refactor loop used in test-driven development. However, test-driven development is a much more difficult craft, in my opinion. Red-green-refactor also does not easily give way to doing refactoring in a legacy codebase.
  • Tools—We need something to help ensure that what we are doing is safe. The most common way to achieve this is through automated testing. But as already mentioned, learning to do effective automated testing is difficult in itself.
Figure 1.1 Skills, culture, and tools

The following sections dive into each of these areas and describe how we can begin our refactoring journey from a much simpler foundation without testing and abstract code smells. Learning refactoring this way can quickly catapult junior developers’, students’, and programming enthusiasts’ code quality to the next level. Tech leads can also use the methods in this book as a basis for introducing refactoring in teams that are not routinely doing it.

livebook features:
highlight, annotate, and bookmark
Select a piece of text and click the appropriate icon to annotate, bookmark, or highlight (you can also use keyboard shortcuts - h to highlight, b to bookmark, n to create a note).

You can automatically highlight by performing the text selection while keeping the alt/ key pressed.
highlights
join today to enjoy all our content. all the time.
 

1.1 What is refactoring?

I answer the question “What is refactoring?” in a lot more detail in the next chapter, but it is helpful to get an intuition for it up front before we dive into the different hows of refactoring. In its simplest form, refactoring means “changing code without changing what it does.” Let’s start with an example of refactoring to make it clear what I’m talking about. Here, we replace an expression with a local variable.

Listing 1.1 Before

Listing 1.2 After

return pow(base, exp / 2) * pow(base, exp / 2);
let result = pow(base, exp / 2);
return result * result;

There are many possible reasons to refactor:

  • Making code faster (as in the previous example)
  • Making code smaller
  • Making code more general or reusable
  • Making code easier to read or maintain

The last reason is so important and central that we equate it with good code.

Definition

Good code is human-readable and easy to maintain, and it correctly performs what it set out to do.

As refactoring mustn’t change what the code is doing, in this book we focus on human-readable and easy to maintain. We discuss these reasons to refactor in more detail in chapter 2. In this book, we only consider refactoring that results in good code; therefore, the definition we use is as follows.

Definition

Refactoring—Changing code to make it more human-readable and maintainable without changing what it does.

I should also mention that the type of refactoring we consider relies heavily on working with an object-oriented programming language.

Many people think of programming as writing code; however, most programmers spend more time reading and trying to understand code than writing it. This is because we work in a complex domain, and changing something without understanding it can cause catastrophic failures.

So, the first argument for refactoring is purely economic: programmers’ time is expensive, so if we make our codebase more readable, we free up time for implementing new features. The second argument is that making our code more maintainable means fewer, easier-to-fix bugs. Third, a good codebase is simply more fun. When we read code, we build a model in our heads of what the code is doing; the more we have to keep in our head at one time, the more exhausting it is. This is why it is much more fun to start from scratch—and why debugging can be dreadful.

livebook features:
discuss
Ask a question, share an example, or respond to another reader. Start a thread by selecting any piece of text and clicking the discussion icon.
discussions
Get Five Lines of Code
add to cart

1.2 Skills: What to refactor?

Knowing what you should refactor is the first barrier to entry. Usually, refactoring is taught alongside something called code smells. These “smells” are descriptions of things that might suggest our code is bad. While they are powerful, they are also abstract and difficult to get started with, and it takes time to develop a feel for them.

This book takes a different approach and presents easily recognizable, applicable rules to determine what to refactor. These rules are easy to use and quick to learn. They are also sometimes too strict and require you to fix code that is not smelly. On rare occasions, we might follow the rules and still have smelly code.

As figure 1.2 illustrates, the overlap between smells and rules is not perfect. My rules are not the be-all and end-all of good code. They are a head start on the road to developing a guru-like feeling for what good code is. Let’s look at an example of the difference between a code smell and the rules in this book.

Figure 1.2 Rules and code smells

1.2.1 An example code smell

A well-known code smell is as follows: a function should do one thing. This is a great guideline, but it is not easy to know what the one thing is. Look again at the earlier code: is it smelly? Arguably, it divides, exponentiates, and then multiplies. Does that mean it does three things? On the other hand, it only returns one number and doesn’t change any state, so is it doing only one thing?

let result = pow(base, exp / 2);
return result * result;

1.2.2 An example rule

Compare the preceding code smell to the following rule (covered in detail in chapter 3): a method should never have more than Five Lines of Code. We can determine this at a glance, with no further questions to ask. The rule is clear, concise, and easy to remember—especially since it is also the title of this book.

Remember, the rules presented in this book are like training wheels. As discussed earlier, they cannot guarantee good code in every situation; and on some occasions, it might be wrong to follow them. However, they are useful if you don’t know where to start, and they motivate nice code refactoring.

Note that all the names of the rules are stated in absolute terms, using words like never, so they are easy to remember. But the detailed descriptions often specify exceptions: when not to apply the rules. The descriptions also state the rules’ intentions. At the beginning of learning refactoring, we only need to use the absolute names; when those are internalized, we can start learning the exceptions as well, after which we can begin to use the intentions—then we’ll be coding gurus.

livebook features:
settings
Update your profile, view your dashboard, tweak the text size, or turn on dark mode.
settings
Sign in for more free preview time

1.3 Culture: When to refactor?

Refactoring is like taking a shower.

—Kent Beck

Refactoring works best—and costs least—if you do it regularly. So if you can, I recommend that you incorporate it into your daily work. Most of the literature suggests a red-green-refactor workflow; but as mentioned earlier, this approach ties refactoring to test-driven development—and in this book, we want to separate them and focus specifically on the refactoring part. Therefore, I recommend a more general six-step workflow to solve any programming task, as shown in figure 1.3:

  1. Explore. Often, we are not completely sure what we need to build right from the start. Sometimes the customer does not know what they want us to build; other times, the requirements are written in ambiguous prose; sometimes we do not even know if the task can be solved. So, always start by experimenting. Implement something quickly, and then you can validate with the customer that you agree on what they need.
  2. Specify. Once you know what you need to build, make it explicit. Optimally, this results in some form of automated test.
  3. Implement. Implement the code.
  4. Test. Make sure the code passes the specification from step 2.
  5. Refactor. Before delivering the code, make sure it is easy for the next person to work with (and that next person might be you).
  6. Deliver. There are many ways to deliver; the most common are through a pull request or by pushing to a specific branch. The most important thing is that your code gets to the users. Otherwise, what’s the point?
Figure 1.3 Workflow

Because we are doing rule-based refactoring, the workflow is straightforward and easy to get started with. Figure 1.4 zooms in on step 5: refactor.

Figure 1.4 Detailed view of the refactoring step

I have designed the rules so they are easy to remember and so that it’s easy to spot when to use them without any assistance. This means finding a method that breaks a rule is usually trivial. Every rule also has a few refactoring patterns linked with it, making it easy to know exactly how to fix a problem. The refactoring patterns have explicit step-by-step instructions to ensure that you do not accidentally break something. Many of the refactoring patterns in this book intentionally use compile errors to help make sure you don’t introduce errors. Once we’ve practiced a little, both the rules and the refactoring patterns will become second nature.

1.3.1 Refactoring in a legacy system

Even if we are starting from a large legacy system, there is a clever way to incorporate refactoring into our daily work without having to stop everything and refactor the whole codebase first. Simply following this awesome quote:

First make the change easy, then make the easy change.

—Kent Beck

Whenever we are about to implement something new, we start by refactoring, so it is easy to add our new code. This is similar to getting all the ingredients ready before you start baking.

1.3.2 When should you not refactor?

Mostly, refactoring is awesome, but it has a few downsides. Refactoring can be time consuming, especially if you don’t do it regularly. And as mentioned earlier, programmer time is expensive.

There are three types of codebases where refactoring probably isn’t worth it:

  • Code you are going to write, run only once, and then delete. This is what is known as a spike in the Extreme Programming community.
  • Code that is in maintenance mode before it is going to be retired.
  • Code with strict performance requirements, such as an embedded system or a high-end physics engine in a game.

In any other case, I argue that investing in refactoring is the smart choice.

livebook features:
highlight, annotate, and bookmark
Select a piece of text and click the appropriate icon to annotate, bookmark, or highlight (you can also use keyboard shortcuts - h to highlight, b to bookmark, n to create a note).

You can automatically highlight by performing the text selection while keeping the alt/ key pressed.
highlights
join today to enjoy all our content. all the time.
 

1.4 Tools: How to refactor (safely)

I like automated tests as much as anybody. However, learning how to test software effectively is a complicated skill in itself. So if you already know how to do automated testing, feel free to use it throughout this book. If you don’t, don’t worry.

We can think about testing this way: automated testing is to software development what brakes are to cars. Cars don’t have brakes because we want to go slowly—they have brakes so we feel safe going fast. The same is true for software: automated tests make us feel safe going fast. In this book, we are learning a completely new skill, so we don’t need to go fast.

Instead, I propose relying more heavily on other tools, such as these:

  • Detailed, step-by-step, structured refactoring patterns akin to recipes
  • Version control
  • The compiler

I believe that if the refactoring patterns are carefully designed and performed in tiny steps, it is possible to refactor without breaking anything. This is especially true in cases where our IDE can perform the refactoring for us.

To remedy the fact that we don’t talk about testing in this book, we use the compiler and types to catch a lot of the common mistakes we might make. Even so, I recommend that you regularly open the application you are working on and check that it is not completely broken. Whenever we have verified this, or when we know the compiler is happy, we make a commit so that if at some point the application is broken and we don’t know how to immediately fix it, we can easily jump back to the last time it was working.

If we are working on a real-world system without automated tests, we can still perform refactoring, but we need to get our confidence from somewhere. Confidence can come from using an IDE to perform the refactoring; testing manually; taking truly tiny steps; or something else. However, the extra time we would spend on these activities probably makes it more cost effective to do automated testing.

livebook features:
discuss
Ask a question, share an example, or respond to another reader. Start a thread by selecting any piece of text and clicking the discussion icon.
discussions
Sign in for more free preview time

1.5 Tools you need to get started

As I said earlier, the types of refactoring discussed in this book need an object-oriented language. That is the primary thing you need in order to read and understand this book. Coding and refactoring are both crafts that we perform with our fingers. Therefore, they are best learned through the fingers by following along with the examples, experimenting, and having fun while your hands learn the routines. To follow along with the book, you need the tools described next. For installation instructions, see the appendix.

1.5.1 Programming language: TypeScript

All the coding examples presented in this book are written in TypeScript. I chose TypeScript for multiple reasons. Most important, it looks and feels similar to the most commonly used programming languages—Java, C#, C++, and JavaScript—and thus, people familiar with any of those languages should be able to read TypeScript without any problem. TypeScript also provides a way to go from completely “un-object-oriented” code (that is, code without a single class) to highly object-oriented code.

Note

To better utilize space in the printed book, this book uses a programming style that avoids line breaks while still being readable. I’m not advocating that you use the same style—unless you are coincidentally also writing a book containing lots of TypeScript code. This is also why indentation and braces are sometimes formatted differently in the book than in the project code.

If you are unfamiliar with TypeScript, I’ll explain any gotchas as they appear, in boxes like the following.

  • 0 == "" is true.

  • 0 === "" is false.

Even though the examples are in TypeScript, all refactoring patterns and rules are general and apply to any object-oriented language. In rare cases, TypeScript helps or hinders us; these cases are explicitly stated, and we discuss how to handle these situations in other common languages.

1.5.2 Editor: Visual Studio Code

I do not assume that you are using a specific editor; however, if you don’t have a preference, I recommend Visual Studio Code. It works well with TypeScript. Also, it supports running tsc -w in a background terminal that does the compiling so we don’t forget to do it.

Note

Visual Studio Code is an entirely different tool than Visual Studio.

1.5.3 Version control: Git

Although you are not required to use version control to follow along with this book, I strongly recommend it, as it makes it much easier to undo something if you get lost in the middle.

livebook features:
settings
Update your profile, view your dashboard, tweak the text size, or turn on dark mode.
settings
Tour livebook

Take our tour and find out more about liveBook's features:

  • Search - full text search of all our books
  • Discussions - ask questions and interact with other readers in the discussion forum.
  • Highlight, annotate, or bookmark.
take the tour

1.6 Overarching example: A 2D puzzle game

Finally, let’s discuss how I am going to teach all these wonderful rules and amazing refactoring patterns. The book is built around a single overarching example: a 2D block-pushing puzzle game, similar to the classic game Boulder Dash (figure 1.5).

Figure 1.5 A screenshot of the game out of the box

This means we have one substantial codebase to play with throughout part 1 of the book. Having one example saves time because we don’t have to become familiar with a new example in every chapter.

The example is written in a realistic style, similar to what is used in the industry. It is by no means an easy exercise unless you have the skills learned in this book. The code already adheres to the DRY (Don’t Repeat Yourself) KISS (Keep It Simple, Stupid) principles; even so, it is no more pleasant than a dry kiss.

I chose a computer game because when we test manually, it is easy to spot if something behaves incorrectly: we have an intuition for how it should behave. It is also slightly more fun to test than looking at something like logs from a financial system.

The user controls the player square using the arrow keys. The objective of the game is to get the box (labeled 2 in figure 1.5) to the lower-right corner. Although the colors don’t appear in the printed book, the game elements are different colors as follows:

  1. The red square is the player.
  2. Brown squares are boxes.
  3. Blue squares are stones.
  4. Yellow squares are keys or locks—we fix this later.
  5. Greenish squares are called flux.
  6. Gray squares are walls.
  7. White squares are air (empty).

If a box or stone is not supported by anything, it falls. The player can push one stone or box at a time, provided it is not obstructed or falling. The path between the box and the lower-right corner is initially obstructed by a lock, so the player has to get a key to remove it. Flux can be “eaten” (removed) by the player by stepping on it.

Now would be a great time to get the game and play around with it:

  1. Open a console where you want the game to be stored.
    1. git clone https://github.com/thedrlambda/five-lines downloads the source code for the game.
    2. tsc -w compiles the TypeScript to JavaScript every time it changes.
  2. Open index.html in a browser.

It is possible to change the level in the code, so feel free to have fun creating your own maps by updating the array in the map variable (for an example, see the appendix):

  1. Open the folder in Visual Studio Code.
  2. Select Terminal and then New Terminal.
  3. Run the command tsc -w.
  4. TypeScript is now compiling your changes in the background, and you can close the terminal.
  5. Every time you make a change, wait for a moment while TypeScript compiles, and then refresh your browser.

This is the same procedure you’ll use when coding along with the examples in part 1. Before we get to that, though, we build a more detailed foundation of refactoring in the next chapter.

1.6.1 Practice makes perfect: A second codebase

As I am a strong believer in practice, I have made another project, provided without a solution. You can use this project on rereading, if you want a challenge; or as exercises for students, if you are a teacher. This project is a 2D action game. Both codebases use the same style and structure, they have the same elements, and it takes the same steps to refactor them. Although this second codebase is slightly more advanced, carefully following the rules and refactoring patterns should yield the desired result. To get this project, use the same steps as described with the URL https:/ /github .com/thedrlambda/bomb-guy.

livebook features:
highlight, annotate, and bookmark
Select a piece of text and click the appropriate icon to annotate, bookmark, or highlight (you can also use keyboard shortcuts - h to highlight, b to bookmark, n to create a note).

You can automatically highlight by performing the text selection while keeping the alt/ key pressed.
highlights
join today to enjoy all our content. all the time.
 

1.7 A note on real-world software

It is important to reiterate that the focus of this book is introducing refactoring. The focus is not on providing specific rules that you can apply to production code in all circumstances. The way to use the rules is to first learn their names and follow them. Once this is easy for you, learn the descriptions with their exceptions; finally, use this to build an understanding of the underlying code smell. This journey is illustrated in figure 1.6.

Figure 1.6 How to use the rules

This also answers why we cannot make an automatic refactoring program. (We might be able to make a plugin to highlight possibly problematic areas in the code, based on the rules.) The purpose of the rules is to build understanding. In short: follow the rules until you know better.

Also note that because we focus only on learning refactoring, and we have a safe environment, we can get away without automated tests—but this probably is not true for real systems. We do so because it is much easier to learn automated testing and refactoring separately.

Summary

  • Executing refactoring requires a combination of skills to know what to refactor, culture to know when to refactor, and tools to know how to refactor.
  • Conventionally, code smells are used to describe what to refactor. These are difficult for junior programmers to internalize because they are fuzzy. This book provides concrete rules to replace code smells while learning. The rules have three levels of abstraction: very concrete names, descriptions that add nuance in the form of exceptions, and, finally, the intention of the smells they are derived from.
  • I believe that automated testing and refactoring can be learned separately to further lower the barrier to entry. Instead of automated testing, we utilize the compiler, version control, and manual testing.
  • The workflow of refactoring is connected with test-driven development in the red-green-refactor loop. But this again implies a dependency on automated testing. Instead, I suggest using a six-step workflow (explore, specify, implement, test, refactor, deliver) for new code or doing refactoring right before changing code.
  • Throughout part 1 of this book, we use Visual Studio Code, TypeScript, and Git to transform the source code of a 2D puzzle game.
sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage