Bare Strings Considered Harmful

FlickrExport is a pretty mature project now. Lots of things in the code that I once wasn't sure would last are now solid, tested and reliable. However, with age, software becomes interdependent and thus fragile. Some of the work I've been doing on FlickrExport 3 has been to formalise the relationships between various classes.

One of the problems that FlickrExport has is that it has a lot of threads. Because of a limitation in NSURLConnection - namely, that its asynchronous API only works when the runloop is in NSDefaultRunLoopMode - FlickrExport handles its own thread creation and uses NSURLConnection's synchronous API.

As a result, I spend a lot of time calling `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:modes:]`. What's mildly (OK, *very*) annoying about this call is that it only lets you pass in one object. Consequently, one also spends a lot of time packing arguments into NSDictionary and unpacking them again on the other side. I criticise this method but I am, in fact, old enough to remember the days of Mac OS X 10.1 when it did not exist. Weighing up the pros and cons I'm happy to marshal and unmarshal objects all day long :-)

Anyway, I often use NSString as the key to these dictionaries. The trap for young players is this: if you find yourself writing `[myDict setObject: foo forKey @"theFoo"]`, stop and think carefully. Unless the locality of use of that key is within five lines of code of where you set it, I would argue that you're doing something wrong.

Why is it wrong? Two reasons. Firstly, if that dictionary is accessed from outside your class, you're leaking implementation details. You're no longer free to change the key to that object in that dictionary, unless you know the details of all the clients of that class everywhere. Secondly, your client classes critically depend on the spelling of a string - something that the compiler will not check for you.

FlickrExport had a number of places where one class would do something on a background thread, package up the arguments into an NSDictionary using bare strings as keys, and send that dictionary to another class in another thread.

What I've been spending time on today is defining constants for every possible key for every NSDictionary that is accessed outside of the class that creates it. I'm also defining a lot of constants for dictionaries that are passed around inside a single class. If your dictionary isn't used outside its class, you're at least not violating encapsulation but you are still at risk of simple typos causing obscure runtime errors.

I'm not quite at the point where I would argue that you should define constants for your KVO/KVC paths. However, I simply avoid referring to KVC paths more than one element deep in my code for precisely the reason that it can't be checked by the compiler. The only exception I make is calling `-mutableArrayValueForKey:` in order to do array manipulations in a way that is seen by KVO and Cocoa Bindings.

Daniel's suggestion of `@keycode(foo)` is tempting.