Currently I’m working on an algorithm for an unbeatable tic-tac-toe AI in node.js. In doing so, I’m implementing the recursive Minimax algorithm to enable the computer to maximize the minimum gain for its human opponent. I decided initially to use node.js so a Backbone client can pass and receive state with the server, and so that HTTP requests could be handled in an asynchronous manner. I’ve also been writing a lot of Javascript recently, so it seemed like a natural choice.
Once I had an implementation I thought might work, I decided to test a POST request to the server to see what best move it generated. I ran into problems when regardless of board state, the server always seemed to return 0 (the index which references the upper-left tic-tac-toe slot). My first inclination was that the asynchronous model of node.js was causing my recursive function calls to be handled out of order. I spent a couple of hours debugging with console.log(), playing around with adding callback parameters to functions and experimenting with forcing synchronous execution. Having decided late at night that wrapping my head around the runtime behavior of node.js for this purpose was an ill-use of my time, I decided I might port my program over to Sinatra. I even got as far as setting up a basic Sinatra app before heading to bed.
The next morning I awoke motivated to at least spend another hour or so seeing if I could debug the existing Express app. I had already written a few unit tests to ensure the trivial initialization behavior of my Board model was as expected. Realizing that I had a series of private functions in my AI model which all accepted a Board instance as a parameter, I refactored these into the Board model itself, and made them part of its public API. I then wrote a unit test just to drive out the recursive base case for a terminal board state, one assertion to check that the function returns correct for a state for a computer victory, and another to ensure expected behavior for a human victory. My initial code following the refactoring looked like this:
Some of you might already be able to tell what’s wrong with this code, but when thinking at the algorithm-level, it’s easy to miss what’s gone askew. So driving the behavior for the minimax() function out through the following mocha describe() function, I saw that for the computer-winning state the test passed, yet for the human-winning state, it did not:
So tracing through the series of function calls in my algorithm, the next function called was the isWinner() function from above. Since I had already refactored it to be public in the Board model, I was able to write the following tests:
So refactoring the code to a point where I could debug it by writing a unit test against it has enabled me to nail down a distinct point of failure in my implementation. Something is wrong with the isWinner() function. Debugging this in the node REPL, I realized what I was branching on was causing unintended behavior, briefly walking through this, here’s what happened:
Javascript syntax, as I realized almost immediately, does not allow for this type of equality checking. So unit testing has now enabled me to completely debug this behavior, and implement the following correct function:
Moreover, unit testing here has saved me a hassle of deploying and creating an entirely new Sinatra app server, and saved me the demoralization of failing to implement a solution the first go-around. Perhaps most important of all, writing unit tests has debunked an incorrect assumption I had regarding node.js, an assumption that would have held in my mind had I ported to Ruby before testing.
One thing that bears mention is that I clearly have not test-driven this application. There is an obvious difference between test-driven development (TDD) and writing unit tests after the fact. On the spectrum of validity in software development methodologies, I have perhaps faithfully executed middling. I would also say that what I’ve done here is uncontroversially superior to what I was on the verge of doing, which is not writing tests at all. Stay tuned for perhaps another post where I document a hard-learned lesson that was solved through test-driven development. If you’d like to read more about TDD, the book I’m currently reading is the canonical Test Driven Development: By Example, written by Kent Beck.
In the meantime, if you have anything to correct and/or add, please feel free to join the conversation on Hacker News, tweet @_kulte or email me at zafriedman at gmail dot com. Furthermore, I will be posting the entire source code for my tic-tac-toe game, both the node.js server as well as the Backbone client, as soon as it’s completed on GitHub.
Let’s face it, great developers can take their pick of jobs right now. These same developers know the value of coding in the open and will want to build up a portfolio of projects they can show off to their friends and potential future employers.
Tom Preston-Warner in Open Source (Almost) Everything
I am 24 years old and fancy myself a very self-aware software developer. I am at the stage in my career where I believe the lion’s share of software developers spend the entirety of their careers, somewhere between competence and proficiency. In Japanese martial arts, there exists the concept of Shuhari, which describes the stages of learning to mastery. Michael Norton gave an excellent talk at Windy City Rails this past summer where he extends the concept of Shuhari to professional software developers. Applied in this context, Shu encompasses novices and advanced beginners, Ha encapsulates those who are competent and those who are proficient, and Ri represents the experts and innovators in our field, people like Michael Feathers, for instance. Ascending from proficiency to expert-level is extremely hard, so hard that most people never do it.
Everyone who gets paid to write software got into doing so for slightly different reasons. For me, it is the acceptance that dedicating myself to a body of work where I will never be done learning means that I will never be bored. So at 24 years old, I can look at this information in two ways. I could be content that I am already at or near the ultimate stage of progression for most software developers at a relatively young age. But doing so would violate first and foremost my personal reason for what I have chosen to devote my life to. It would also violate the premise that I want the benefits bestowed upon the great developers that Tom Preston-Warner refers to. For obvious reasons, I want to be able to take my pick of jobs, and I, without qualification, recognize the value of coding in the open and wish to build up a portfolio of open source contributions. But remember I’m not an expert yet, so what are the action steps?
Let’s take a brief sidestep. I love Backbone. I was a relatively early adopter and I can’t be sure, but I’m reasonably positive my proficiency with it got me my current job. I’ve been wishing to contribute for months now, for all of the reasons I previously mentioned as to why a developer would wish to participate in open source. But I have not. Why? Because I suck at reading other people’s source code. When I first gave it a go at understanding the Backbone source, I thought the annotated source code would make it easy for me to grasp. It did not.
The annotated source of Backbone is beautiful, but I far prefer simply reading the standard source. It’s much more familiar when the comments are above the code in vertical space, and there is something about horizontally aligned comments that my brain actually works less efficiently with.
Personal ProTip
I then moved to reading the source in standard form, and I can grasp what the native Javascript code is doing line-by-line, the assignments, the object creation, but there’s one vital aspect that I missed when just reading the source, and that’s Why? It’s very difficult to understand simply by reading the source what every variable’s and object’s raison d’être is. So I tried something crazy. I started not by reading the source, but with reading the tests.
Let me walk you through an attempt to understand the source for Backbone.Events. We’ll do this twice, first by reading the first few lines of source and the comments, and then once again by reading the first QUnit test written for the Events module.
So from this we can learn a few things:
This is actually superb documentation, and the code is also written very well. Reading the source in this way is extremely helpful for someone wishing to write application-level client-side “programs” using Backbone, and it was helpful for me when I first attempted to do just that. But as Justin James highlights in his blog post 10 tips to go from a beginner to an intermediate developer, if our goal is to learn by looking at senior developers’ code, this blob of source doesn’t really do that much for us.
So how can we do better? Let’s go another direction and navigate from the root directory of Backbone to /test/events.js. And let’s just look at the first QUnit test defined in this file.
This code is very easy to understand. Moreover, I’ve actually learned something about how the system works. So what do we learn by reading this test?
We also can pick up some extra breadcrumbs in regards to coding-style from Jeremy Ashkenas, someone who I would consider to be at least at the expert realm of the earlier Shuhari discussion.
This is admittedly a trivial example, at least compared to what we can learn from reading all of the tests included in the Backbone suite. But if our goal as intermediate programmers is to learn from the experts, I’d argue that we’ve already achieved a more efficient pass through the source code this way than we did in the first example where we jumped feet-first into the executable Javascript. Knowing that object.on binds the calling object to an event (as denoted by the event string) is simply more valuable as a means to accomplish the goal of understanding how the Backbone system is architected. Want proof? Go back and read the bullet points I’ve summarized above for what we learned from reading the source first. Compare what those words meant to you before we read the tests and after. It should click in a way that is much clearer now than before.
My plan for the New Year in furthering my level of mastery in software development is to engage in this practice with open source repositories like Backbone. If you have anything to correct and/or add, please feel free to join the conversation on Hacker News, tweet @_kulte or email me at zafriedman at gmail dot com.