visit
I bet at this moment, some of you just close this story with thoughts: What, a Tic-Tac-Toe game? How can one fail to implement such a simple thing? You should be a total newbie, man, not being able to solve such a trivial exercise!
The rest of you, who decided to give me a chance, I invite you to read my story and probably this experience help you to escape similar mistakes in your software development practice and be more careful.Sounds very straightforward, right? The greatest twist here is a time limit. You’re only given a 1 hour to build the MVP, i.e., working terminal-based game for two players. Also, there is an additional list of priorities that clarify and extend the design requirements. Note that they list in the order of decreasing importance.
Photo by on
As soon as I’d read the document with requirements, my thoughts were: Hey, the basic gameplay of a Tic-Tac-Toe game is very trivial so let’s focus on the UX and handling the input at the beginning, and then build the rest of the game logic. I wanted to build something fancy and interactive. Up to this moment, I’ve developed many CLI tools, and simple terminal experience wasn’t enough. So I chose the curses library to make things interactive and convenient. (I know, it could sound strange to talk about “convenience” of a terminal-based interface, but still…)
There is one small question here: How would you debug a program that controls the terminal? Here is a small snippet to explain what I am talking about. The code below shows the simplest curses program to interactively with a breakpoint.
<a href="//medium.com/media/c89a5ae7ab304b81e4cbfede35ef09b0/href">//medium.com/media/c89a5ae7ab304b81e4cbfede35ef09b0/href</a> Looks nice, right? Until you try to run it.Having an interactive UI wasn’t a strict requirement, as you see from the list shown above. Still, this thing drained my attention and a considerable part of the allocated time.When you run this snippet, it not shows you what you could expect. As you have probably already guessed, the curses take control over the terminal session, and when a debugger tries to render its interface, things are .
Not a something you would like to see when running a debugger I’d spent a whole lot of time trying to set things up and deal with a broken debugger. Sure enough, the precious minutes wasted without bringing any value to the developed program. The most interesting thing here is that having an interactive UI wasn’t a strict requirement, as you see from the list shown above. Still, this thing drained my attention and a considerable part of the allocated time.
As you already know, I was running out of time, so I’d tried to focus on the building working solution and put off the refactoring for the very end of the process. After some time, the Game was responsible not only for the gameplay but also rendered the board, including a few hard-coded constants and so on. Without notice, my Game class became responsible for everything.
Ad-hoc solutions and quick patches had led to the avalanche of bad code without time to fix it
Expectation vs Reality from the world of Computer Programming
So as you can guess, I didn’t have enough time to factor out this AllmightyGame class into something more manageable and light-weighted. It becomes a great illustration of instead. Ad-hoc solutions and quick patches had led to the avalanche of bad code without time to fix it. It seems that distinguishing the clean code from the ugly one is a more simple thing than spotting how it slowly slides from one category into another.
Wrong. Even with the simplistic and trivial programs, you need somehow verify that your input generates a valid output. You still need to write some entry point to run your program, and you are going to execute it very often during the development process. These entry points are precisely the thing tests give you.
The tests not only don’t add too much overhead to your code, but they also bring many benefits to the development process, even for pretty simple, toy programsMoreover, writing tests has a significant effect on your API building process: it forces you to decouple modules and untangle pieces of your system. See the snippet below. You need to verify that game logic works, and the most straightforward way to do so is to pass the game state from outside. Then you only need to call your winner checking function and see if it works as expected. Otherwise, you would need to run the game UI, and type turns manually. Even for small game states, it would steal a few seconds of your time on each run, not talking about testing bigger boards. <a href="//medium.com/media/6a660c160bcede64a4715182d2e8d0cc/href">//medium.com/media/6a660c160bcede64a4715182d2e8d0cc/href</a>
Therefore, the tests not only don’t add too much overhead to your code, but they also bring many benefits to the development process, even for pretty simple, toy programs. I believe there is an opposite tendency actually: the tests could even speed up the development process and help to make it better. One should think twice before shrinking testing to save time.
Have you ever had similar experience during your developer’s carrier? Or if you’re an employer, which kind of tasks you prefer to give to your candidates? What do you think about such type of “stress-testing”? Would be glad to hear your thoughts!