The Quest for Clean Code.

Refactoring Earthy.

Joshua Comeau
7 min readApr 29, 2014

My grandfather took on many jobs in his life, but he was truly passionate about carpentry. Working with wood, metal, and glass, he’d craft intricate clocks, multi-compartmental lockboxes, and ridiculously detailed handmade train models.

When I code — on the best of days — I feel as I imagine my grandpa felt, painstakingly and meticulously sanding, varnishing and polishing a coarse product into a flawless masterpiece. There’s a surprising amount of craftsmanship with coding; when I’ve produced an efficient, stable block of clean, concise code, I almost want to blow the sawdust off my computer, and sit back with a beer to admire my handiwork.

Unfortunately, the real world doesn’t seem to indulge this style of laid-back perfectionism. When I participated in the NASA Space Apps Challenge hackathon I previously blogged about, there was no time to be polishing and tidying up our code. The goal was to produce a working prototype as quickly as possible, no matter how hacked-together or sloppy the codebase. By the end of the weekend we had our MVP, sure, but we also had a mess of spaghetti code. It felt like the 4th quarter of a game of Jenga, just waiting for someone to poke it the wrong way and destroy the whole thing. My grandfather would not approve.

Much like trying to untangle earbuds that have been sitting in the bottom of your knapsack for a while, trying to clean up jumbled, messy code can be arduous. With the pressure of the hackathon gone, I decided to see if I could make some improvements.

The first thing I noticed: my teammates Dominique and Sanborn beat me to the punch. Our once unwieldy main game method had already been abstracted into a handful of convenient methods, and things are structured in a much nicer way than I remembered.

Now, I could have basked vicariously in their glory, but I’m more a fan of first-hand satisfaction. After refamiliarizing myself with the code, I found something to polish.

Initial Prognosis: Convert form-passed data to cookies.

The project we made for this hackathon was Earthy, a name-that-place trivia game that takes advantage of beautiful NASA satellite imagery. The user is posed a total of 6 questions per round, and due to the stateless nature of HTTP, we had to find a way to retain data like the user’s name and current score from question to question.

Initially, because none of us had used sessions or cookies with rails, we went with passed form variables — Every time a view is loaded, the essential info would be populated into hidden form inputs. Then, when the form was submitted, the data would be collected into instance variables that would be passed into the next view’s form, and so on.

So we got it working using this method, but we didn’t like it. There are at least 3 problems with this solution:

  • It’s not secure. Because everything is stored right there, unencrypted, in the source code, the user could just write in whatever they wanted and the code would accept it without hesitation.
  • There’s no persistence. If the user navigates away from the game at any point, their entire history is forever lost in the ether. It requires the user to follow a very rigid, linear path.
  • Perhaps most importantly, it’s downright clumsy. It requires a lot of unnecessary code that feels like a hack-job; there’s no pride to be had in this method.

Thanks to teammates Brock and Sanborn, we learned how to use cookies during the weekend, and migrated some of our data over to using cookies, like the user’s name. Cookies solve all 3 of our problems — they’re encrypted to prevent fiddling, they persist for as long as you want them to, and they lead to cohesive, understandable code. They aren’t without their downsides (they can be disabled), but they represent a massive improvement.

Because we only had 36 hours to get Earthy working, we didn’t spend the time moving everything over to cookies. When I started on my refactoring quest, only one variable was still being form-passed: @counter_passed_array.

@counter_passed_array contains the unique IDs of the questions posed to the user. For example, after 4 questions, the array might look like [22, 4, 17, 13]. When stored in the form, it gets transformed into a string delimited with pipe characters, such as “22|4|17|13".

The purpose of this array is two-fold:

  • Primarily, it is used as a way of measuring how many questions have been posed. Our default is 6 questions per round, so at the start of every round, we check @counter_passed_array’s length to see if it has reached that limit, triggering end-of-round.
  • The other purpose is it holds all of our question IDs. This is used at the end of the round to populate a table that lists the questions asked. Unfortunately, this counter doesn’t tell us whether we got it right or wrong — that duty falls to a cookie called ‘scores’. The scores cookie is a hash with two objects: Our total score, as an integer, and an array of boolean values, representing whether the user got each question right or wrong. At the end of the round, our array is combined with this score data to display the user’s results.

Uh, what? If this sounds overly complicated, that’s because it is. Rather than having all of our related data in one place stored in the same way, we have all kinds of different objects and structures, sprinkled arbitrarily across the code. Trying to make sense of it is a bit like being involved in an Easter egg hunt.

And just like that, my refactoring quest got a little more involved. I made a note to reconsider how I’m storing data. Not wanting to get ahead of myself, though, I made the relatively simple transition of turning @counter_passed_array into a cookie. Curious eyes can see the git diff right here.

That change done, I sat back and started considering how to restructure our data.

The Crazy Cookie Combination Conundrum

After some thought, I opted to create a new hash containing all of my score-related data. This hash would be stored in a cookie called ‘game’, and consist of the following:

  • Player name,
  • Current round,
  • Current round’s score,
  • Total score, and
  • Questions.

Most of these are self-explanatory, and contain a single string or integer. ‘Questions’ is a little more complicated — it is an array full of hashes. Each hash contains one key-value pair, with the key being the question ID, and the value being whether the user got the question right or wrong.

Here’s an example of what a cookie might look like after a couple of rounds:

Why an array of questions? Initially, the plan was to shove all those question key-value pairs into a single hash, rather than each question ID getting its own hash. The trouble is that hashes are unordered: I needed an ordered list of key-value pairs, for the summary page.

Massive Project? Not so much.

I’ll be honest: When I started refactoring all the data into a single cookie, I was expecting trouble. I was completely changing the data structure, which means all the references in the model, controller and views would be out of sync. I figured it would take hours to pin down every last reference and update them to use the new single-cookie system.

Fortunately, Rails has excellent error-handling: Most of the potential issues were caught by Rails and threw an exception, pointing me right to the problem. The few issues that weren’t caught were simple enough to track down and update.

It can be said that you’re never done refactoring. While the term is a bit of a misnomer, the “final result” can be seen here.

Moral of the Story

As far as I can tell, with most things in life, the further you get into a project, the harder it is to make changes. It’s almost as if projects take on a life of their own past a certain point, and all you can do is nudge them along.

I used to make music, and there was this sense that after a certain point, the arrangement felt ‘locked in’ and you could only make subtle changes. Some of this can be because of bad structure, but I think that’s just a catalyst; the real problem is psychological. It can feel daunting to overhaul something you’ve put so much work into.

Happily, at least in this instance, changing the variable structure was painless. It’s empowering to know that psychological barriers are only as daunting as you believe they are.

--

--