Nice moments
I just had one of those nice moments where you write some code and you know straight away that you’ve done the right thing. These moments usually follow a period of doubt, then something slips into place and you realize that you’ve found the correct solution.
In this case I was writing an Objective-C wrapper for some sysctl(3) stuff. I like to write wrappers around Carbon, POSIX and Mach stuff so that my Objective-C source code looks nice and is easy to maintain. I find it very distracting when I have to mix together procedural C code with Objective-C message sending; in fact, it starts to look decidedly ugly to me, especially in the case of the Carbon API.
Now, I don’t want to sound irrational or prejudiced about this. I think good old-fashioned C code that does nothing but call POSIX or Standard C Library stuff can look pretty attractive in a lean, utilitarian kind of way. Objective-C, especially the Cocoa variety, can look decidedly elegant. Carbon pretty much always looks ugly to me because so often it requires you to pass variables by reference which then become recipients for your return values; I much prefer the way most Objective-C methods give you a single return value, they don’t require you to pass things by reference, and they flag error conditions either with simple BOOL return values or by raising (catchable, well-documented) exceptions. Regardless of which approach might be your favorite, most would agree that mixing these different coding philosophies together almost always looks horrible. Best to keep things consistent and keep blocks of like code together.
So I like to wrap up that ugliness and encapsulate it inside an Objective-C interface. Once it’s wrapped up I never have to look at it again and my source remains nice and clean and easy to maintain. Using all these different APIs is somewhat of a necessary evil too, because there is a lot that Cocoa just can’t do and probably never will be able to either.
So anyway, I had my sysctl wrapper up and working and I was happy that it allowed me to encapsulate away some of the ugliness that I’d otherwise have to sprinkle throughout my Objective-C source, but I felt uncomfortable about it. I knew that wrapping up the sysctl API was at least grouping a related set of functions together into a single logical unit, but it still didn’t feel right. It was still slightly arbitrary; group these together because that’s the way they’re grouped at the POSIX level. But I wasn’t working at the POSIX level, I was wanting to work at the Cocoa level. The Cocoa way would be to group things together according to their purpose, not their underlying implementation details. And although I’d been able to abstract away some of the nasty details of the API, I was still having to deal with the unsavory aftertaste of dealing with POSIX and Carbon return types that were returned by my wrapper.
In the end I realized that I could have my cake and eat it too. The sysctl wrapper gives me an Objective-C interface to a very low-level API, and I’ve taken advantage of that API in a number of other high-level classes and categories. The categories are where the code feels most "right"; because it’s where I’ve been able to fill in some of the holes in Apple’s methods with methods of my own, faithful to their style and conventions, but leveraging the power of the (now-encapsulated and hidden) lower-level API. Rather than providing methods to do anything you could possibly imagine, I provide methods to provide the thing you’re most likely to actually want to do (which in theory is what Apple does as well). In the places where I need more control I can talk directly to the wrapper; but in almost all other cases I can just talk to my category-augmented versions of old Cocoa favorites like NSWorkspace and friends. As soon as I did it it felt "right".
You might doubt that so many levels of abstraction are a good thing (programmer calls a category, category makes use of a wrapper class, wrapper class accesses yet-another, lower-level API; and within those layers there are sometimes additional levels of message-forwarding in methods that have been (re)factored to maximise code reuse) but in many cases this is exactly what is happening inside Apple’s Frameworks. A lot of the stuff we access via Cocoa is really just something that rests on top of the Carbon, POSIX or Mach layers. Furthermore, the code I’m talking about here is infrequently called and unlikely ever to become a bottleneck. The levels of abstraction do not slow things down as far as the user is concerned, but for the programmer they make the code easier to read and more maintainable which means that it gets written faster, works better, and is less likely to have bugs.