About a billion years ago, Bertrand Meyer (he of Open-Closed Principle fame) introduced a programming language called Eiffel. It had a feature called Design by Contract, that let you define constraints that your program had to adhere to in execution. Like you can convince C compilers to emit checks for rules like integer underflow everywhere in your code, except you can write your own rules.
To see what that’s like, here’s a little Objective-C (I suppose I could use Eiffel, as Eiffel Studio is in homebrew, but I didn’t). Here’s my untested, un-contractual Objective-C Stack class.
@interface Stack : NSObject
- (void)push:(id)object;
- (id)pop;
@property (nonatomic, readonly) NSInteger count;
@end
static const int kMaximumStackSize = 4;
@implementation Stack
{
__strong id buffer[4];
NSInteger _count;
}
- (void)push:(id)object
{
buffer[_count++] = object;
}
- (id)pop
{
id object = buffer[--_count];
buffer[_count] = nil;
return object;
}
@end
Seems pretty legit. But I’ll write out the contract, the rules to which this class will adhere provided its users do too. Firstly, some invariants: the count will never go below 0 or above the maximum number of objects. Objective-C doesn’t actually have any syntax for this like Eiffel, so this looks just a little bit messy.
@interface Stack : ContractObject
- (void)push:(id)object;
- (id)pop;
@property (nonatomic, readonly) NSInteger count;
@end
static const int kMaximumStackSize = 4;
@implementation Stack
{
__strong id buffer[4];
NSInteger _count;
}
- (NSDictionary *)contract
{
NSPredicate *countBoundaries = [NSPredicate predicateWithFormat: @"count BETWEEN %@",
@[@0, @(kMaximumStackSize)]];
NSMutableDictionary *contract = [@{@"invariant" : countBoundaries} mutableCopy];
[contract addEntriesFromDictionary:[super contract]];
return contract;
}
- (void)in_push:(id)object
{
buffer[_count++] = object;
}
- (id)in_pop
{
id object = buffer[--_count];
buffer[_count] = nil;
return object;
}
@end
I said the count must never go outside of this range. In fact, the invariant must only hold before and after calls to public methods: it’s allowed to be broken during the execution. If you’re wondering how this interacts with threading: confine ALL the things!. Anyway, let’s see whether the contract is adhered to.
int main(int argc, char *argv[]) {
@autoreleasepool {
Stack *stack = [Stack new];
for (int i = 0; i < 10; i++) {
[stack push:@(i)];
NSLog(@"stack size: %ld", (long)[stack count]);
}
}
}
2014-08-11 22:41:48.074 ContractStack[2295:507] stack size: 1
2014-08-11 22:41:48.076 ContractStack[2295:507] stack size: 2
2014-08-11 22:41:48.076 ContractStack[2295:507] stack size: 3
2014-08-11 22:41:48.076 ContractStack[2295:507] stack size: 4
2014-08-11 22:41:48.076 ContractStack[2295:507] *** Assertion failure in -[Stack forwardInvocation:], ContractStack.m:40
2014-08-11 22:41:48.077 ContractStack[2295:507] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'invariant count BETWEEN {0, 4} violated after call to push:'
Erm, oops. OK, this looks pretty useful. I’ll add another clause: the caller isn’t allowed to call -pop unless there are objects on the stack.
- (NSDictionary *)contract
{
NSPredicate *countBoundaries = [NSPredicate predicateWithFormat: @"count BETWEEN %@",
@[@0, @(kMaximumStackSize)]];
NSPredicate *containsObjects = [NSPredicate predicateWithFormat: @"count > 0"];
NSMutableDictionary *contract = [@{@"invariant" : countBoundaries,
@"pre_pop" : containsObjects} mutableCopy];
[contract addEntriesFromDictionary:[super contract]];
return contract;
}
So I’m not allowed to hold it wrong in this way, either?
int main(int argc, char *argv[]) {
@autoreleasepool {
Stack *stack = [Stack new];
id foo = [stack pop];
}
}
2014-08-11 22:46:12.473 ContractStack[2386:507] *** Assertion failure in -[Stack forwardInvocation:], ContractStack.m:35
2014-08-11 22:46:12.475 ContractStack[2386:507] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'precondition count > 0 violated before call to pop'
No, good. Having a contract is a bit like having unit tests, except that the unit tests are always running whenever your object is being used. Try out Eiffel; it’s pleasant to have real syntax for this, though really the Objective-C version isn’t so bad.
Finally, the contract is implemented by some simple message interception (try doing that in your favourite modern programming language of choice, non-Rubyists!).
@interface ContractObject : NSObject
- (NSDictionary *)contract;
@end
static SEL internalSelector(SEL aSelector);
@implementation ContractObject
- (NSDictionary *)contract { return @{}; }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
sig = [super methodSignatureForSelector:internalSelector(aSelector)];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)inv
{
SEL realSelector = internalSelector([inv selector]);
if ([self respondsToSelector:realSelector]) {
NSDictionary *contract = [self contract];
NSPredicate *alwaysTrue = [NSPredicate predicateWithValue:YES];
NSString *calledSelectorName = NSStringFromSelector([inv selector]);
inv.selector = realSelector;
NSPredicate *invariant = contract[@"invariant"]?:alwaysTrue;
NSAssert([invariant evaluateWithObject:self],
@"invariant %@ violated before call to %@", invariant, calledSelectorName);
NSString *preconditionKey = [@"pre_" stringByAppendingString:calledSelectorName];
NSPredicate *precondition = contract[preconditionKey]?:alwaysTrue;
NSAssert([precondition evaluateWithObject:self],
@"precondition %@ violated before call to %@", precondition, calledSelectorName);
[inv invoke];
NSString *postconditionKey = [@"post_" stringByAppendingString:calledSelectorName];
NSPredicate *postcondition = contract[postconditionKey]?:alwaysTrue;
NSAssert([postcondition evaluateWithObject:self],
@"postcondition %@ violated after call to %@", postcondition, calledSelectorName);
NSAssert([invariant evaluateWithObject:self],
@"invariant %@ violated after call to %@", invariant, calledSelectorName);
}
}
@end
SEL internalSelector(SEL aSelector)
{
return NSSelectorFromString([@"in_" stringByAppendingString:NSStringFromSelector(aSelector)]);
}

Pingback: Michael Tsai - Blog - Contractually-obligated Testing