The Ninth Principle: An idiom should be debuggable.

This is a continuation of the blog post 12 Principles of Good Software Design.

9. An idiom should be debuggable.

As software developers sometimes we become distracted by shiny bobbles and design patterns that represent a theoretical ideal that we think will help solve our problems. But in the process we don’t consider the fundamental process of writing software, we break our tools and we inadvertently create inefficiencies in the name of that ideal. Some developers even go so far as to think that if the tools are broken in order to fulfill their ideal, that it’s the tools fault–not considering that regardless of fault, they now have to write software with one hand tied behind their back.

The fundamental process of writing software is the Edit-Compile-Test cycle: we write code. We compile code. We test, find bugs and fix them by editing the code. And the faster we can perform this cycle, the more productive we can be.

Now a considerable amount of attention has been paid to the edit part of our process: IDEs, code refactoring, autocompletion; they all help us create and change code faster. We also have spent considerable amount of time on the compile process, even going so far as to advocate computer languages (such as Javascript or Ruby) which skip the compile step entirely. But we seem to take testing for granted, outside of unit testing–which is such a small subset of the entire “test” cycle above it’s almost not worth mentioning here.

And by disregarding testing (except for “unit tests”), we make our lives considerably more difficult.


For example, let’s assume we have a model class which we wish to provide a mock of, for testing purposes. The model class itself contains a lot of sophisticated logic, but it’s not ready yet, so we wish to use a mock object to test our user interface. Our standard mechanism for constructing our model is a ModelFactory:

@class Model;

@interface ModelFactory : NSObject

+ (ModelFactory *)shared;
- (Model *)create;

@end

In order to allow us to create our mock object, we use the NSProxy object, which takes all method calls to it and encapsulates them as an invocation which can then be forwarded to the appropriate object for execution.

So we create our protocol to define the procedures used by our model:

@protocol ModelProtocol <NSObject>

- (NSInteger)getItemCount;
- (NSDictionary *)getItemForIndex:(NSInteger)index;

@end

And we then define our proxy with the protocol:

#import "ModelProtocol.h"

@interface Model : NSProxy <ModelProtocol>

- (id)initWithObject:(id)object;

@end

For this example, our proxy simply wraps an object which provides the implementation, though in practice this could be considerably more complex:

@interface Model ()
@property (strong, nonatomic) NSObject <ModelProtocol> *forward;

@end

@implementation Model

- (id)initWithObject:(id)object
{
	self.forward = object;
	return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
	return [self.forward methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
	[invocation invokeWithTarget:self.forward];
}

@end

Our mock object, of course, is relatively simple:

#import "ModelMock.h"

@implementation ModelMock

- (NSInteger)getItemCount
{
	return 3;
}

- (NSDictionary *)getItemForIndex:(NSInteger)index
{
	return @{ @"Index": @(index) };
}

@end

Now we have the machinery in place to allow us to create a Model object, and since we’re building our Model from a factory singleton, we have a single place where we can produce our model (or Mock model) quite easily.


At this point we can obtain an instance of our proxy through the model factory, and execute methods in our proxy, switching between our mock object and our production project in our factory.

So, in our View Controller:

@interface ViewController ()
@property (strong, nonatomic) Model *model;
@end

@implementation ViewController

- (void)viewDidLoad {
	[super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.

	self.model = [[ModelFactory shared] create];
	NSInteger index = [self.model getItemCount]; // Test our call
	NSLog(@"%d",(int)index);

	(other initialization here)
}

We create our model from the model factory, and we can invoke a method within our proxy which then is forwarded to our mock object.


So now we need to debug this. And so we set a breakpoint on the line where our method call is made to our proxy:

NewImage

And we step into our method. Or rather, we try to step into our method. Result?

NewImage

We don’t step into the method.

The current version of Xcode (Version 3.7.1 (7D1014) as of this writing) cannot step into a proxy. That’s because the process wraps the proxy into an invocation and then calls the invocation elsewhere.

Now for our simple mock object, it may not matter–but if we need to step into the actual model code, we’re screwed.

We cannot debug our code anymore.


And it didn’t have to be this way if we didn’t fall victim to the shiny bobbles. We already have the pieces in place to allow us an easier implementation–one which doesn’t have all the shiny coolness of NSProxy, but a more pedestrian implementation which we can actually debug.

Step 1: Get rid of the Model object entirely. We don’t need it.

Step 2: Change our ModelFactory to return the protocol representation of our object:

@protocol ModelProtocol;

@interface ModelFactory : NSObject

+ (ModelFactory *)shared;
- (id)create;

@end

Our ModelFactory then directly returns the raw mock model object or the regular model object.

Our View Controller also needs to change:

@interface ViewController ()
@property (strong, nonatomic) id model;
@end

@implementation ViewController

- (void)viewDidLoad {
	[super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.

	self.model = [[ModelFactory shared] create];
	NSInteger index = [self.model getItemCount];
	NSLog(@"%d",(int)index);
}

But the changes are not significant; just the actual declaration of the model property.

Now we set our breakpoint as before:

NewImage

And we step in, and amazingly enough, we actually manage to step into our mock object:

NewImage


There are so many patterns out there that seem cool and interesting, but which make debugging our code significantly harder. We saw this in a previous post where we examined a method swizzling technique on a category of UITableView, where a bug in our endless scrolling code could potentially break every single UITableView and not just the ones that use endless scrolling–and without leaving us a clue as to how things went wrong.

Proxies and delayed invocation objects are also quite nice and can be used to solve very specific problems–but in general there are often other methods which don’t require more esoteric expressions which a modern debugger cannot handle.

An idiom should be debuggable, because if it is not, we’re left being lost, forced to do our job with one arm tied behind our back.

Leave a comment