Monday, 4 May 2015

Kata - Part 2: In which speed becomes the primary focus

Pursuant to part 1, this is the story of how the speed-run kind of got out of hand.

So I was chasing this time: 12:44. It was faster than I'd ever been, and... there was that damn smiley-face mocking me.

I sat down and thought about it for a while. The first step of optimising for speed is simply this:

Do less.

So I did -- using the tooling at my fingertips, learning more about what I could get it to do, trusting in the R# completion a bit more (which I used to trust only when I couldn't be bothered to type something out in full or when I wanted to take advantage of the automatic library inclusions) and figuring out how I could write my code with the least keystrokes -- with the proviso that the code still had to be something I liked. That means no single-character variable names or stupid test names. My tests must still describe what they do -- they still have to follow the template

{MethodName}_Given{Input}_Should{ReturnSomething or HaveSomeEffect}

My implementation should still be pleasurable to read. I'm an advocate of the idea that good code doesn't need comments because it already reads like a story, conveying the author's intents to the reader whilst simultaneously allowing the compiler to convey the author's intents to the host machine.

I also still had to follow TDD: write a test, test must fail for the correct reasons, write simplest implementation, test should pass, optionally refactor if I can think of a better way to write it. It's important to note the "optionally" part of this last step: when optimising for speed, I don't want to compromise code quality, so I try to write the "best" code first. I do think that I can write prettier code (and I have, for the same kata), but I have allowed simpler code where that would shave seconds off of the final time.

Of course, the next place you get to after "do less" is quite simply "get faster". Whilst engaging in that cycle though, being aware of ways you could "do less" whilst still maintaining the parameters of the assignment becomes vital: sometimes getting faster at a particular unit of work uncovers how you could do less there (or elsewhere). It's quite a lot like optimising code for execution times.

My speed-run implementations centered around an extension-method approach, which I think provides a level of readability to the final result. Extension methods are, IMO, one of the best features of .NET and I use them in production to make code read better -- code which reads better is easier to maintain and has less ramp-up time for the new developer who invariably is saddled with the troubles of coders past.

I slowly whittled away at my 13:48:
10:42...
09:16...
08:48...
and, this morning:
07:35.

Now bear in mind that Katarai does give me a little time boost here: the system bootstraps a solution with a shell test fixture and a shell implementation which has the Add method throwing NotImplementedException. So if I were to do this bare-bones, I'd expect probably another 30 seconds for creation of those files. In other words, I'd like to recognise that this isn't the time required to write every character of code in the outcome. On the other hand, I also make mistakes in this run, so I could theoretically go faster -- though I'm not sure if there's any point in trying to do so (and I also said that when I did 10:42, so... yeah...).

You can view the kata here: StringKata 2015-05-04, completed in 07:35 if you'd like.

I had to tweak my whole way of doing things to get down to this time, including
  • moving my taskbar to the right so that Katarai wouldn't overlay the area I was working on
  • learning to trust in R#'s code completion more
  • using R#'s CamelCase feature
  • learning the requirements for the kata so intrinsically that, in this particular run, I actually "forgot" where I was in the whole process, but somehow continued to write the next correct test. Weird.
The question, of course, is what the value is of completing a kata in this kind of time. I'd argue that a lot of the value of the kata is lost once you've got to the point of knowing the steps and your counter-argument implementation iterations so well. I'm not thinking about a new way to complete the problem. I'm not critically analysing my thought processes around the problem or TDD. I'm blindly banging it out... Write test!... Run all!... Write code!... Run all!... Write test!... Run all!... Write code...! Lather! Rinse! Repeat!.

I had actually argued that I thought I wasn't gaining anything once I got past around 12 minutes, until a conversation with Chris Ainslie this morning. He raised the same question: "Once you're going that fast, is there anything to gain from the kata?". In talking it out though, I can attest that there is something to gain in a pure speed-run, much like there might be something to gain through placing another arbitrary constraint on the problem, a practice which is sometimes used to stretch the kata or make the process a little more mindful. Constraints that I have tried for a kata include:

  • No mouse (this will get you familiar with your keyboard shortcuts, for sure)
  • Implementation has no variables or state or loops. LINQ is allowed. Logic is allowed, through the terniary operator only. All member methods start with the return keyword (I find the result from this constraint set particularly pleasing and it's the basis for my speed-run result, though being pure on this does take a little longer).
  • Implementation makes use of no LINQ or inbuilt string functions (yes, write your own Split! Just like grandpa used to! *ahem* I mean, just I like I used to, back in the days of C)
By making the constraint the rather open-ended "shortest time possible", I have gleaned the following:
  • Learned more about my tooling (I've mentioned R# and code completion, for example)
  • Practice typing for speed and practice reducing typos when typing at speed!
  • Learn to evaluate a prior test as a possible candidate for a shell for a new test which can be modified and used in a shorter time than writing the test from scratch
  • Enforcing a strict adherence to readable code, even when time is tight
  • Enforcing a strict adherence to TDD, even when time is tight
  • You can always get a little better; always go a little faster. Translated to production code, this makes me more critical of the code that I am writing -- could I have done it more succinctly (but still make it read like a story)? Have I done the smallest thing possible or am I gold-plating? Am I implementing features which aren't (at least not yet) actually required? This last one trips us all up: we all too easily get into a mindset of trying to produce perfect code instead of great code which can be extended on requirement.
At the very least, this video should suppress the argument that I have heard that doing a String Kata in 20 minutes is a ridiculous requirement. If I can do it in 07:35, so can you -- indeed, I bet this is still far from the floor of times that are possible for this kata.

No comments:

Post a Comment

Everything sucks. And that's OK.

There is no perfect code, no perfect language, no perfect framework or methodology. Everything is, in some way, flawed. This realisati...