It's not about code reuse, it's about maintenance
Uli Kusterer has an article on his blog about what he calls “Cocoa ground rules.”
Since I’ve seen many people violate two ground rules of object-oriented programming (OOP), I thought I’d list them here, in the hopes it’ll help some beginners not fight the frameworks but rather go with the flow in Cocoa:
Uli then lists encapsulation and designing for code reuse as the ground rules. Now Uli is giving some good advice here, but I’d like to nit pick him here a bit for my own personal agenda… I mean, for the good of the people. Yeah, that’s it, the good of the people.
I propose that designing for code reuse, in most cases, is not the best thing to do. Allow me to explain.
Think about the times that you actually reuse code, in real, live applications. Its usually data structures, generic algorithms (like sorts and searches), and standard controls. Now think about where you find such code. That’s right, the basic data structures, algorithms, and UI widgets that you reuse in different applications are provided by the operating system or language for you. You don’t need to implement them, and, in general, shouldn’t try to. Apple is smart and realizes that applications get developed faster when developers don’t have implement basic data structures etc, that the operating system could provide for them.
Since the operating system and programming language provide most, if not all reusable classes, what’s left in your application is the application-specific classes. By definition application-specific code doesn’t typically have reuses in other applications, unless you’re writing competition for yourself. For example, if you have a class that reads in a SVG document, then you are probably drooling on yourself from having to read that insane specification. Plus, its unlikely you’ll need that SVG reader class in another application unless its also a SVG editor.
Now some of you think you have just spotted a hole in my logic. What if you have an application that is a lightweight SVG viewer and then a full-featured vector editing application? Both are plausible products that would need a class to read SVG documents. But they will have very different uses of the SVG reader class. The lightweight viewer will want to quickly build up the graphics state and draw, while the editor will want to keep the SVG DOM around so it can be edited later. Not a problem, you can abstract the SVG reader class to simply use callbacks on the delegate and let the delegate build up a DOM or whatever it needs to do to be efficient. That fixes the problem.
Right?
Except that the whole “abstracting” part is real hard. Very hard, especially if you haven’t implemented all the possible users of the class yet. As any API designer will tell you, it’s difficult to anticipate how a class is going to be used. That can lead to one of two situations: a class that isn’t abstracted enough to be used by multiple client classes, or a class that’s over engineered and very hard to use. Neither is ideal, but I will argue that being over engineered is worse.
Over engineered code is often bug riddled because of its complexity. It also runs the risk of having untested code in it, where the implementor said, “I don’t need this method now, but someone might need it later.” This introduces code that may or may not work, but creates the illusion, by being in the codebase, of being tested. That’s very bad. Over engineered code, as a result of being complex, also has a higher rate of being abandoned by potential users. If another programmer can’t figure out a class, they’re much more likely to create a new class to do what they want. On the other hand, if they see a simpler class that they understand, but doesn’t quite do what they need, they are more likely to enhance it to meet the original need and theirs as well.
I’m not saying code reuse is bad, I’m saying code reuse as a goal can be bad. In the stead of code reuse, I propose a new goal (or old, depending on how long you’ve been around).
As I so convincingly showed above, achieving code reuse is quite often not an attainable or realistic goal, because it so rarely happens. But what does happen a lot, and is in fact inevitable, is code maintenance. No matter what you write, you will have to maintain it at some point, unless you write really crappy code that no one in their right mind wants to use. There are several reasons why maintenance is more important than reuse:
- Money. More money is generated from product upgrades (maintenance) than the initial release.
- Time. Maintenance is where the product spends most of its life cycle.
- Code reuse itself. As I said above, most code reuse is achieved by modifying simple classes to meet the needs of additional clients.
Now that everyone knows that code maintenance is where developers spend their time, and make their money, we should optimize for it. How do we do that? Well, we need some new ground rules for that.
The ground rules of any programming, whether object-oriented or otherwise, lies in two terms: cohesion and coupling. Hopefully you remember these terms from your computer science classes, but in case you were getting a bikini wax that day, I’ll define them.
Cohesion describes the internal relationship of a module, or in OOP terms, a class. High cohesion means that everything in the class is tightly related (e.g. the x and y member variables in a point class), where low cohesion means that everything inside is only loosely related, if at all (e.g. the Windows Registry).
Coupling describes the external relationships of a module or class. High coupling means that a class is very dependent on other classes, such as deriving from them or declaring them as friends. Coupling can also be the use of other classes or aggregating instances of other classes. Low coupling means that a class or module pretty much stands on its own. i.e. It doesn’t need other classes to compile or run.
Ideally, you want high cohesion and low coupling.
High cohesion and low coupling improve code maintenance in some simple ways. High cohesion means you don’t have to go searching everywhere in the codebase to find everything about, say, vectors. Its all in one place. Which means the code is easier to understand, and also modify. Low coupling also means that code is easier to understand, because you don’t have to understand how several classes work (such as a class and its parents and friends), but just a minimal set. More importantly, low coupling means the code is easier to modify. Since code isn’t tightly coupled, then you can modify code in one class and not have to worry about it effecting a class its not coupled to. This extremely important in large software systems, like when you’re trying to figure out why moving a button in a dialog changes how your preferences are written out.
“Fine”, you say, “I want high cohesion and low coupling, but it sounds like proper encapsulation will get me that, which in turn will lead to code reuse, easier maintenance, and incredible abs. How is that any different from what Uli said?”
The goal is different. You’re goal is not code reuse, although it might be a welcomed side effect. Your goal is easy code maintenance. Focusing on how to make things easier to maintain, as opposed to reusable, changes how you code your classes. Instead of spending time trying to guess all the possible uses of a class and making them generic as possible, you code it to satisfy the needs of its current clients efficiently. As different clients require its use, then you modify the class to accommodate them.
In all fairness, reusability for Uli is really important. But its not because of programming ground rules, its because of product requirements. Uli has lots of sample code, and the primary feature of sample code is reuse. That said, I would still argue that it is more important that the sample code be maintainable than outright reusable by everyone. Its impossible to guess all the possible ways a Cocoa view could be used, so its more pressing that it can be understood and modified by the user to do what’s needed.
In computer science you should always optimize for the common case. Which is code maintenance, not code reuse.
(P.S. My apologies to Uli if I have misrepresented anything you said. Your comment on code reuse got me thinking about the people who rant and rave about code reuse as if it were the Holy Grail.)