Ruby, OOP, TDD, Remote Work, Miscellany

There’s been more than a little attention devoted lately to the value of TDD in software development, driven in large part by a fewcriticalblog posts by DHH.

I disagree with most of what’s said in those posts, but that’s not why I’m writing this. Others have already responded to the details of his arguments, for example the purported sacrifice of code clarity for testability, far better than I could. In particular, I can highly recommend Uncle Bob’s and Gary Bernhardt’s responses.

But my personal experiences with TDD have highlighted a benefit of the technique that hasn’t received much attention in the last few weeks’ discussion, so I thought it worthwhile to share some of my experiences.

TDD and Working Memory

I’ll begin with a little bit of background: I have a 14-month-old son who has yet to sleep through the night. This means that, with only a handfull of exceptions made possible by heroic efforts on the part of my partner, I haven’t had a full night’s sleep in over a year.

The effects of chronic sleep deprivation are myriad, but most notable to me in my day-to-day life as a software developer is that my working memory is shot to shit.

While I don’t really have much more difficulty remembering facts, or marshalling my skills and experience to solve a problem, my ability to hold information relevant to the task at hand in my mind while I work is greatly reduced. This makes it quite a bit more difficult to implement any remotely non-trivial software system, since a big part of that process is maintaining at least a partial representation of the whole system in your mind as you work through building out and composing its individual parts.

This is where TDD comes in. As Katrina Owen astutely points out in her excellent talk Therapeutic Refactoring, refactoring with tests makes you smarter:

You offload a bunch of those little details, that under normal circumstances go into working memory, into your tests. Once you start refactoring, you start reclaiming your brain.

TDD greatly reduces the demands that developing software places on your working memory, since you’re in effect planting signposts for yourself as you go along, each one showing you the next step you need to take.

Perfectly Spherical Programmers in a Vacuum

I get it. You’re smart. You’re a good, nay a great programmer. On a good day, when your brain is firing on all cylinders, you can easily compose a complex subsystem without writing anything down, holding the whole thing in working memory as you effortlessly hop between collaborators, building out their integrations and implementations. Me too.

But even you, and definitely I, have lots of days that aren’t so good. Sleep deprivation is only one of many possible reasons you might suffer from a temporary deficit of working memory. You could be distracted and worrying about a situation in your home or work life that has no bearing on the task at hand. You could be uncomfortable, too hot, too cold, hungry, sick, thirsty, upset, overfull, or god forbid hungover.

And in those situations, on those days, benefitting from what Katrina cleverly calls an exobrain is a life saver. You have a series of small, fast tests that have led you down the path of implementing your system right to the point you’re at right now, in this second, deciding whether you need to write > or < in this conditional. So if you lose your train of thought or something slips your mind because you’re not quite 100% today, don’t worry. “This is still red. Why is this red? Oh, I need to X.” And you’re right back at it.

It’s no hyperbole for me to say that there’s no way I could have kept myself employed over the past year without the help of this exobrain.

I truly believe that TDD helps you write better, less tightly coupled software. I believe that it protects you from bugs and regressions, and that it can be a huge help in refactoring your code to be more expressive and readable. But for me, this past year, I would still have practiced TDD even if none of those things were true. Because TDD is a reliable methodology for producing code that does what it’s supposed to do. Because if you have sufficient knowledge of the language you’re working in, the patterns of software design, and the domain of your application, you can apply that process and make good software even on the kind of day where you have a hard time making breakfast.

And on days when you are your very best, you still win. That extra working memory you now have available can be used to think about the high-level design of your system, the expresiveness of the method you’re writing, et cetera. Because you’ve grown accustomed to not trying to remember everything about the class you’re working on and its collaborators, because that information is in your tests, you’re free to use that extra mental RAM on more rewarding efforts.

Keep your Skills Sharp for When your Brain Isn’t

Based on my experience this year, I now see this as one of the key benefits of TDD: it allows you to keep operating at a very nearly optimal performance level under much less than optimal circumstances. It’s like a redundant backup system for your ability to do your job.

Even if you don’t plan on having children, (or if you’re one of those horrible people whose children sleep at night), there’s a good chance you’ll hit a point in your life when you’re not your best, sometimes for weeks or months on end. Even if you’ve tried TDD and thought it wasn’t for you, even if you feel like you “don’t need it”, I’d urge you to get a bit more practice with it, because it just might come in handy someday if your brain lets you down.

I came across a particularly tricky issue with one of my integration tests using capybara and poltergeist this week, so I thought I’d write it up for the benefit of anyone else encountering a similar problem.

The Issue

A key step in the workflow under test was clicking on a “Publish” button located at the bottom of the page. This test had been passing for some time, but started failing when we added a floating toolbar to the site.

