I love learning about economics - especially behaviorial economics - so of course, I love the Freakonomics podcast & blog and such. One of the really awesome things that they’ve covered several times is England’s “nudge” unit, which uses small changes to a process to try and create gradual but substantial change. It’s based on a really cool branch of behavioral economics that shows how really tiny changes (which they call nudges) can be wildly influential. Things like having a place to sign a letter without actually needing to sign or send the letter back in significantly increases the chances that someone will actually read a letter that’s sent to them by their government, and that means they’ll hopefully get some important information from that communication.
Software engineers are in the nudge business, whether they like it or not. We create applications that influence the behavior of our users, and the choices we make in creating those applications can deeply affect the world. Sometime’s it’s explicit, like when Facebook spends millions of dollars to try and change their UX to increase the time a user spends in the app. But more often than not, there are things that we’re doing that are subtly changing the ways users interact with our products, and potentially how they interact with the world.
And we’re also nudging the other engineers working on a project (including our future selves). We’re subtly influencing future development in our applications by the choices that we make, and if we think about this a little deeper we can use some principals of economics to try and encourage better design and testing practices!
One thing I was talking about with some folks at RubyConf last week (which was AWESOME by the way) was about how to encourage better testing practices. Well, it was actually more about how to discourage bad testing practices that can lead to highly coupled design, like using RSpec’s
allow_any_instance_of and the related
any_instance_of methods. Right now it’s just as easy to use that method as to use any other method of testing, and when you have a particular problem that you think requires
allow_any_instance_of, there’s frequently a way to avoid it - it’s just usualy way harder than using
allow_any_instance_of. The cost of that method is just too low, so of course the market (ourselves and our fellow engineers) are buying it (using it) because the immediate benefits outweigh the immediate cost. That shortsightedness is just about everywhere, and there’s really no way to avoid it other than a more steely constituion. And then we’re frequently stuck with that test, and more importantly that design, and the long term costs of those less-than-great choices add up.
So, how can we make
allow_any_instance_of cost more? Well, “cost” can come in many different forms. It can be money, or time (which is basically money), or social status, or various other things. Now while I wouldn’t actually want to see this happen, don’t you think that you’d be less likely to use that method of testing if the RSpec team pulled all those sorts of things out into something like RSpec Pro that you had to pay for?
But that’s a terrible idea. There’s got to be something better. How about costing some time? I hate slow test suites, so how about some sort of exponential backoff for every use of those methods? Sometimes you just might really need those methods (like if you’re trying to assert messages are sent to a controller instance in a controller test), so you don’t want it to hurt too bad to use them just a few times, but I would propose that after, say, 5 uses of that method, it just starts inserting
sleep statements in the method somewhere to slow your test suite down. So, for tests 5-10 that use
allow_any_instance_of you could put in
sleep 0.5, and then for the next five
sleep 1, and then
sleep 2, and so on. As you relied more and more on things that you really should avoid, the immediate pain would significantly increase, and hopefully “nudge” you into using a different - and better - method of testing that behavior.
While that actual example might not be a really great one, there are some already out there that highlight how we can use these economic theories to our advantages! One of the best ones I know of is the
good_migrations gem from Test Double. Basically, we know that we shouldn’t actually use ActiveRecord models in migrations, but it’s trivally easy to do so. The cost is too low, and we need to add some cost to that process to more accurately reflect the long term cost you’ll pay. So with this gem, it prevents the Rails auto loader from loading anything in the
app/ directory during a migration, meaning that you can’t take advantage of the auto loader. It’s not impossible to use your
app/ code since you can manually
require those files, but it nudges you in the direction of good behavior by making the bad thing harder. It’s a really great idea, and one I think deserves more concious consideration from many developers.