In the course of my work I see a lot of code. Sample projects, full applications, frameworks, and proofs-of-concept have all crossed my desk at one time or another. In the labs at WWDC I see various bits of code which are giving developers trouble. The source of the project doesn’t seem to matter much; the problem is the same.
The problem I see time and again in all of this code is an unclear separation of responsibilities. Here are just a few of the things I’ve seen:
- A UITableViewController subclass which is not only the delegate and the data source, but is also making explicit networking calls to update its model data.
- Network transport classes which explicitly message UI elements in order to do their work (the inverse of the above, really).
- UIViewControllers (or any class) knowing about the internal structures or properties of other UIViewControllers.
I hear many reasons for this state of affairs in code:
- “This is just temporary. I’ll clean it up in a bit.”
- “It was really late in the schedule and this was the fastest thing we could do to get the feature working.”
- “I tried a bunch of things and this is what seemed to work.”
I’ve been guilty of all of these (and worse, probably). However it’s justified I can tell you right now you’re going to pay and it won’t take long for the bill to come due.
It’s not just temporary. Now that it works you’re not going to touch it because something else depends on that behavior and touching it will be far too risky. You’re probably not going to clean it up, except perhaps to put it under the bed and hope no one trips over it later.
Even on a tight schedule, last-minute block-ship bugs appear and what should have been a simple, straightforward bug fix will turn into some Giger-esque state-driven nightmare causing everyone associated with the project to invent new profanities because the ones they have don’t seem emphatic enough.
In the next release a new feature will be impossible to implement because class A has intimate, incestuous, biblical knowledge about class B. It seemed to work but the child of this relationship is going to cause you problems for the rest of its life even if you manage to separate the star-crossed classes and send them back to their families.
Here’s the thing: you are not ruthless enough. You are certainly not ruthless enough to your objects, and you probably need to be more ruthless to yourself.
Being ruthless to yourself means every time you say “oh, I’ll just open up this internal bit over here…” use that moment to give yourself whatever negative feedback you need to go back and write the correct interface. Imagine the bugs you’ll get later. Give yourself a 12 volt shock through your chair. Picture the sleepless nights chasing down an issue that only happens for that one guy but it’s the guy who signs your paycheck.
Every time you throw in a quick fix for something because it’s Getting Late(tm), stop and see if you can fix it correctly right then. Pragmatism says it might not be possible in the time remaining, and that’s ok; “Real artists ship” and all that but a ruthless artist will fix the problem first thing in the next release so they can keep shipping again and again and again.
Every time you throw in something that seems to work, find out why it works. If you don’t understand why it works then the code is just magic and you’re letting yourself off the hook. That code will become enshrined in your version control system for later generations (read: you, three months from now) to discover, wonder at, puzzle over, and leave it because no one understands what it does.
Being ruthless to your objects means having clear bright lines separating responsibilities in your code. Where does that data download activity belong? It’s definitely not a view. It’s not the model but it gets things for the model. It’s sort of a controller, mediating between the network object on the far side and the model on the near side, but it’s not part of a controller. It’s its own object and it deserves its own interface. Put it in its own box. Keep it there. Don’t let it out except through the clean interfaces you write. Don’t let anything else in either.
As a frameworks developer I tend to look at this problem from the frameworks perspective. Part of being ruthless to my objects is writing real API every time I encounter one of these problems. Objects become testable in isolation. Complex behaviors arise from combining simpler objects in controlled ways through API. The key letter in the acronym is the I - the interface. It’s the clear bright line between the classes. It’s how you keep objects in their respective boxes.
The back of King Crimson’s album Discipline says “Discipline is a means to an end, never an end in itself.” Being ruthless to yourself is having the discipline to become a better developer - not letting yourself get away with the easy or convenient. Being ruthless to your objects is having the discipline to write the API which separates their responsibilities effectively. The combination is what enables you to produce consistent results - to keep shipping, to keep creating great software, and to keep improving.