Stone Design Stone Design
News Download Buy Software About Stone
software

OS X & Cocoa Writings by Andrew C. Stone     ©1995-2003 Andrew C. Stone

Writing Good Cocoa Code - A Primer for the Objective C Programmer

After 15 years of writing code almost exclusively in Objective C (yes there was that 1 year interlude in the nineties when it looked like Java was going to replace our beloved ObjC), I thought I would put together a few pointers on creating code that is easy to read, easy to understand, easy to maintain and most importantly, has a sound architecture that allows easy addition of new features. This article, while no means a comprehensive guide, will touch on various aspects of writing sound software.

Firstly, and I'm lifting this from the book Mythical Man Month, remember that your first version of a program, whether you ship it or not, will be a prototype! Luckily, prototyping with Interface Builder is fast and fun. Learning to throw away code is more important than trying to get spaghetti code to fly!

Secondly, forget the idea that there is only one right way to solve a problem - usually there are many ways, but some of them will be better than others. The hallmarks of good code are proper overall design of classes, proper factoring of methods within each class, error handling and readability. Let's look at each of these in turn.

Architecture - if the foundations are rotten, the building will fall!

We've come a long way in the understanding of how software engineering affects the life cycle of software. If there is rigorous adherence to the Model-View-Controller design pattern, then the program will rest on a solid foundation. A good introduction to this concept is presented in

/Developer /Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/AppArchitecture/AppArchitecture.html and iMVC.html

The good news is that if you use Cocoa's NSDocument architecture, then you are already on the road to an MVC program. You then get all sorts of power absolutely free, including the ability to have unlimited undo and applescriptability. By cleanly separating what the program knows (the data Model), what the user sees (the View) and how the user interacts with the program (the Controller), your program will be scalable. New features can be added cleanly, and the software will be a joy to maintain.

Objects Are Closer Than They Appear

Good object-oriented design is based on encapsulation of data and operations on the data in an opaque "black box" manner.
By leaving the implementation details to each class, we can change how the class operates internally without affecting its interaction with the rest of the program.

For example, an NSMutableArray might be a good choice as a collection data structure if there are only a dozen items in the collection. But if there are a hundred, then an NSMutableSet might be more appropriate since it offers a quick way to determine if an item is in the collection or not. Should the number of items reach a thousand, then the most efficient collection might be an NSMutableDictionary, since hashtables allow direct access to any element in constant time.

The Factor Factor

The concept of factoring is simple: write small enough methods so that core functionality is available for use both programmatically and via the interface, with any entry funneling through this core. Moreover, as functionality grows, you'll find the methods "cascading", with simple 0 parameter entry points that call the multiple parameter methods. Since that was a mouthful, here's an example showing the concept:

- (IBAction)redrawGraphicsAction:(id)sender {
    [self redrawAllGraphics];
}

- (void)redrawAllGraphics {
    [self redrawGraphicsInList:_graphicsList];
}

- (void)redrawGraphicsInList:(NSArray *)list {
    [self redrawGraphicsInList:list andNotfiy:YES];
}

- (void)redrawGraphicsInList:(NSArray *)list andNotify:(BOOL)sendNotification {
    while (i--) [[list objectAtIndex:i] draw];
    if (sendNotification) [[NSNotificationCenter defaultCenter] postNotificationName:DrawGraphicsDidRedraw object:self];
}

So, in a very terse manner, we have provided an entry for the interface (redrawGraphicsAction:) as well as more complex versions for specific behavior. Most importantly, they all filter through the core method, redrawGraphicsInList: andNotify:. If you notice your methods have many lines of repeated code, consider factoring the subcomponents of the method into reusable methods of their own.


The Devil is in the Details

The main difference between first pass, get it up and running, code and final-for-now production code is how you handle edge cases and error checking. Our tendency is to imagine that everything is just going to work, but reality and user feedback shows us otherwise. Be sure to check the value of any method that returns a BOOL, since forgetting to do so may cause code that follows to fail. An example would be writing a file to disk:

// works, sometimes:
    [data writeToFile:someWhere atomically:YES];

// better:
    if (![data writeToFile:someWhere atomically:YES]) [self notifyUserFileWritingFailed:someWhere];

// best:
    NSFileManager *fm = [NSFileManager defaultManager];
    BOOL fileExists = [fm fileExistsAtPath:someWhere] ;
    if (data == nil || [data length] == 0)
        [self notifyUserFileWritingFailed:someWhere reason:@"Nothing to write"];
    else if ([fm isWritableFileAtPath:[someWhere stringByDeletingLastPathComponent]] &&(!fileExists || (fileExists && [fm isDeletableFileAtPath:someWhere])) {
        // we can probably write to that file
        if (![data writeToFile:someWhere atomically:YES]) [self notifyUserFileWritingFailed:someWhere reason:@"Could not write file"];
    } else [self notifyUserFileWritingFailed:someWhere reason:@"Could not write file - check permissions on folder"];


Readability

The One True Brace Style

There a minor myth in the Legends of C which we refer to as the "One True Brace Style". If you pin someone down to find out what this is, it invariably turns out to be the brace style they use! Mine opts for one less line of code for each nested brace by starting the open brace at the end of the line - like this:

- (void)whatsSoFunnyAboutPeaceLoveAndUnderstanding; {
    if (theySayWeNeedWar) {
        [mastersOfWar lookWhoProfits];
    } else {
        [citizens keepYourEyesOpen];
    }
}

Now, I bet you are thinking that the ";" that appears on the first line is a typo or bug! I know I did back in 1989 when Glenn Reid showed this to me. Actually, it's a neat feature of Objective C which allows you to quickly copy and paste method prototypes from your .h header file, and just start coding in the .m class file. The spurious-looking semi-colon is conveniently ignored by the compiler.


Consistent Variable Naming

There are a few good tricks to help others quickly understand your code. First is using fully descriptive English as much as possible! Gone are the days where we have to use one-character variable names to make faster programs. Instead, use descriptive names in your class, method and variable names. Second, make class instance variables immediately obvious by preceding the name with an underscore. Finally, don't forget comments! I like the double slash (//) comment delimiter since they can be nested within the old-style C comment delimiters ( /* */ ), allowing me to easily comment out blocks of code.

Objective C canonical form is simply English words concatenated with each successive word after the first capitalized. Here's part of a header that follows this style:

// one single import is all you need:
#import <Cocoa/Cocoa.h>

// your class name should be derived from the super class for easy comprehension

@implementation DrawWindowController: NSWindowController {

    // for highest level of security - make your instance variables private:
    @private

    // for maximum compile-time checking, type your instance variables:
    // The IBOutlet informs InterfaceBuilder that you can connect these
    // ivars to actual controls or menu items:

    IBOutlet NSTextField *_pageNumberField;
    IBOutlet NSPopupButton *_pageNumberPopup;
    ...
}

// IBAction is typedef'd as void - but it's useful to show the reader that this method might be called from a control or menu item. Moreover, if you include the word "Action" as the last part of the method name, then the intent of the method is obvious:

- (IBAction)changePageNumberAction:(id)sender;
- (IBAction)sendGraphicsToBackAction:(id)sender;
...

@end

Conclusion

Programming is an art, but one based in science. Adhering to some basic concepts will help you create code that has a long and happy lifespan!

PreviousTopIndexNext
©1997-2005 Stone Design top