Archive for February, 2007

NULL as a function argument should be illegal

Sunday, February 25th, 2007

I'm going to confess a realization that I've come to regarding one of my oldest and strongest API-designing habits—that I now believe that habit to be a mistake.

Whenever I design an API, my mind habitually wanders to how its functions should handle NULL for various arguments. Usually I make NULL do something special. In this way, I give NULL the implication of “do this operation with what you already have”, or “do this with the default value for this argument”.

This is a mistake. NULL should be an error.

There are two reasons why this is so:

  1. Clarity

    Compare these two pieces of code*:

    remove_filter(NULL, NULL);

    remove_all_filters();

    Which is clearer? Which makes the statement's action more explicit?

  2. Robustness

    What happens when (not if) somebody passes NULL by accident?

    Consider this:

    hookName = [filterNameField stringValue]; [self removeFilterForHook:hookName target:nil selector:NULL];

    What happens when you forget to connect the filterNameField outlet in Interface Builder, or when one of your localizers accidentally disconnects it, or when you or another programmer on the project deletes filterNameField, replaces it with something else, or cuts-and-pastes it (e.g. into a new tab view item), and fails to reconnect it?

    When the outlet is not connected, filterNameField will at runtime be nil. Messages to nil return nil, so hookName is assigned nil. Then you pass this value (nil) as the hook name to removeFilterForHook:target:selector:**. Oops—you just removed all the filters.

    The solution is to add a new method called removeAllFiltersForHook:, and make nil/NULL illegal in all four arguments (the one to removeAllFiltersForHook: and the three to removeFilterForHook:target:selector:). Thus:

    hookName = [filterNameField stringValue]; //stringValue returns nil, as before [self removeAllFiltersForHook:hookName]; //Assertion failure

    Rather than removing those filters and causing invalid (unfiltered) data to pass through, which will hopefully be noticed now but Murphy's Law says will be noticed much later (after you have forgotten this code), the new code comes to a screeching halt with a message in the console. If your program is seriously robust, your crash-catcher (for you do have one) will catch the exception and present a crash-report dialog.

    And, of course, your code is much clearer—no longer must the maintenance programmer (you, in six months) think about and interpret what you meant by “target:nil selector:NULL”, as such magic has been banished.

Counterpoint, and the rebuttal to the counterpoint

The main reason why I've made functions accept NULL and handle it specially is so that I can implement some higher-level method (such as removeAllFiltersForHook:) with it. For example, a function in Growl's Carbon API:

Growl_RegisterWithDictionary

Register your application with Growl without setting a delegate.

Boolean Growl_RegisterWithDictionary(CFDictionaryRef dict);

When you call this function with a dictionary, GrowlApplicationBridge registers your application using that dictionary. If you pass NULL, GrowlApplicationBridge will ask the delegate (if there is one) for a dictionary, and if that doesn't work, it will look in your application's bundle for an auto-discoverable plist.

<rant>

Now, I ask you: What purpose is there for this magic NULL behavior? Why would you set a delegate (which the framework asks for a registration dictionary immediately), then explicitly register with no dictionary? What would you expect that to do?

This flagrantly violates the principle of UI design of “if you had to document it, it wasn't designed intuitively”. The same principle ought to apply to API design. There's already a Growl_Reregister function which you can use if your delegate's registration dictionary changes, or if you didn't have one and you just created it. There's no reason why you should be able to call Growl_RegisterWithDictionary with no dictionary (for that's exactly what passing NULL is) and have it do the same thing. What it should do is fail an assertion or something.

