The Seventh Principle: An idiom should stick to language features which make code obvious.

7. An idiom should stick to language features which make code obvious.

The Objective C language has a number of interesting features which allow us to add new functionality to our code. Features such as KVO allow us to trigger an event when a field changes. Categories allow us to extend an existing class. Method swizzling allows us to replace the functionality of a method with another for all objects of a given class.

Java has a number of interesting features as well. The ability to customize our class loader allows us to modify the way we load executable code. Java has a rich set of introspection primitives as well as reflection support to allow us to dynamically examine classes and code. Java even includes an annotation mechanism that can help us guide our introspection to associate semantic meaning to methods and fields.

Do not use them unless you absolutely must.

When you use these features you can make the functionality of your code impossible to follow for someone new to a project. These features can make it impossible for someone to discover if functionality is built in or added, impossible for someone to follow the flow of execution, impossible to discover how to fix bugs or extend functionality.

And some of the tools at your disposal are downright dangerous, in the sense that if you screw something up you can make the application unstable. Certain techniques, such as method swizzling, can introduce bugs that are nearly impossible to debug.

So many times we use features such as this, when it is not necessary; similar functionality could be achieved more simply through easier to understand and easier to maintain techniques, such as class inheritance.

Suppose you have the following application which displays a table of items in iOS. The model declaration is simple: given a start row and a number of rows, this loads an array of the objects and asynchronously returns the loaded list of items.

The essence of our endless scrolling code involves looking at the current offset of the content, and if it is close enough to the bottom, trigger a callback to ask our view controller to load more rows. If we have no more rows to load, then we disable endless scrolling for that table view. We also keep track if the table view is in the loading state; if it is, we don’t trigger multiple callbacks to load more data. (We can use that state to extend our table view to maintain some loading UI, such as a spinner at the bottom of our content area.)

It’s fairly straight forward. The first method calculates the offset which would be the “maximum scroll”; that is, the maximum amount our content can scroll. The second method determines if we are loading, and if we are not, then determines if we are at the bottom of the scroll area. If so, we note that we’re loading, and call our callback.

Because this is a category we cannot simply extend our class. We must instead use an association to store our data:

This additional callback in our delegate allows our view controller to detect if we need to load more items and take the appropriate action. Once we’re done loading (as loading is an asynchronous process), we call the -finishedAppendingData; category method. If there are no more rows we disable endless scrolling by calling -setEndlessScrollingEnabled: with NO.

Now at the heart of our endless scrolling, we require callbacks as the contents move. Currently on iOS, when the contents move, the method setContentOffset: is invoked. As the contents resize, setContentSize: is invoked. In order to intercept calls to these methods we must use method swizzling. We basically replace the pointer to the function that is invoked by the selectors setContentOffset and setContentSize with pointers to our own functions. Our functions then invoke the previous selectors, then detect if we hit the bottom by calling our -es_TestBounds methods.

Notice the oddity that the methods look like they’re calling themselves. They’re not. As we swizzle the methods, what’s old becomes new again–and what appears to be a method calling itself in fact is the new method calling the original method.

The notable elements are in blue. Our delegate method -appendMoreDataToTableView: handles the operation of appending more data; if more data is not available, endless scrolling ends.

Problem solved, right? And in such a clever way.

Well, not quite.

First, because we used a category and method swizzling, a casual user who has never seen this code before would not know how endless scrolling was hooked into our system. It’s not readily apparent, for example, that our category has added endless scrolling to all UITableViews and not just the one we’ve enabled, by virtue of the +load method. Worse, if you have an application with two or three dozen table views, only one of which implements endless scrolling, it may be nearly impossible for a new user to realize your category even exists.

This is important, because second: our method swizzling takes place on startup, and because all tables are affected (and not just the ones with endless scrolling) any bugs in our endless scrolling code will break all table views (and not just the ones we’ve added our functionality to). This means that a bug in a chunk of code that is entirely invisible to the user who is maintaining your code (because the <UITableView+EndlessScroll> category still affects a table view even though the header wasn’t loaded) will wind up with an exceedingly hard to understand crash, thanks to swizzling. What should be an easy to understand issue may wind up looking like an internal iOS bug.

Third, supporting the swizzling operation makes our code considerably longer and more complicated than it needs to be.

Suppose instead of adding a category we created a child class of our UITableView. This would have the nice property that only tables with endless scrolling would be affected by any bugs in our code. And it eliminates our need for swizzling and for tracking internal state using an association.

Of course this requires a minor change in our view controller: we need to replace our UITableView with an ESTableView. But the rest of the View Controller logic is absolutely identical.

Now we’ve replaced our Objective-C specific category, swizzle and property-based solution with a more traditional child object and object inheritance solution, we’ve now done a few things:

First, we have isolated any bugs in our endless scrolling code to just the table views which have endless scrolling. This is great because it means if there is a defect in your endless scrolling code, it won’t present itself in some odd location elsewhere in the application as if it were a bug in the iOS operating system.

Second, and more importantly, because we now have to switch the UITableView to an ESTableView for endless scrolling, we signal in the code those places were we are using the enhanced functionality. This allows someone new to the code to understand that new functionality was added, and to allow them some insight as to where to find the new functionality (in the ESTableView class).

In other words, it has become exceedingly obvious that a class with new functionality has been added, while avoiding potentially dangerous constructs such as swizzling.

Of course there are times when using swizzling and attaching properties dynamically to a table helps solve a deep problem. However, most of the time there is no reason why creating a child class wouldn’t give the same results, and in the process isolate any problems while making it blinding obvious to a new developer what is going on.

And if it is blindingly obvious you have extended the functionality by creating a child class, then you will have to answer fewer questions from any new developers who take over your code.

Which means you can get home to your life sooner, while maintaining your level of productivity.