Writing less code

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:

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:

  1. Generate NSString - @#param turns into @"name"
  2. Generate unique variable definition by joing symbols - NSString *local_##param = #@param; turns into NSString *local_name = @"name";
  3. 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]
  4. Enforce compile time errors and prevent making spelling mistake when using keyPaths/properties - ({if(NO){ [self param]; }; #@param;}) can be used as keyPath(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?

  1. Macro’s
  2. Chainable DSL for validators
  3. Key Value Coding
  4. 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;
  };
}
  1. return a block that matches our property definition
  2. when that block is executed we add a new validator with a 1-liner validation block
  3. 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:

Conclusion

There are many techniques to keep your code clean and DRY, let me know what are yours.

You've successfully subscribed to Krzysztof Zabłocki
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.