Last week, I talked about doing things on the right level. In some ways, that post is a plea for consistency. In the world of programming, consistency is a worthwhile ambition. Here, Brent Simmons requests some sanity when it comes to dot-notation versus bracketed message sending.

Let’s talk about a couple of languages. Smalltalk and Ruby are very similar languages, the latter rooted in the former. Smalltalk gets consistency right, to a fault. Everything is an Object, including numbers, booleans, and the idea of nothingness. Do you want Swift-esque enumerations? Use polymorphism. Everything is an Object. There’s one type of nil, compared to Objective-C’s four (nil, Nil, NULL, and NSNull). Smalltalk values consistency so much that it throws user expectations out the window. Remember how I mentioned Smalltalk is consistent to a fault? In Smalltalk operators (like + or * ) are just methods that are defined on objects. However, messages have precedence from left to right, so 3 + 4 * 5 evaluates to 35, as though your code read 3.plus(4).times(5), instead of adhering to normal mathematical order-of-operations, as you might expect. (Seems like Alan Kay can invent OO, but can’t get OoO right!)

Ruby also has this Everything is an Object concept, with one object for nil, etc. But in Ruby, purity gives way to pragmatism, and operators like * have a higher precedence than operators like +. However, closures, simple in Smalltalk, are made complex in Ruby. There are three types of closures in Ruby: block, Procs, and Lambdas. The differences between them are unimportant and enumerated elsewhere. With three types of closures, knowing when to use which becomes an exercise in expertise rather than ease. It’s not immediately clear when you should use each or what the performance ramifications are. The stewards of Ruby let implementation details get in the way of the “user interface” of their language. Developers don’t care which closures can’t go in arrays and they don’t want to have to think about what the return keyword binds to — they just want to write code. Keep it simple for them.

Objective-C, like Ruby, is also based in Smalltalkian roots. In trying to make Objective-C more performant, however, compromises were made, and the designers used primitives to represent concepts like truth, falsity, and numbers. These primitives, since they aren’t objects, can’t go into native collections like arrays and dictionaries and must be “boxed-up” in a special class. That class, NSNumber, has no facility for manipulating the numbers therein, which leaves the developer constantly boxing and unboxing numbers to use them. Swift smoothes over this particular issue by treating integers and other numbers as structs, which retain the performance characteristics of primitives, can be stored in collections, and can be messaged.

And consistency trickles down from languages into standard library design. Apple does a great job with consistency in API design. Long, precise method names make it extremely specific what types you need to pass in, what type you’ll get, and what the function does. Apple even provides a WWDC talk about how to design your APIs.

Contrast this with PHP’s standard library. rtrim, strlen and str_replace are all functions that operate on strings, but they all have differing prefixes. (It turns out that this is because PHP’s first function lookup was based on the length of the function name, so functions needed names that were as varied as possible. The height of absurdity.) Compare these function names to the related Foundation method names. Here, consistency gives programmers a super power — the ability to guess what the function should be called — and autocomplete can take it from there.

And it affects your own code as well. Let’s take an example, like view allocation. There are a lot of places where you can set up your subviews. You can do it in your UIViewController’s -loadView or -viewDidLoad, or you can do it in UIView’s -initWithFrame:, or in Interface Builder, or anywhere else (it’s just software, after all). My preference is a lazy initializer:

- (UITextField *)usernameField {  
	if (!_usernameField) {  
		UITextField *usernameField = [[UITextField alloc] init];  
		usernameField.placeholder = @"Username or email";  
		[self addSubview:usernameField];
		
		self.usernameField = usernameField;  
	}  
	return _usernameField;  
}  

Once allocation and configuration are always done in the same way, you no longer have to think about where it goes. New method, every time. You never have to think about where to find it, either. Command-click on the getter for the view, and you’re taken right to the place where the view is created and configured. Now, if we extract the line of code that sets the placeholder, we’re suddenly left with a method that relies only on the name and class of the property.

- (UITextField *)usernameField {  
	if (!_usernameField) {  
		UITextField *usernameField = [[UITextField alloc] init];  
		[self configureUsernameField];  
		[self addSubview:usernameField];
		
		self.usernameField = usernameField;  
	}  
	return _usernameField;  
}  

Is this ripe for metaprogramming? You’d better believe it. If we mark the usernameField property as @dynamic, we can catch it in +resolveInstanceMethod: and create and configure it dynamically:

@implementation SKView

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorName = NSStringFromSelector(sel);
    
    NSString *viewName = [self viewNameFromSelector:selectorName];
    Class viewClass = [self viewClassForPropertyNamed:viewName];
    SEL configurationSelector = [self configurationSelectorForViewNamed:viewName]; //-configure<ViewName>
    BOOL isSetter = [selectorName hasPrefix:@"set"];
    if (isSetter) {
        IMP implementation  = imp_implementationWithBlock((id) ^(SKView *self, UIView *newView) {
            return [self.views setObject:newView forKey:viewName];
        });
        class_addMethod(self, sel, implementation, "v@:@");
        return YES;
    } else {
        IMP implementation  = imp_implementationWithBlock((id) ^(SKView *self) {
            if ([self.views objectForKey:viewName]) {
                return [self.views objectForKey:viewName];
            }
            UIView *view = [[viewClass alloc] init];
            [self addSubview:view];
            [self.views setObject:view forKey:viewName];
            if ([self respondsToSelector:configurationSelector]) {
                [self performSelector:configurationSelector];
            }
            return (id)view;
        });
        class_addMethod(self, sel, implementation, "@@:");
        return YES;
    }
}

@end

Consistency isn’t just a facet of good code, it is good code.