Recently, Brent Simmons posted a catalog of some ineffective approaches customers take when lobbying to get their requested changes into his software. One that stands out for me is “surely it must be easy for a developer like you.” Yes, this is a funny one! The customer uses this incredibly loaded language that in one fell swoop expresses ass-kissing and the threat of dismissive contempt should you refuse. Bastards!
But that got me thinking. Surely it must be easy for developers like us, at least most of the time. If it wasn’t, we’d find other work or go nuts under all the pressure. Easy and hard are powerful words in the software world, and convey a lot of meaning – some desirable and some not. Who on your team is management’s favorite programmer? The one who replies “Oh, that’ll be easy,” to every marketing requirement they’re presented with. It doesn’t have to be true. If management thinks that Sarah has an easy time turning all of the company’s ideas into pure profit, then she’s the favorite. So what if she’s working her ass off until 3 in the morning every night to “make it so.”
If you’re honest and admit how difficult something might be, you risk being labeled by management as a can’t-do individual. Some people are pathologically negative about the outlook of a task’s difficulty. Just as Sarah masks the difficulty of the task to make herself look good, her coworker Jenny masks the easiness of the task… to make herself look good. How could this be? Sarah knows she’ll look good because she gets things done. Jenny knows she’ll look good because nobody will be able to understand what it is exactly that she does. If she’s lucky she’ll last many years as the team’s misunderstood genius, until one day management realizes she hasn’t done anything at all. Meanwhile Sarah has been asked to work on every hot project that comes down the line.
Surely there must be a compromise between running yourself ragged to maintain the illusion of effortlessness, and pessimistically shooting down every opportunity that presents itself to you. My proposal? Consider your work and any future work that is offered to you as a “series of time-consuming yet easy tasks.” This strategy allows you to estimate with some accuracy the realistic amount a time a project will take, while still offering management the comforting sense of “easiness” that seals your reputation as a “can-do” person. This series of easy tasks also works as a beautiful motivational and planning tool.
It turns out Brent’s nightmare customer is right. Surely it must be easy. In fact, that’s my new mantra. From here on out I shall let that phrase serve as a reminder to me that my office is an “ease-enabled” environment. I’ve reviewed many of the trendy programming philosophies: extreme programming, pair programming, agile development, etc. They all offer interesting advice, and none of them particularly prescribes taking the whole kit and caboodle. I’ve picked and chosen from the advice that appeals most to me. So I can’t say I’m an “extreme programmer” or that I follow the “agile development process.” I guess I’ll just admit that I’m easy.
Below are a few guidelines that help me maintain an easy programming lifestyle. I hope these resonate with you. Feel free to pick and choose!
The Simplest Correct Solution
Planning for the “inevitable” is almost always a waste of time. If you are building functionality into your product, it better satisfy a current customer requirement, or you’ve got some explaining to do. You’re gambling with your own time, and doing so with terrible odds. The payoff for such squandering needs to be huge; it needs to be a proportional avalanche to the investment you make today. Should you invest 10 hours now on a feature that has a 50% chance of saving you 1000 hours in a year? Maybe. Should you invest 10 hours now on a feature that will be interesting to a niche customer base if they happen to take a spark to your product’s 1.0 release? Absofrickinlutely not.
Why does the payoff have to be so huge? Because it’s probably not going to happen. Planning for the inevitable in software is like planning to win the lottery. Whatever you think your customers or clients will want in the future is wrong. Big companies pay thousands of dollars on focus groups and user testing just to figure out what users want and need right now. How are you going to find out what they’ll want next year?
This fact of life is only depressing until you look at it from the point of view that it gives you virtual carte blanche to do the minimum amount of work required to satisfy today’s requirements. It’s not even laziness. It’s efficiency. Enjoy the extra time. Go for a bike ride. Read a book. Write a blog entry. Take a nap. It’s easy.
I can hear the steam coming out of your ears from halfway around the world. Damnit! You hate it when pinko commies like me tell you it’s foolish to plan for the future. Relax. Of course you should plan. Only, plan for the future that will actually happen! It’s all right (even imperative) that you write code for cases that don’t exist, but be extremely rational in your decisions. Let’s say you’re writing software that manages vehicles and roadways. You’ve got special cases for commercial trucks, passenger vehicles, etc. A good programmer will argue that “vehicle” should be abstracted such that future or overlooked classes of vehicle can be easily adapted to the existing system. Fine – this leads to a good design. But the bad programmer will argue that “vehicle” needs to be suitably abstracted such that it covers hovercraft and spaceships. Then they will argue with you endlessly about the series of eventualities that the software will be unprepared for, if by some chance they should happen. I know – I’ve been the bad programmer. Not on this project, buddy!
Only Work on Easy Tasks
In easy programming, you have permission to be selective in choosing which requirement to tackle next. I suggest you turn your nose up at hard problems, and instead only work on easy ones. Obviously, this is a trick, right? Sort of. You’ll end up with hard tasks on your list, and you have to make those lists disappear from the list. One way is to barrel ahead and complete the hard task, as painful as it may be. The other way is to turn the hard task into a series of easy tasks. Now you’ve got a “lot more TODO items,” but the good news is they’re all easy.
For anybody familiar with the Getting Things Done methodology, working on only easy tasks should be familiar. I developed this approach after reading about the “next actions” strategy of GTD. Basically, tasks that are singular are “actions” while multi-step processes are “projects.” Most of us make the mistake of listing projects on our TODO lists, and projects are overwhelming. The challenge here is to look at your hard tasks and think, “what is the simplest possible task that needs to get done before this project can proceed”? Often you’ll find it comes down to something like “send an email to mailing list asking for help,” or “print out Apple’s documentation on NSProxy.” These are tasks a trained monkey could do – almost.
When I’m at my least motivated, sometimes my “next action” on a particularly hard project is something as simple as “create empty template source files.” Once that’s out of the way, I’m free to move on to the next easy task on my list.
Never Fix a Bug Twice
Lots of programmers make their jobs harder than they need to be by fixing the same problems again and again. They also confuse this ability to fix the bug as a laudable skill, when it’s really a symptom of stupidity. If you find yourself fixing the same kinds of bugs so often that you get good at it, then you’re probably getting more practice than you should be. Why is this bug, or a bug very much like it, coming up again and again?
I used to suffer from the common problem of my shoelaces coming untied. Every day, at least once, I would find myself kneeling down to retie my shoes. I figured this was just the way things went. You walk, your laces come untied, you retie, you walk. Although I had a problem – a recurring problem – I was adept at solving the problem in seconds flat. Then at WWDC in 1998 or so an Apple compiler engineer taught me how to tie my shoes. A very minor variation on the regular shoe-tying knot produces a knot that doesn’t slip and won’t come untied until requested to do so. For the past 8 years I have tied my shoes once per wearing. I changed my process and it eliminated the bug-fixing. The bug fix was the process change.
When bugs come back, it sometimes means the bug wasn’t really fixed correctly the first time. You can do a lot to prevent bug recurrences (shoes untying) by thinking carefully about what it means to fix the bug. Let’s say you get a customer report that your application crashes when an extremely long an nonsensical phrase is entered into the “City” field. You dutifully type or paste the long phrase into your app and, sure enough, it crashes. Using the debugger, you see problem almost immediately. The open source library you use to process text is obviously not too careful about the length or character encoding of its inputs. You see an easy fix and, having the project’s source code, you’re able to fix it in just a few minutes. Months later, a similar but different report comes in. Again, it comes down to this damned open source project! Another assumption made wrong. How many times are you going to go through this process before you treat the problem at the source? The open source library can’t be trusted with unbounded text, and you’re not in a position to review the entire source base correcting problems. You have to adjust your contract with the library. Instead of assuming that it was programmed by people with your same attention to detail and crash prevention, you need to accept its shortcomings and massage the data you feed it. By adding one “sanitizer” function, you can ensure that your data meets the library’s foolish assumptions. Instead of relying on the library to be bug free, you rely on your code to be provocation-free. Now this bug, and others like it, are permanently fixed.
Test Your own Strength
Even after you’ve made a wise choice in permanently fixing a defect, it’s liable to sneak back up on you – often when you’d least expect it to. Unit testing and test-driven development are concepts which I agree with in concept but which have not completely taken over my development process. If you pick just one aspect of your work to apply programmatic testing to, however, it should be in the bug fixing department. By writing a test that first detects your bugs presence, and then (after you fix it) correctly detects its absence, you’ve bought yourself permanent piece of mind. Bugs may come and bugs may go, but you’ll be first to know when your unit test sets off the alarm.
I’ve found that test-driven development can also help a lot when spinning wheels in the “make all tasks easy” phase. Often the easiest possible task that you can do on a project is the one that satisfies your next test. You have to write a complex natural language parsing algorithm encapsulated by an Objective-C object that takes an NSData object containing English paragraphs and returns a tree structure representing the grammatical constructs contained in the text. The first test? “[[MyParser alloc] initWithData:myText] should not be nil”. Phew! That was easy. Didn’t even need a PhD! Next test, please!
Do Hard Things Once
If you hate doing hard things as much as me, then you better make sure that when you’re done grumbling your way through the seemingly endless list of hard tasks made into easy actions, you have something you can reuse. The very fear of having to do this again someday should be enough to encourage a modular design that can be repurposed as needed down the road.
Wait! Doesn’t this conflict with my “don’t bet on the future, sucker” advice? Not at all. Writing modular code that can be reused when needed is nothing like the crystal-ball peering I was so dismissive of. This is planning for the expected, while the other plans for the unexpected. You will probably be asked to do something very similar to this hard task in the future, and if you can’t reuse your existing code, then you’re going to feel like a real fool. Notice that planning for reuse doesn’t require planning for future requirements, it just means leaving a set of tools on your belt that will be useful regardless of future requirements.
By always focusing on how much you really, really, really don’t want to have to do this tedious task again, you are subliminally programming yourself to write a reusable solution that will protect you from such a cruel fate.
Just Say No to Features
Some programmers assume that the way to improve software is to add features. This is true to some extent, but without further clarification, it’s a useless concept. Suppose a Martian lands on Earth and, needing some ground transportation, decides to steal a car. Before doing so he carefully examines the behavior of people and their cars. Turn the wheel to steer. Press the pedals to speed up and slow down. Extend middle finger as appropriate. Periodically add clear and brown liquids. He thinks he’s got it down so he steals a car and speeds off down the road. Things are going great: the wind in his hair, Tom Petty on the radio, not a care in the world when the car putters to a stop. “Aha!” he observes shrewdly, “it’s time for the clear liquid.” Spotting a lake nearby he siphons a full tank’s worth into the car and gears up to speed off again.
Putting “features” into your application is like putting “liquid” into your car. It’s absolutely essential, but picking the wrong kind will prove disastrous.
Instead of asking which features should be added, consider asking which should be taken away. There should be a “feature brainstorm” for the high level requirements. These are the “what your product does” features. Then you trim away everything you can from even those features, say no to absolutely everything else, and ship. Apple took over the portable mp3 player market by producing a device with markedly fewer features than the competion. What does my iPod do? It plays songs and syncs with iTunes. Those are the high level features and everything else is up for negotiation.
Brent’s advice, while useful for customers who want to sweet-talk their way into a personalized fix or feature, also serves as a list of warning signs for developers. When these schemes show up in your inbox, respond cordially but do not be tempted to add useless features. It’s you who will pay for it eventually.
Consumers love easy products! And easy products are, it should come as no surprise, easy to program and support. Fewer features + more sales = retirement. Then you can work on hard stuff if you really want to.
The Mental Tie-Breaker
A lot of the time when I’m stuck or unmotivated it’s not because of a particularly hard problem, it’s because I’ve reduced a hard problem to two equally appealing series of easy steps, but I can’t decide which to choose. The classic dilemma, which path to follow? Sadly, in computer programming the path less traveled is liable to lead to disaster. Except for rare, innovative departures from the norm, succeeding in software is about taking the road most traveled. Design patterns are one example of a method aimed at helping you do this, but they only work if you recognize the synopsis of the pattern in your problem.
When I’m stuck and don’t know how to design some piece of my application, I take comfort in “mental tie-breakers.” The problem of indecision often stems from an underlying statistical tie. Basically, you can see the good and bad in both of your choices, and are worried that there’s more bad in one than you can currently observe. The mental tie-breaker can help you tip the scales in one direction or the other, usually with the weight of some valuable truism that you respect implicitly.
To be really effective, I think you have to develop your own mental tie-breakers. These are personal. They work because they represent your values, and you’ve been stunned to see their value proven in real life. Here are two examples of mine, which can be phrased in the context of that magical “easiness”:
Would a scripter have an easy time writing to this model?
This question often reveals the inherent flaws in your MVC design. If your scripter can’t easily identify the items in your model, then they’re probably being modeled in the wrong way. This trick is amazing because it often reveals surprising flaws like “I can’t believe I stuck all that model behavior in the view.” If you identify design flaws using this tie-breaker, you will have an insanely easy time adding scriptability to your app, and you will be in excellent position to take advantage of the many benefits a separated MVC hierarchy brings.
Which of these approaches yields an easier API?
By thinking of your code as “API code” even if it’s only ever going to be used by your application, you will probably choose better and more robust designs. This question or a variant on it is also useful when iterating over your task list. When deciding how to approach a “hard” problem on your list, it can be helpful to ask “What kind of API would make this problem easy?” Once you answer the question, you no longer have a problem, you have an API (a separate project) to write.
I’m sure this is the tip of the iceberg with easy programming. If you’re of a similar mind and have other tips for making our job the laziest, happiest job on earth, please share them in the comments below.
Update (and incidentally, appropriate to wrapping up): I should have known that I couldn’t mention the miracle shoelace method without at least trying to explain how it works. Well, I really don’t look forward to trying to draw it or even explain it, but fortunately there are sites out there that talk about this kind of stuff, and I was lucky enough to find the knot. This site contains a catalog of shoelacing knots. The one I’m referring to is called the better bow on the site, but will always for me be remembered as the Ira Ruben Miracle Knot, in honor of the person who showed it to me.
The knot diagrams make it look really hard, but it’s easy if you do a “standard knot” already. If your current technique involves making a bow and wrapping the other lace around before “tucking through” to make the second bow, then that’s the “standard knot.” All you have to do for the Ira Ruben Miracle Knot is wrap around twice before tucking. You probably already use your thumb or another finger as the placeholder for the tucked bow. Just wrap around it twice instead of once. It produces a sort of double-cinch that keeps the knot tied all day (or forever). If you know me or if you ever meet me, be sure to make me show you in person the next time we meet. In honor of Ira Ruben I will be happy to pass this life-changing trick on to anybody who asks.
A side-effect of using this knot is that you end up with just about the tidiest looking knots possible. At least, in my humble opinion :)