Better Code in 187 Easy Steps

Nikki Graybeal
9 min readJul 25, 2021

My Journey from Hack-n-Slash to Well-Planned, Logic-Led Algorithms

Since I have only (very) recently recovered from my unhealthy attachment to hacking my way through coding challenges, I thought it a good time to reflect on my journey and try to identify some guiding principles that I can refer back to if ever, God forbid, I find myself in the midst of a relapse.

Only a few days ago, I considered myself somewhat of an ‘intuitive’ coder. Hahaha. How naïve I was. When faced with a new challenge, I would often get a flash of an idea, pound out a skeleton of a plan, then jump straight into coding until I ran into trouble, which I avoided just often enough to think my strategy was working.

Overconfidence can only take you so far, as it turns out.

Well, to my dismay, it didn’t work for long. The minute I had to code under any kind of pressure (you know, like a live, timed coding challenge 😖), my ‘intuitive’/code-’til-it-doesn’t-work approach morphed into panicked intellectual flailing and desperately trying out half-baked ideas with even less foundation in logic than usual. Sigh. It wasn’t my most shining moment. But it WAS instructive.

I’ve since coded through several challenges, documenting my process in order to discover where I’m getting stuck and most tempted to resort to hack-n-slash. As a result, I’ve come up with a few good strategies and principles for making sure I have solid logic guiding me instead of just a wild hunch. What follows is that journey, in all it’s messiness, laid bare for all to see.

I’ll take you through 2 coding challenges. I’ll use my former approach on the first, defining some guiding principles of good planning gleaned from my mistakes along the way. Then, I’ll apply those principles to a second challenge so you can see them in action.

Well, here goes. It’s starts off pretty ugly, but if you hang in there, I promise there’s a happy ending in store 😬

Example 1: The I̶n̶t̶u̶i̶t̶i̶v̶e̶ Hack-N-Slash Approach

Challenge: Given a string consisting of 1 or more lower case letters, write a method that returns an integer representing the length of the longest substring that is also a palindrome without using the reverse method.

Well, ok. Let’s just break that down into manageable chunks and show this problem who’s boss.

And now I know what a Gist is 💪

Well, that was easy! Seems a promising start at the very least.

I can stand to flesh this out a little more though…

Ok! Time to get down to business and write some code. How can something that feels so right be wrong?!

Retrospective Commentary: This algorithm isn’t even close to being ready. And now I know that. And I have a rule about it:

YOUR ALGORITHM SHOULD TRANSLATE EASILY INTO CODE

If you have to think through logic while you write code, you need to stop coding and rework your algorithm.

But that’s not what I did here…

oh wait…I need to be able to increment the length of my substring! I need a nested loop.

Ah. that looks better. Maybe I should go back and look at my algorithm…nah. Everything is great! I’m sure it will all work out… 😇

Retrospective Commentary: There are GLARING errors here that I would have caught if only I had a set of rules to code by…

MAKE SURE YOUR CODE MATCHES YOUR ALGORITHM

If it doesn’t, it’s a red flag. Either follow the plan or make a new plan.

AND

TEST EARLY, TEST OFTEN

It would have been soooo easy to quickly check that my call to each_index and slice were working the way I thought they should.

But alas, I did not bother.

Feel free to scroll through to the next RULE at this point. It’s just a long stream of intelectual floundering for awhile…

Ah, shoot. It just dawned on me that’s not gonna work. I’m trying to use an array method on a string 😕 I need to change the string into an array from the get-go…no problem!

Just a slight adjustment, doesn’t really change anything crucial…no need to revisit that algorithm!

Looking good! I see no problems here. Onward!

Hmm…looks like I need to initialize an empty string somewhere…

Oops…gotta initialize that size_arr as well…

And now for the final touch…

Whoop! Let’s run this thang.

😢 Not unexpected, ‘cause this is how I roll. Let’s see what our output really looks like. I’m just gonna throw a p in front of that size_array.max on the last line.

Nil?? well, that’s curious. I didn’t see that coming, for some reason…🤔

Ok! Hopefully, you didn’t look too close at any of that🤦‍♀️

Here’s another rule gleaned from that last unexpected return value ☝️:

ALWAYS KNOW HOW AND WHY YOUR CODE WORKS

If you can’t explain it, you’ve got some thinking to do.

Back to our program…

What the heck is going on here? Maybe there is something more fundamentally wrong with my code?? It may be time for some deeper reflection, I suppose. Time to comb through each line and figure out where I went wrong.

Well, I can get around that problem…I can just subtract 1 from size. That way it’s a non-issue and I don’t have to think too hard 😇

Retrospective Commentary: This is another huge red flag. And hence, another basic principle for writing decent code:

YOU HAVE TO UNDERSTAND THE PROBLEM TO COME UP WITH A GOOD SOLUTION

If your “fix” is solving a problem you don’t understand, you’ve got work to do.

Again, I encourage you to simply scroll through to the end of this challenge. Spoiler: I do end up with a working solution in the end, but it’s a painful ride. Best to not look too close…

Well, that changed nothing 😒

Swapping length and index

Pleaseohpleaseohpleaseohleaseletthiswork 🙏

😭

Time to put p’s everywhere. I’m sure that’s how the pros do it.

Just digging myself a little deeper…

Fat chance. The first one is working though! I’m taking it as a win.

Ok…moving down the line. Yeah, I know, I don’t really know if my “magical fix” worked. Duly noted. Moving on, I say.

Let’s see what reversed has in it. I’ll just throw a p in there and see what happens…

Hmmm, somethings not quite right with reversed. Shocking.