(For the record, I'm not calling out anybody else on the Growl Project on this. I designed that API, and I wrote the entirety of what I quoted above. All blame for this rests squarely with me.)

</rant>

The reason why I had this function act this way was so that Growl_Reregister could be a one-liner. Here's the code (Growl BSD license):

void Growl_Reregister(void) { Growl_RegisterWithDictionary(NULL); }

Convenient. In turn, Growl_RegisterWithDictionary does some set-up, then passes the registration dictionary (whether it was passed in, obtained from delegate, or otherwise produced or assembled) off to another, internal function. I wouldn't want to duplicate the code of Growl_RegisterWithDictionary, right?

But I don't have to. All the set-up code from Growl_RegisterWithDictionary does is:

  1. If a dictionary was passed in, make sure that that dictionary is completely filled-in (and if it isn't, fill it in from the delegate's dictionary). This is handled by another function—Growl_RegisterWithDictionary just calls that function.
  2. If a dictionary was not passed in, get a filled-in dictionary from one or more other places.

If we make Growl_RegisterWithDictionary assert that its dictionary is not NULL, the code changes to:

  1. Assert that a dictionary was passed in.
  2. Make sure that the dictionary is completely filled-in.

Neither of these would be duplicated in Growl_Reregister—it takes no arguments, and the dictionary that it gets from elsewhere either is already completely filled-in by the time Growl_Reregister receives it or is not usable anyway. More to the point, any code that is using Growl_RegisterWithDictionary(NULL) would have to change to Growl_Reregister(), making it both clearer and shorter.

Conclusion

NULL should not be magic. It should not be special. For the sake of clarity and robustness, It should be illegal.


* The functions are named this way because I was thinking about WordPress at the time, but this really has nothing to do with WP, and I don't know if it is even relevant at all. That is to say, I don't know whether WP's remove_filter treats null specially in any way. Case in point.

** Alert readers will note the similarity to NSNotificationCenter's API. This is not intentional, but the similarity is real, and you should take from it that this situation is not merely theoretical: it certainly has happened, and will happen again.

CFMutableData doesn’t hide its internal storage after all

Sunday, February 18th, 2007

From the 2005-12-06 definition of CFDataGetMutableBytePtr:

This function either returns the requested pointer immediately, with no memory allocations and no copying, or it returns NULL. If the latter is the result, call the CFDataGetBytes function to copy the byte contents to an external buffer. Note that if you subsequently call any mutating function on theData—including operations that may not appear to change the length of the data—this may invalidate the pointer.

Whether this function returns a valid pointer or NULL depends on many factors, all of which depend on how the object was created. In addition, the function result might change between different releases and on different platforms. So do not count on receiving a non-NULL result from this function under any circumstances.

From the 2007-02-08 definition of CFDataGetMutableBytePtr:

This function is guaranteed to return a pointer to a CFMutableData object's internal bytes. CFData, unlike CFString, does not hide its internal storage.

Good to know.

It works for sets, too

Saturday, February 17th, 2007

You probably know that NSArray supports KVC as a sort of pseudo-HOM:

NSArray *words = …; NSArray *lengths = [words valueForKey:@"length"]; //Invokes -[NSString length] for every element, assuming that each element is a kind of NSString

What you may not know is that this works for NSSet as well. As -[NSArray valueForKey:] returns an NSArray, so does NSSet return an NSSet.

Apple Bug Friday! 47

Friday, February 16th, 2007

You thought it was dead! But no! Apple Bug Friday is back!

This bug is Drawing text changes the fill color. It was filed on 2006-08-18 at 20:35 PDT.

(more...)

Responses to Steve Jobs’ “Thoughts on Music”, in a total of five words

Wednesday, February 7th, 2007

First, the article.

The DRM-opponents' response

Holy shit.

The response from the RIAA and its member organizations

Oh shit.

The response from Microsoft, Creative, et al

Shit.

What’s the resolution of your screen?

Sunday, February 4th, 2007

A few weeks ago, I installed Adobe Reader to view a particular PDF, and noticed something interesting in its Preferences:

Its Resolution setting is set by default to “System setting: 98 dpi”.

“Wow”, I thought, “I wonder how it knows that.” So I went looking through the Quartz Display Services documentation, and found it.

The function is CGDisplayScreenSize. It returns a struct CGSize containing the number of millimeters in each dimension of the physical size of the screen. Convert to inches and divide the number of pixels by it, and you've got DPI.

Not all displays support EDID (which is what the docs for CGDisplayScreenSize say it uses); if yours doesn't, CGDisplayScreenSize will return CGSizeZero. Watch for this; failure to account for this possibility will lead to division-by-zero errors.

Here's an app to demonstrate this technique:

ShowAllResolutions' main window: “Resolution from Quartz Display Services: 98.52×96.33 dpi. Resolution from NSScreen: 72 dpi.”

ShowAllResolutions will show one of these windows on each display on your computer, and it should update if your display configuration changes (e.g. you change resolution or plug/unplug a display). If CGDisplayScreenSize comes back with CGZeroSize, ShowAllResolutions will state its resolution as 0 dpi both ways.

The practical usage of this is for things like Adobe Reader and Preview (note: Preview doesn't do this), and their photographic equivalents. If you're writing an image editor of any kind, you should consider using the screen resolution to correct the magnification factor so that a 8.5×11″ image takes up exactly 8.5″ across (and 11″ down, if possible).

“Ah,”, you say, “but what about Resolution Independence?”.

The theory of Resolution Independence is that in some future version of Mac OS X (possibly Leopard), the OS will automatically set the UI scale factor so that the interface objects will be some fixed number of (meters|inches) in size, rather than some absolute number of pixels. So in my case, it would set the UI scale factor to roughly 98/72, or about 1+⅓.

This is a great idea, but it screws up the Adobe Reader theory of automatic magnification. With its setting that asks you what resolution your display is, it inherently assumes that your virtual display is 72 dpi—that is, that your UI is not scaled. Multiplying by 98/72 is not appropriate when the entire UI has already been multiplied by this same factor; you would essentially be doing the multiplication twice (the OS does it once, and then you do it again).

The solution to that is in the bottom half of that window. While I was working on ShowAllResolutions, I noticed that NSScreen also has a means to ascertain the screen's resolution: [[[myScreen deviceDescription] objectForKey:NSDeviceResolution] sizeValue]. It's not the same as the Quartz Display Services function, as you can see; it seemingly returns { 72, 72 } constantly.

Except it doesn't.

In fact, the size that it returns is premultiplied by the UI scale factor; if you set your scale factor to 2 in Quartz Debug and launch ShowAllResolutions, you'll see that NSScreen now returns { 144, 144 }.

The Resolution-Independent version of Mac OS X will probably use CGDisplayScreenSize to set the scale factor automatically, so that on that version of Mac OS X, NSScreen will probably return { 98.52, 98.52 }, { 96.33, 96.33 }, or { 98.52, 96.33 } for me. At that point, dividing the resolution you derived from CGDisplayScreenSize by the resolution you got from NSScreen will be a no-op, and the PDF view will not be doubly-magnified after all. It will be magnified by 133+⅓% by the UI scale factor, and then magnified again by 100% (CGDisplayScreenSize divided by NSDeviceResolution) by the app.

Obviously, that's assuming that the app actually uses NSScreen to get the virtual resolution, or corrects for HIGetScaleFactor() itself. Adobe Reader doesn't do that, unfortunately, so it suffers the double-multiplication problem.

So, the summary:

  • To scale your drawing so that its size matches up to real-world measurements, scale by NSDeviceResolution divided by { 72.0f, 72.0f }. For example, in my case, you would scale by { 98.52, 96.33 } / { 72.0, 72.0 } (that is, the x-axis by 98.52/72 and the y-axis by 96.33/72). The correct screen to ask for its resolution is generally [[self window] screen] (where self is a kind of NSView).
  • You do not need to worry about HIGetScaleFactor most of the time. It is only useful for things like -[NSStatusBar thickness], which return a number of pixels rather than points (which is inconvenient in, say, your status item's content view).

How I learned Dvorak

Saturday, February 3rd, 2007
  1. Print out all the letters and some punctuation onto sticker paper. For a US keyboard, you'll need the alphabet except a and m, and all of “-=[];'/,.” I've forgotten the font I used (it was years ago that I did this), but I can offer you this: On my keyboard (a Macally iKey), the letters are 4 mm tall.
  2. Cut out the characters and affix each one to the matching key in the Dvorak position. For example, the ‘p’ goes on the QWERTY ‘r’ key (top row, fourth in from tab). Using labels rather than rearranging your keys allows you to easily switch back to QWERTY if you should need to for something.
  3. Hunt-and-peck with this arrangement. Do as much typing as you can arrange for yourself. Pay attention to the letter arrangement, which is not accidental—the vowels are all together on the left and many common digraphs (e.g. tr, nt, ?s, th) are on the right.
  4. When not at your computer, air-type in the Dvorak positions. This gets you used to the finger movements. Muscle memory is your friend. Anytime you daydream, or speak, or hear speech, narrate it in text on a keyboard of air. You don't need your arms in position for this; at your sides/in your pockets will work just fine. It's your hands that you're training.
  5. At the end of about two weeks, the stickers will fall off of the keys, and you will not complain because you have ceased to need them.

It wasn't much longer before I had matched my old QWERTY speed, and I've since surpassed it—I type around 100 WPM. (Curious as to your own WPM? Try this free typing test.) I'm rusty with QWERTY now, but I can still type it if I need to.

A caption contest, sort of

Saturday, February 3rd, 2007

Sam Javanrouh's Daily Dose of Imagery brings us this photo of a Windows Vista promotion:

Photo of a “brick” wall made of ice. Through it, we see the Office 2007 logo and a coat hanger on the opposite side.
Exterior shot of an ice house made on Dundas Square to promote the launch of Windows Vista.

So, your task, should you choose to accept it, is to come up with a tagline, slogan, or other quick advertisement-style summary that explains how an ice house promotes Windows Vista.

Good luck. I know you'll need it…

Two ways to strengthen your Cocoa-fu

Friday, February 2nd, 2007
  1. Chris Forsythe's Weekly Cocoa App Challenge, wherein you must clone an existing app with no source. “Week 3” is on now, since over two weeks ago. At the end of each logical week, Chris reveals the source, so you can check your answer (and his).

  2. Colin Barrett's Koan. The Koan that Colin poses is a verbal challenge, and is much harder than anything that Chris has put up so far. You basically need to already have strong Cocoa-fu in order to find the answer to this one within you.