Ease. We should have it. What should we do when we haven’t?
Ideas, realizations, come to us at odd times. This morning, it was time to refill the ice trays. I had used the last loose cubes to make my chai, so it was time to dump the ice trays into the bag, and then refill them. I filled them, stacked them, opened the freezer door down below the fridge door, and gracefully, smoothly, and with ease, put the trays in the freezer without pause and without spilling a drop.
OK, yeah, not exactly juggling 7 flaming torches, but I noticed how smoothly my aging old bones did this simple task and it got me started thinking about ease and grace in programming.
I immediately remembered a old quote: “It was hard to write, it should be hard to read”, a famous, hopefully fake quotation from some programmer somewhere. I found myself imagining that that programmer worked for me and said that in my hearing, and that I might take them aside and urge them to seek employment elsewhere. Ah, fantasy.
But here’s the deal. When some code is hard to write, we owe it to ourselves to figure out why and make it easy to write. Whether a given bit of code was hard to write or not, we owe it to our future selves, and to our colleagues, to make it easy to read.
Here in the Dungeon
Sometimes, here in the dungeon, things are easy to code. Sometimes they are not. Yesterday, for a while, they were not, and since yesterday wasn’t long ago, I actually remember why. I’d been thinking and experimenting for a couple of days with ways to express the kind of Decor and Loot that goes into a level. I was working on a path where correlated arrays of Decors and Loots and whatnot would be zipped together to create an array of key-value tables custom made for a new creation method driven from just those tables.
Yesterday, starting to write tests for this idea, I found myself struggling. I didn’t want to type in tables like that. Now, I had told myself that creating them manually wasn’t a problem, because in the imaginary final implementation, the arrays would be built by some imaginary tool that the imaginary Level Designers would use. And I did have some very nice zipping code working in a Spike.
I kept thinking about how to conveniently test the Decor construction, and while I wasn’t exactly struggling, I was thinking hard and moving slowly. It wasn’t the smooth, graceful, ease that I experienced this morning with the ice cube trays.
One possibility, I suppose, is that I should give up programming and take up a profession of door-to-door ice cube tray filler, but I think a better path is to think about ease in programming, what it tells us when we have it—and when we don’t.
Ease, With and Without
One notion stands out for me. When I can readily write TDD tests for what I’m doing, moving in small steps, making each test run before doing the next, I almost invariably have a sense of ease. We’ll return below to why, then, I don’t always work that way, because first we’ll list a few things that need to be true for small-step TDD to be possible for me.
First, setting up the test needs to be easy. Some of my Dungeon tests are easy to set up. Create an object, send it messages, ensure that it does what I intend. (More on that coming soon.) Often, the test doesn’t even need enough setup to require the use of the
after CodeaUnit features. Type an example, test to see it fail, type some code, test to see it work, small steps, each one taking at most a few minutes.
Mind you, it isn’t that I always know exactly what the next bit of code is. I know what the next bit of problem is, and it’s easy to write the test to call for the solution, and I know the objects well enough to quickly find the right places to improve to make the test run.
If it’s easy to write the test, that’s because the objects are helping me. The “language” in which I speak to them fits what I’m asking them to do. A counterexample might be a test that required me to set up two, three, or four parallel arrays, because as soon as I set out to do that, I discover what everyone knows, it’s hard to get them all to line up right.
If it’s easy to write the test, that’s because the result is easy to express. It’s difficult to test “the Decor are placed randomly in the Dungeon”. What does that even mean? It’s easy to test “this Decor is scheduled to contain a Health power-up”: we just look.
If it’s easy to write the test, that’s because making the request of the object expresses readily what we’re trying to get done. If we have to send three or four messages to the object under test before we can see whether it has worked, that’s a pretty clear clue that there’s a missing method on that object, quite possibly one that combines those three or four other calls.
If it’s easy to make the test work, that’s because the object itself has access to all the objects it needs to do its job, and because we can see which of those objects may need enhancement to support this new need.
And so on … what we begin to see is that when everything is right, testing and making the tests run is easy … and when things are not right, it’s hard.
So yeah, that’s obvious. And yet, what I for one often do is to fail to correctly interpret that difficulty. Instead of making it easy to do my job, I keep smashing my head against the difficulty until finally I smash through. And often, the result of that work looks about as neat as a concrete wall looks after a hole has been bashed into it: that is, not neat at all.
And, OK, that can happen, but when it does, you’re supposed to patch the wall so neatly that no one can tell it was bashed, even if you have to replace a section of it. And sometimes, in my hurry to get done—and my relief that I made it work at all—instead of cleaning things up to make them neat again, I just back away slowly, hoping that the wall stays up for a while.
But Ron, It Can’t Always Be Easy
Quite possibly you’re right: it can’t always be easy. But that doesn’t mean that if it’s hard, we’ve discovered one of those things that can’t be easy. It’s more than likely that we can make it easier than it is. More often than not, we can actually make it easy. Once in a while, maybe we have to do something hard, but even then, we can almost certainly isolate the hard bits and make the thing easy to use, and make the code as easy as possible to understand.
And that last word: understand. There’s one more thing that makes programming hard, and that’s when we don’t really understand what we want to do, much less how to do it.
I started on the Decor control question with what seemed like a good idea, zipping arrays together to create a control table. It seemed to me to be a good way to isolate the different bits that have to happen: choose which Decors, choose which Loots, choose which pain, choose which Tiles. That led to an interesting spike, and to some code that might be useful someday.
If I had started with a question, I might have done better. I knew we’d probably never work on the Making App to make Dungeon layout easier: I consider it tedious, and I’m not really creating a game, I’m studying incremental iterative development. So I didn’t think about what a Level Designer might really want to do: I jumped straight to an interesting solution.
If, instead, I had started with a test that actually allocated Decor, or something simpler, like Loot, in a controlled way, I might have learned sooner what I learned yesterday: “That’s not how I want to do it”. Then I might have thought sooner about what I did want, and might have come sooner to how to do it.
Let’s Not Over-Generalize
Let’s not go too far with this. We wouldn’t want to conclude that we should have designed the entire Dungeon Experience before starting, nor even that we need to hire a couple of Level Designers and ask them what to do. We are required by the nature of our work not to put long periods of thinking ahead of making progress in our code.
You might prefer efforts where you do get to do lots of analysis, lots of design, before you do lots of code, and then, I suppose, lots of testing. That’s OK by me, but most of the reality of most programmers today is that they are working on a live, released code base, making various changes, and under high pressure to get those changes out. That’s why in these articles, I move quickly from a vague problem to the best solution I can create in a reasonable amount of time … because that’s what we need to be good at.
But Let’s Generalize …
When something seems difficult, I want to assume that it isn’t really difficult: I just don’t understand it yet. So I need to work to make it less difficult, ideally getting all the way to “easy”. And it’s best—I know this, even when I don’t do it—it’s best when I can readily write a small test to try out the idea. That will help me focus on using the thing, not just making it do the thing, and will help me break down what needs to be done into small steps.
I’d like my quote to be:
If it’s hard to write, I’m not done yet. Make it easy to write. It’s always possible.