What are some of my favorite techniques for cleaner and more readable code?
Over the years I’ve become very pedantic when it comes to code quality.
All my projects use -Weverything & treat warnings as error and only selectively disable warnings if there is a valid reason to do so.
My code still have bugs every now and then, no way around this, everyone makes mistakes. There are many ways in which one can improve quality and limit bugs, TDD/BDD would be on top of my list.
Having tested code doesn’t neccesary equal clean code.
I value Readability keeping code DRY much more important. I can work on a project without tests, but working on a code that’s not readable or was written by copy-paste monkey is going to be dreadful experience.
There are many ways in which one can improve Readability and DRY’ness of code:
- smart refactoring.
- using self-resolving architecture.
- using behaviours.
- many more.
I’d like to share few techniques I’ve been using to simplify code.
Before we start looking at examples, be aware that pretty much ANY code technique can be misused, that doesn’t mean you should avoid it altogether.
KZAsserts
Asserts are great for adhearing to first part of the equation, they’ll crash your app, but they are usually stripped in release(they should), so we need to have proper error handling in release code.
Naive code would look like this:
NSParameterAssert([dataFromServer isKindOfClass:[NSDictionary class]]);
if ([dataFromServer isKindOfClass:[NSDictionary class]]) {
*error = [NSError errorWithDomain:MyerrorDomain code:FSProfileParsingFailedError userInfo:@{NSLocalizedDescriptionKey: "[dataFromServer isKindOfClass:[NSDictionary class]] failed"}];
}
NSParameterAssert([something isKindOfClass:[NSString class]]);
if ([something isKindOfClass:[NSString class]]) {
*error = [NSError errorWithDomain:MyerrorDomain code:FSProfileParsingFailedError userInfo:@{NSLocalizedDescriptionKey: "[something isKindOfClass:[NSString class]] failed"}];
}
There is a lot of duplication here, that's probably a reason why few people use assertions (I’d rather gauge my eyeballs out than write/read this kind of code).
So how could we achive all of the above (and more), but keep code simple and easy to read?
AssertTrueOrReturnError([dataFromServer isKindOfClass:[NSDictionary class]]);
AssertTrueOrReturnError([something isKindOfClass:[NSString class]]);
Preprocesor Macros
Contrary to Apple beliefs (Swift language doc), macros are used for much more than constants (using them for constants is plain wrong).
What are common techniques for leveraging macros ?
Given a macro definition:
#define Macro(param)
and a call like
Macro(name)
We can:
- Generate NSString -
@#param
turns into@"name"
- Generate unique variable definition by joing symbols -
NSString *local_##param = #@param;
turns intoNSString *local_name = @"name";
- Leverage gcc expression extension for multiple statements with return value -
({ result = doSomething(param); result; })
can be used as part of other expressions[Macro(name) doSomethingElse]
- Enforce compile time errors and prevent making spelling mistake when using keyPaths/properties -
({if(NO){ [self param]; }; #@param;})
can be used askeyPath(name)
to get keyPath for a property that you can NEVER make a mistake with (because it will throw compile error if an object doesn’t have property called name).
Techniques like this were crucial for my KZPropertyMapper DSL, let’s look at other techniques used there.
Example property mapping might look like this:
[KZPropertyMapper mapValuesFrom:dictionary toInstance:self usingMapping:@{
@"videoURL" : KZBox(URL, contentURL).isRequired().min(10),
@"name" : KZProperty(title).lengthRange(5, 12),
@"videoType" : KZProperty(type),
@"sub_object" : @{
@"title" : KZProperty(uniqueID),
},
}];
This little piece of code does a lot of things:
- Handle NSNull’s in source data
- Gracefully handle optional params
- Executes type conversions eg. string to URL
- Executes specified validations
- Generates compile time error if you make mistake in property name
- Looks awesome, just look at those validators, so clear and readable.
I’d say that’s quite a lot of bang for a buck. How can it do it?
- Macro’s
- Chainable DSL for validators
- Key Value Coding
- Runtime
Macro’s we already discussed, KZBox/KZProperty are macros that use above techniques.
DSL for validators.
I bet you appreciate how readable and easy to use validators are. Imagine them written as standard objc method calls, it wouldn’t be as easy to write or read.
Instead of nice
@"videoURL" : KZBox(URL, contentURL).isRequired().min(10).startsWith(@"http://myapi.com")
Even if I used same smart technqiues for chaining I’d still have lots of []
symbols, likes of:
@"videoURL" : [[[KZBox(URL, contentURL) isRequired] min:10] startsWith:@"http://myapi.com"]
This code is not as easy to change, if you wanted to remove or add validation you need to jump between end and start of the definition. Not to mention one can only take so many []
.
We can achieve simple and chainable DSL like the above one by leveraging properties along with blocks:
@property(nonatomic, copy, readonly) KZPropertyDescriptor *(^length)(NSInteger length);
then by calling
@"videoURL" : KZBox(URL, contentURL).isRequired().min(10).startsWith(@"http://myapi.com")
What we are actually doing is accessing some block properties and executing them, but how are those block set ?
Very simply:
- (KZPropertyDescriptor * (^)(NSUInteger length))length
{
return ^(NSUInteger number) {
[self addValidatorWithName:@"length" validation:^BOOL(NSString *value) {
return value.length == number;
}];
return self;
};
}
- return a block that matches our property definition
- when that block is executed we add a new validator with a 1-liner validation block
- our block returns self so that we can chain another validator on top of it.
Just take a look at other validators here.
Key Value Coding
Key value coding is really cool technique that I use in normal code but also very often while debugging.
KVC allows us to leverage:
- automatic boxing / unboxing of primitive types (eg. change int into NSNumber and viceversa)
- collection operators like sum/avg/max
- more complex operators like unionOfObjects
- extract only interesting attributes
- ALL of the above can be applied on subobjects
Examples:
Instead of:
- (CGFloat)before:(NSArray *)charts
{
CGFloat maxValue = CGFLOAT_MIN;
for(HRBBarGraphChartDescriptor *chart in charts) {
maxValue = fmaxf(chart.value.floatValue, maxValue);
}
return maxValue;
}
We do:
- (CGFloat)after:(NSArray *)charts
{
return [[charts valueForKeyPath:@"@max.value"] floatValue];
}
Unique elements from a sub-collection? Instead of:
//! We could use set operations here, but it's just trading speed with memory usage
- (id <NSFastEnumeration>)uniqueElementsBefore
{
NSMutableArray *allElements = [NSMutableArray new];
for (ShapeGroup *group in _shapeGroups) {
for(CCSprite *element in group.elements) {
if(![allElements containsObject: element]) {
[allElements addObject:element];
}
}
}
return [allElements copy];
}
Simple:
- (id <NSFastEnumeration>)uniqueElementsAfter
{
return [_shapeGroups valueForKeyPath:@"@distinctUnionOfArrays.elements"];
}
When working with debugger I often want to query some collections for interesting properties, eg. I only want names of Users from coredata object.
po [[website valueForKeyPath:@"users.name"]]
KZPM mostly uses KVC ability to box / unbox properties:
Collection will only contain NSNumbers, but if your class uses NSInteger or other primitives you can get that conversion for free:
[self setValue:@2 forKeyPath:@"primitiveNSInteger"]
Runtime
Meta programming is one of my beloved techniques, I just hate repetition, DRY all the way.
Smart runtime usage can give us a lot of power, I wrote about them extensively before:
- Dynamically create / override functions and classes:
- Automatically pick up classes implementing specific protocol. I wrote about this here
- Intercept methods to log or modify behavior eg. Aspects.
- Implement similar functions without repetition:
- eg. implementing ActiveRecord style findByField would only require a few lines of code for all properties of an object.
- Store context data per instance.
- Resolve methods/classes dynamically. Read here
- Adding guards around Apple API's misuses:
- Adding UIGestureRecognizer support to Cocos2D
- Implement Higher order messaging:
[[windowsArray do] setHidesOnDeactivate:YES];
Conclusion
There are many techniques to keep your code clean and DRY, let me know what are yours.