Fixes the Build: Clean Code

07/16/2019

Author: John McVeigh

Introduction

Software engineering is a rather peculiar profession. A good chunk of our time is spent solving logistical problems and engineering systems, but the ways in which we design and organize our solutions involves a tremendous amount of creativity. And through these creative processes, we often take our working “bad” code and magically transform it into “good”, clean code. I’ve been giving this process a lot of thought lately, so to pique my interest I decided to pick up Clean Code by Robert Martin. Robert Martin is a prolific developer and writer. He has worked on literally hundreds of software projects over the past 40 years and has authored several “landmark” books on various topics like C++ and OO programming. Clean Code provides a cohesive set of practices for developing and maintaining readable code. I’d now like to share some of his most impactful ideas and delve into a few of his thoughts on functions, the “blood” of our code. In doing so, I hope to provide you with a fresh perspective on code design that can applied to your everyday problems.

Disclaimer

Like any art form, there are several different schools of thoughts, many of which we may not entirely agree with. The principles defined in Clean Code fall into just one of these schools. As aspiring masters of our craft, it would be a pity to limit our scope to only one such school. Even if we do not agree with a practice of a particular school, through understanding, we will either strengthen our own beliefs or adopt new ones.

What is Clean Code?

“You know you are working on clean code when each routine you read turns out to be pretty much what you expected. You can call it beautiful code when the code also makes it look like the language was made for the problem.” – Ward Cunningham

Ward’s definition really resonates with me. When you feel as though the author has carefully planned for almost all of your anticipated questions and desires about the code you are working on, it becomes nearly effortless to understand and expand.

Generally, most consider clean code to be:

  • Readable
  • Elegant
  • A vehicle for clear intentions
  • crisp abstraction
  • Easy for others to enhance
  • Cared for
  • Tested code

Why do We Want Clean Code?

We typically spend well over 10 times more time reading code than writing it; “making it easy to read actually makes it easier to write.” This is largely due to the fact that 80% of our time is spent “maintaining” existing code. As we all know, messy code significantly slows down our productivity. Changing it often breaks code in other places and we typically find ourselves cornered with using solutions that leave it in an even worse state. The most concerning issue of all is that once some messy code makes its way into a code base, it can spread like a virus. As the messy modules grow, other systems are left conforming to the “hacks” it imposes and before too long, you’re left with one giant rotting mess. Therefore, it is essential for us to proactively “leave the campground cleaner than we found it.” Robert Martin calls this the “Boy Scout Rule.” Following this simple rule doesn’t require anything big, just enough to keep things steadily moving in the right direction. A code base will never rot if we follow this simple rule.

A growing mess causing productivity to decrease, asymptotically approaching zero

Clean Functions

Keep Them Small

The first rule of functions is to keep them small, states Robert Martin. The second rule is that they should be smaller than that. Small functions are necessary for telling stories (more on this later), increasing transparency, and providing a compelling order. After decades spent creating everything from 3,000 line monsters to short 20 line functions and everything in between, Robert Martin believes that functions should rarely need to be more than 20 lines long. In situations where I needed to work with two functions that were equally cared for but had differentiating lengths, I too found this to be the case. I need to spend extra time in the details of a longer function to understand the intent of the underlying behavior.

Do One Thing

“FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY.”

This practice really resonates with me. Functions that simply do the one thing that their name declares are typically easy to follow and understand. The hardest aspect of this practice is distinguishing whether a function truly does just one thing. A simple way to test your functions is to develop a “TO paragraph” with the following structure:

TO <function-name> we <do steps>

If your function can do all of its steps at one level of abstraction below itself, then it is doing one thing. Another way to check if a function is doing more than one thing is to ask whether another function can be created from it with a name that is not merely a restatement of its implementation. For example,

Original game update loop

void c_game::update_loop(float delta_time){     c_input *input= get_user_input();     if (input->is_key_down(_key_space))     {         m_player->shoot();     }     else if (input->is_key_down(_key_escape))     {         quit_game();     }     // continued…      m_player->update(delta_time);     update_projectiles(delta_time);     // continued…      c_render_surface render_surface= new c_render_surface(k_viewport_aspect_ratio);     render_surface->draw(m_player->get_current_sprite(), m_player->get_position());     for ( int i= 0; i < m_projectiles.length(); ++i)     {         render_surface->draw(m_projectile[i]->get_current_sprite(),             m_projectile[i]->get_position());     }     // continued…}

In this case it would read:

TO update the game we get user input, if the space key is down then we shoot, then we check to see if the escape key is down, if so we quit the game…

We’re obviously working deeper than one level of abstraction here. By breaking up the function into smaller ones we now make the intent of this function apparent. We can now also operate on one level of abstraction below the function under inspection.

Refactored game update loop

void c_game::update_loop(float delta_time){     handle_user_input();     update_game_state(delta_time);     render();}

One Level of Abstraction per Function

Functions that operate on varying levels of abstractions are often confusing; readers may find it difficult to distinguish essential concepts from details. In the original version of the previous example, m_player->update() is a high-level concept, c_render_surface is an intermediate level of abstraction, and k_viewport_aspect_ratio is a lower-level concept.

Once details are tangled together with essential concepts, more and more details will often find themselves building up within these functions.

Tell a Story

Code that does what we expect it to often reads like a top-down narrative; it begins at the highest conceptual level and works its way down to the details. We want to read the program like a set of TO paragraphs that describe the current abstraction level and reference subsequent TO paragraphs at the next level down. The refactored version of the previous example would read something like:

TO update the game we handle user input, update game state, and render the game.

TO handle user input we get user input, check if the space key is down, if so then we…
TO get user input we…
TO update game state we update the player and all of the projectiles.
TO update the player we…

Robert Martin refers to this as “The Stepdown Rule”. Applying this practice is key to keeping functions short and making sure they do just one thing.

Don’t Repeat Yourself

Repeated code makes code bases much more difficult to maintain. Since the dawn of the subroutine, programming languages have been trying to provide us with ways to efficiently eliminate repeated code. Whenever repeated code needs to change, we must update all of the areas where it has been duplicated. This increases the chance for error and adds more overhead to our workflow.

How do You Write Functions Like This?

Like any other form of writing, you start out with a rough draft. The rough draft serves as a cheap way to ensure that the core desires of our work are being met. When writing a novel for example, this might mean that the content aligns with the theme and plot. When writing code you typically desire logical results like fixing a bug while still passing all of your verification tests. Therefore, there is no real benefit to writing expensive ship-ready code from the start. The effort you would put into making your code clean will often need to be scrapped when you realize that your code is failing to deliver the logical results you desire. Once your core desires have been fulfilled, you can simply iterate on it until it becomes squeaky clean.

Where to Go From Here?

The rest of the book covers a wide set of programming topics including things like classes, unit tests, and comments. It is divided into three parts:

1. Principles, patterns, and practices of writing clean code

2. Case studies that exercise the techniques provided in part one

3. A single chapter containing a list of heuristics and smells gathered while creating the case studies in part two

This book requires a bit of work to get through all of the code and exercises, but I think it is worth it. By taking the time to exercise the provided techniques, you will understand the context in which the heuristics were applied and written; this is invaluable.

I hope that techniques like these will make your code design process more enjoyable by being less magical and more concrete.

leave a comment