Doing things at the right level is very important for long-term, sustainable programming. One theme of this blog is how to beat Massive View Controller. Massive View Controller happens because you’re doing things on the wrong level. Things in the model layer, like values and enumerations, bubble “up” to the view controller, while organizational responsibilities, like coordination, bubble “down” to the view controller. The combined complexity of all this code makes the view controller become tangled with itself and impossible to grok.
What do I mean by “the right level”? Let’s dive in.
The Knight’s Template Method
If you have one thing, and you want to make another thing, using inheritance to solve that problem will always put you in a bind. I talked about sidestepping inheritance for share code in last week’s Prefer Composition to Inheritance. This week I’d like to talk about when the extenuating factors win out, and you have to use inheritance.
If you subclass from one thing to make another thing, you’re failing to keep things on the right level. To maintain order in your code, remember: you can’t have just one specialization. In this case, you have three things: the shared code, which goes in a superclass; and the two branches of differing code, which are the specialized subclasses.
This type of structure creates two levels. The top level, the superclass, contains the abstraction, the prototype for the concept; it shouldn’t be instantiated. The bottom level contains the concretion, the congealed form of the ideas; it can’t exist without the upper level. This is the Template Method pattern. It’s probably an anti-pattern. Nevertheless, when you have to do it, do it right.
Color-coded
All modern editors use color to signify meaning. Constants are one color, methods another, local variables a third. This highlighting is designed to help readers parse code more easily, by allowing the user to focus on chunks like literals or instance variables at once. One new way I’ve learned to use syntax highlighting is called the squint test, and it’s a litmus test for the clustering of code.
The squint test is simple: squint at your code, and make sure similar colors sit together. If they don’t, you might not be doing things on the right level. In its simplest form, it will compel you to move string literals to the top of a file. You can use it for much more, though. The squint test can also help you separate the semantic components of your class that are related. In this talk, Sandi Metz refactors the Gilded Rose kata, and when we squint at the product of her refactoring, it’s clear that the right-hand side of the slide is better separated and better factored.
N.B.: you’d be forgiven for thinking she needs to refactor this more. She does, and her final result is awesome. I highly recommend the talk. Even better, try the kata out before you watch the talk.
At C level
Objective-C is designed as a layer on top of C. It can do everything that C can do, and then some. You could ship an iOS app written entirely in C or C++ if you wanted. This interoperability has been a boon to developers, allowing them to use a vast array of libraries in their apps that were never intended for use in Objective-C.
However, it’s also provided confusion to developers writing code. Should this simple string transformation be in a category on NSString
or a C function that takes one string parameter? A category incurs the cost of an objc_msgSend
, but dipping down to the C level incurs a context switch for the reader of the code.
When I’m reading C code in an app, I wonder what dropping to C means, semantically. Does it mean that the code needed to be more performant or that the author was more comfortable writing C than Objective-C? What should I infer from the switch? Staying consistent and using one language prevents these questions from being asked; also, staying consistent in your basis for dropping down to C guides your reader into inferring more meaning from your code.
Preprocessor macros are similar. They have a ton of value in providing code generation and metaprogramming to more static languages, but they incur a context-switching cost as well. Worse still, macro code doesn’t get highlighted and there’s a bunch of slashes everywhere, making it hard to read. Do string literals need to go in a #define
, or can they be class or instance methods in an Objective-C class?
In the latest version of the Genius app, we support annotating other websites. As you can imagine, this requires calling a lot of JavaScript from within Objective-C. All of this JavaScript is inside NSString
literals, which, like preprocessor macros, have no syntax highlighting. To alleviate this as much as possible, the JavaScript is clustered together in the same object, and hidden behind a type-safe Objective-C interface. If you need to mess with the JavaScript, there’s one .m file to go to. No more grepping for stringByEvaluatingJavaScriptFromString:
.
Staying on the right level helps keep your code legible to not only your teammates but also future versions of yourself.
Layer cake
Your app is composed of several layers: data storage, manipulation, presentation, and so on. The more clearly the layers are defined, the more obvious it is when code from one level bleeds over into another level. Unfortunately, we’re stuck with the ill-defined and meaningless name Controller, and we have to work with it. Presentation code might go into the controller at first. But as it gets more and more bulky, it can split into its own layer, like a cell undergoing mitosis. This new, well-defined layer should have less trouble keeping out-of-scope responsibilities out and keeping its role clear.
Wrapping up
These examples seek to show two things: 1. working at the wrong level makes your code more complex and hard to understand, and 2. working at the right level smoothes out your code and keeps it easy to maintain.
However, keeping your code neatly organized in these ways requires rigor. No amount of automated testing or static analysis will reveal that your code is poorly structured. Well-crafted programs come from the human element. Meticulous planning and code review help you critically analyze the code until it satisfies you.