Looks like I need to empty reversed at the end of every iteration of the times method call.

I DID IT!!!! I’m as surprised as anyone.

Nice work — you made it to the end. I hope your scrolling finger is ok.

Although the basic ideas in my original algorithm are still present in my solution above, there are crucial details missing from the logic that tripped me up and took tons of time to hack my way through. Let’s take a look at that poor forgotten algorithm:

By my count there are 8 added method calls or adjustments to the logic of my original algorithm in my solution. Also, I’m pretty sure this isn’t the most elegant solution possible. There has to be a better way to deal with strings of 1 character at the very least. And a triple nested loop? Really??

So, now that we’re all past that catastrophe, I give you my guidelines for writing decent code with efficiency:

MAKE SURE YOUR CODE MATCHES YOUR ALGORITHM

TEST EARLY, TEST OFTEN

YOUR ALGORITHM SHOULD TRANSLATE EASILY INTO CODE

ALWAYS KNOW HOW AND WHY YOUR CODE WORKS

YOU HAVE TO UNDERSTAND THE PROBLEM TO COME UP WITH A GOOD SOLUTION

Alright. Now that we have some rules of the road, let’s see if we can get from point A to point B with a little more grace.

Example 2: The Well-Planned, Logic-Led Approach

Challenge: Given a string, return true if the string can be reconstructed using a substring it contains.

First off, let’s use some kind of framework for problem solving. No need to reinvent the wheel here, PEDAC plus a couple additions that I’ve picked up along the way, will go far to ensuring my process and solution are sound.

As I’m developing my algorithm here, I’m trying to not just use words to explain my thinking to myself, but also to develop a mental model, or picture, of how the various parts move and fit together. This has been a key step for me in getting a handle on complex problems. Although I’m not a natural visualizer, when I make the effort to create a mental picture I come away with a much deeper and enduring grasp of things.

Now that I have the bare bones of a solution in place, I’m just going to look it over and see if I can implement it without having to think about more than syntax. Remember, there is a rule about this:

YOUR ALGORITHM SHOULD TRANSLATE EASILY INTO CODE

This is especially true of Ruby, which virtually reads like plain English.

Let’s break that algorithm down line-by-line.

  • if string size is odd return false
    There’s nothing wrong with this, but I know I’m going to want to write this bit of code in one line, so I like to structure my algorithm the way I intend to structure my code:
  • return false if string size is odd
    That translates directly into the code I want so I have even less to think about when the time comes.
  • iterate over string with index
    Ah, I need to turn string into an array first.
  • turn string into an array called chars
    I like to come up with my variable names when I work out my algorithm instead of waiting until I code. Again, it just means I have less to think about down the line.
  • iterate over chars with index |idx|
    — create a substring from the 1st char through idx
    This is pretty straight forward. I know I can use a range from 0 to idx with the slice method.
    — add substring to itself until it is the same size or greater than the string
    This definitely needs some fleshing out…
    — loop until substring size is greater than or equal to string
    — —add substring to itself
    — compare string and substring
    It looks like a simple ternary operator might work here:
    — if they are eqivalent, return true otherwise, return false

I think it’s time to code! If I’ve really taken the time to think through the problem, I should be able to focus exclusively on syntax. And of course, I have my rules to guide me:

MAKE SURE YOUR CODE MATCHES YOUR ALGORITHM

If I run into any problems in my logic, I can switch gears and go back to my algorithm to make adjustments before trying to code a solution.

Hmmm, substring is an array here. I don’t think that matters for this line of code, but it will when I need to compare them. Back to the algorithm!

And now the code:

Darn. This is going to return a Boolean for every iteration of chars so the method will always return the result of the last iteration. I think I know how to fix this. Back to the algorithm.

Now back to the code:

And the output:

So, here I am and I just realized I’ve broken one of my own rules:

TEST EARLY, TEST OFTEN

What can I say? I’m a slow learner sometimes🤷‍♀️

Lets back waaaay up and pretend that didn’t happen.

Time to code…the first bit and then check to make sure it’s working the way it should!

Output:

This seems to be working…but I need substring as a string, not an array.

Output:

Well, that’s not going to work. And I’m not sure why I thought it would- I’m chalking it up to middle age and a need for more coffee. Obviously, I need to use join instead. But, this does bring us to yet another rule:

ALWAYS KNOW HOW AND WHY YOUR CODE WORKS

Although it’s not possible for me to code without making mistakes like this at this point, when mistakes happen, I can take the opportunity to really understand what will be returned by the methods I choose to use.

Just that tiny, yet crucial change to my algorithm:

Output:

Ah…much better. Ok! Now I’m ready to move on to the next bit and see if my until loop is working.

Output:

Awesome- this also seems to be working as it should. Time to add the Boolean return value.

And matching code:

Output:

Well, it wasn’t a flawless execution of my newly minted principles, but it was a whole lot better than the first challenge!

The only rule that didn’t directly come up in that second challenge was

YOU HAVE TO UNDERSTAND THE PROBLEM TO COME UP WITH A GOOD SOLUTION

and I think that is because understanding the problem was my primary concern throughout the whole process. I think this rule is the most fundamental of them all. If you don’t have a solid grasp on the problem you’re trying to solve, the chances of you veering off into left field are high and the chances of you arriving at a working solution without many such side trips is low.

All in all, this has been a pretty transformative exercise. I’m now crystal clear about what I’m sacrificing by hacking my way to a solution and I’ve more than proven to myself that the more effort and time I put in during the planning phase, the more time I will save in the long run. And now I have rules to help me stay on track. If those rules help someone else avoid coding like an amateur knife thrower, all the better.

--

--