The toolbar was of the type that appears as part of the document layout until the user scrolls past it, at which point it attaches itself to the top of the viewport.

When scrolling past the toolbar, the position attribute is switched from static to fixed. Because this yanks the content in the toolbar out of the document flow, it causes the rest of the page to jump upwards to fill the now-empty space, so we apply padding to the header equal to the toolbar’s height to compensate.

With this code in place, the test started failing. Some quick debugging showed that the click on the publish button just wasn’t happening (no click events were fired). Usually, if Capybara can’t find the element you’ve told it to click on, it will throw an error, but in this case it was just failing silently. Remove the fixed toolbar, test goes green again.

The Solution

The key insight towards solving this issue came when I added debug code to the scroll event handler shown above. The page was actually scrolling during the execution of the test.

When Poltergeist clicks on an element, rather than generating a DOM click event, it actually generates a “proper” click. This is much closer to what happens when a real user clicks on the page – but it means that Poltergeist must scroll the page to where the element is, and work out the correct co-ordinates to click. If the element is covered up by another element, the click will fail (this is a good thing – because your user won’t be able to click a covered up element either).

The problem arises because of the way the fixed toolbar is implemented: first it pulls the toolbar out of the document flow, then it compensates with padding.

This results in two page redraws, and an imperceptibly fast jitter in the position of elements on the page. But since poltergeist is operating much faster than a human with a mouse, the time delay between scrolling the page and clicking the button (or, more accurately, clicking the coordinates where it expects the button to be) is negligible. As a result, it clicks while the page is jumping and misses the button.

There are a number of possible fixes here. It’s possible to tell Poltergeist to just trigger a click event on the element rather than scrolling to it and clicking on its coordinates:

1

find_button("Save").trigger('click')

Instead of:

1

click_button"Save"

This solves the problem, but does away with one of the main benefits of using Poltergeist in the first place: it directly emulates the behavior of a user navigation the site. If an element is covered up by something else, Poltergeist will be unable to click on it, just like a user.

The solution I ended up with was to change the implementation: rather than apply CSS to two different elements in quick succession, I apply a class to the page body. Because the padding is variable and needs to be toggled on and off in CSS only, I moved it to a shim element that’s hidden by default. The body class both applies fixed position and unhides the shim:

Because it only redraws the page once, this version doesn’t cause content to jump around and allows Poltergeist to click on the button without issue. Although there’s no perceptible difference when viewing the page, it is actually requiring the browser to do a bit less work, so it’s even hypothetically possible that on slow hardware a user might see the benefit as well.

Summary

When using Poltergeist in combination with any JavaScript code that triggers off of scroll events, it’s important to keep in mind that Poltergeist will scroll the page when required to get to an element you’ve asked it to interact with. This isn’t a bug, it’s a feature: it allows for a closer emulation of a user interacting with your site, and might enable you to pick up issues that other drivers wouldn’t surface.

However, if you’re modifying the page in response to scroll events, you run the risk of introducing subtle race conditions like the one I’ve described. In this particular case, there was a solution that improved both the performance of the page and the test behavior.

A few days ago a coworker of mine ordered a glass of beer in a pub while waiting for some pizzas to bring back to the office. After taking a big gulp, he immediately felt a burning sensation in his mouth and throat, and knew something was wrong. It turned out that the pub had recently cleaned the beer tap lines with a potent cleaning fluid, and it hadn’t all been flushed out. The result was that my friend spent a night in the hospital, and was unable to eat anything for over a day due to the swelling and pain in his throat. It’s still not totally clear what his path to recovery will be like.

When I told the story to another friend, his reaction was “Wow. It’s almost like, as a barman, what’s the worst mistake I’m empowered to make?” And it’s true. Quite literally, there’s not a thing you can do in the execution of a bartender’s job that is worse than serving a customer highly caustic cleaning fluid in their drink.

And that got me thinking. What’s the worst mistake I’m empowered to make? Take the site down? Nope, pretty easy to have it back up in a few minutes, little harm done. Commit a bug to production? It would have to be a whopper. Really, the worst thing I think I’m empowered to do is lose data. If I break something such that, even if we recover, we’ve lost some user data, that’s deadly. I can’t think of any other single misstep that would be more damaging to the company.

As soon as I came up with that answer, I realized I actually spend quite a lot of time thinking about it. Ever since the first time I shelled in to a production server, and not infrequently since, this little voice at the back of my head asks: Are you about to hose any data? Every time I’m running complex migrations, or implement a new feature that alters data in a major why, it’s always there: Don’t do the wost thing you could possibly do.

Let me be clear: avoiding catastrophic fuckups isn’t what I consider the hallmark of a great software developer, or a great anything for that matter. To be great, you need to succeed greatly, not simply avoid failure. But coming back to the example of the pub: this was a really good pub. Fantastic selection of craft beers, best pizzas in the area by a wide margin, and a very welcoming atmosphere. But I still won’t be going back there, because, well, obviously: they poisoned a friend of mine.

So it’s probably worth spending a little bit of time, every once in awhile, asking yourself what the worst possible mistake you’re empowered to make is. And putting in place whatever safeguards seem reasonable to stop yourself from making it.

I thought I’d start posting quarterly breakdowns of the books I’ve been reading, partly just to share stuff I’ve been enjoying, but mostly to shame myself into reading more my holding myself publicly accountable.

Here’s the first edition. Since the start of the year, I’ve finished four books. Pretty weak, I admit, but I’ve also had a child, so that’s my excuse:

Glasshouse, by Charles Stross. Possibly my new all-time favourite science fiction novel. Filled to bursting with ideas, it manages to create a plausible post-scarcity, post-”singularity” universe and a hugely compelling story at the same time. Can’t recommend it highly enough.

How Much Is Enough? Money And The Good Life, by Robert and Edward Sidelsky. A philosophical examination of modern Western society’s organisation, questioning how little of the gains in economic productivity of the last 50 years have gone towards improving average standards of living, framed in terms of “the good life”. The book was often overly academic in tone, but it drew on some interesting research and raised some (I think) very pertinent questions.

Carbon Zero: Imagining Cities that can Save the Planet, by Alex Steffen. A short book, available for free online in full, examining the role that urbanism can (and must) play in staving off the worst of the climate crisis. Steffen’s argument is compelling: converting the entire automobile fleet to hybrid or electric vehicles would take decades even if every car sold today were such a model, and likewise replacing the entire power generation grid with renewables is an enormous undertaking. But with the coming boom in urban development across the world, there’s real potential to make drastic reductions in carbon emissions with innovations in walkable urbanism, transport, and building technology.

The Last Policeman, by Ben Winters. (Currently for sale for $2.99 on the Kindle store.) A fun noir detective story, with the clever twist that it’s set in a world where a giant asteroid is months away from colliding with Earth and ending civilization as we know it. It took me a while to get into this one, as the noir tropes and first person narration felt a bit over the top, though in retrospect it’s clear that was the intent. All in all, it was a captivating mystery, and I’ve pre-ordered the next book in the trilogy, due to be released in July.

The problem is this: Postgres sets up foreign key constraints as “system triggers,” which can only be removed by a superuser. When database_cleaner tries to empty out your database between test runs, it tries to remove these keys, which causes the failure.

Currently there isn’t a “real” fix for this, but Sergey Potapov has bundled together the required hacks into a gem called rails3_pg_deferred_constraints. Pop it into your test group and you should be good to go.

When everyone else’s git commits start showing up as “in the future,” you know something’s wrong.

It’s probably that your VM, having been suspended while your host OS was sleeping, has lost track of what time it is. You could/etc/init.d/networking restart every time you start work in the morning, but that’s a drag, and you’re likely to forget.

Instead, you want to use the NTP daemon to keep your clock in sync. By default, however, NTP will attempt to gradually and incrementally bring your clock back into sync, and will balk at large differences in time (like the 14 hours since you last opened your laptop). Fortunately, there’s a config setting for that.

The important line there is tinker panic 0, which is what tells NTP not to freak out if the difference in time between the local clock and the server is larger than it would normally be comfortable with. This line needs to come before all other config directives.

With that config in place, fire off a quick

sudo /etc/init.d/ntp restart

and you’re in business. Next time you bring your VM back up from a deep slumber, hit date and you’ll be pleasantly surprised.

They alone, therefore, of all the citizens are forbidden to touch or handle silver or gold; they must not come under the same roof as them, nor wear them as ornaments, nor drink from vessels made of them. Upon this their safety and that of the state depends. If they acquire private property in land, houses, or money, they will become farmers and men of business instead of Guardians, and harsh tyrants instead of partners in their dealings with their fellow citizens, with whom they will live on terms of mutual hatred and suspicion; they will be more afraid of internal revolt than external attack, and be heading fast for destruction that will overwhelm the whole community.Plato, The Republic

A great piece of satire about mainstream media science reporting from Martin Robbins’ The Lay Scientist over at The Guardian.

Full of gems like:

This paragraph elaborates on the claim, adding weasel-words like “the scientists say” to shift responsibility for establishing the likely truth or accuracy of the research findings on to absolutely anybody else but me, the journalist.