Vincent Gable’s Blog

January 23, 2009

Never Submit

Filed under: Bug Bite,Design,Programming,Usability | , ,
― Vincent Gable on January 23, 2009

Submit is always the wrong title for a button. Yet it’s still commonly used, even by people who should know better. I had “Submit Comment” buttons on my blog when I first published this.

Buttons should say what happens when they are pushed, in the vocabulary of the person pressing them. Technically a button might submit a form to a server, but what matters is the consequence of submitting the form.

For example,

Picture 27.png

this button should be called “Search” or “Find” or “See Matches” — something that describes what happens when it is pressed, or what the operator will see after pressing it.

That’s a Bad Word

“Submit” has negative connotations, and should be avoided. The first three example usages (in Mac OS X’s Dictionary.app) are all negative,

submit

verb
1 [ intrans. ] accept or yield to a superior force or to the authority or will of another person : the original settlers were forced to submit to Bulgarian rule.

• ( submit oneself) consent to undergo a certain treatment : he submitted himself to a body search.

• [ trans. ] subject to a particular process, treatment, or condition : samples submitted to low pressure.

Say an apartment takes applications on their website. It would be pedantically correct to say “Submit Application”. But it is more respectful to say “Send Application”, or “Apply”. Pressing a “Submit” button implicitly says “I submit”. And that’s the wrong relationship for a user to have to an interface.

Blame The Programmers (Not Really)

One reason so many buttons are labeled “Submit” is that the HTML code for making a button has the word “submit” in it. The code for is <input type="submit" value="This Button">.

If the keyword send was used to build buttons, I would argue that the web would be a slightly less intimidating place today. A button that demands you “send” something is better then a button that forces you to “submit”.

Choose Your Words Carefully…

So perhaps, when choosing programming terms, we should pick the ones with the fewest negative connotations, since inevitably some of those words will bleed over into user-land. Even if programmer words stay in programmer-land, word-choice influences the way we think about things. Best not to encourage berating your users and customers.

Of course, you shouldn’t go overboard avoiding “ungood” words! There is no question that the most clear term should be used (even if it’s offensive). A better programming-vocabulary means better, less buggy, programs. And that’s better for users (no matter what they are called behind their back). But if possible, avoid disparaging words.

And never submit to the temptation of calling a button “Submit”. There’s always a more accurate, respectful name.

January 18, 2009

iChat AppleScript / Apple Event Gotcha With “set status”

Filed under: Bug Bite,MacOSX,Programming | , ,
― Vincent Gable on January 18, 2009

If you run the AppleScript:


tell application "iChat"
   set status message to "testing"
   set status to away
end tell

You might expect to have the away message “testing”. But what you get is an empty away message. That’s because set status destroys your status message. It behaves as if you had selected “Away”, “Available”, etc. as your status from iChat’s status menu.

If you want to set a custom away message, call set status first, so it won’t over-write your message.


tell application "iChat"
   set status to away
   set status message to "testing"
end tell

Filled as radar://6505882, but Apple considers it expected behavior.

January 13, 2009

Running in the Background

Filed under: Bug Bite,MacOSX,Programming |
― Vincent Gable on January 13, 2009

If you want your bundled application to run in the background — not show up in the dock — here’s what you do. Add

<key>LSUIElement</key>
<string>1</string>

(exclusive) or

<key>LSBackgroundOnly</key>
<string>1</string>

To your info.plist file. Use only one or the other.

LSUIElement will let you put up windows and dialogs. They’ll appear in the context of whatever application is frontmost at the time. If your process might ever need to put up an alert, this is what you should use.

If you choose LSBackgroundOnly, then windows and dialogs won’t work. Note that if you are writing a daemon, there are frameworks that you can not use!

January 7, 2009

Objective-C 1.0 Style: Don’t Name Your Enumerators “enumerator”!

Disclaimer

There is a better way to iterate over collections in Objective-C 1.0. You really should use it. It’s easier to write, easier to read, less prone to bugs, faster, and makes what I’m going to rant about here a non-issue, because you won’t have any NSEnumerator variables in your code.

Badly Named Problem

The standard iteration idiom in Objective-C 1.0 is:

NSEnumerator *enumerator = [collection objectEnumerator];
id element = nil;
while( nil != (element = [enumerator nextObject]) ) {
   ;//do stuff...
}

Unfortunately, I see otherwise steller programmers name their NSEnumerator variables “enumerator”. That’s always wrong, because it does not tell you what the enumerator enumerates. We already know that enumerator enumerates things, because it’s type is NSEnumerator, unless it’s name tells us more then that it’s hardly better then no name at all.

This is an especially amateurish practice because …

Naming an Enumerator Well is Easy!

Call it, the plural of the element variable. And if that won’t work you can always fall back on calling it collectionEnumerator.

For example, to fix:

NSEnumerator *enumerator = [input objectEnumerator];
NSString *path = nil;
while (path = [enumerator nextObject])

We should name enumerator paths or inputEnumerator. You might find “paths” to be too close to “path” in which case let the “plural form” of element be everyElement, giving everyPath.

These rules can be applied without much thought, but will improve the clarity of code.

Why enumerator is Worse Than i

Firstly, the practice of naming an the index in a for-loop i is not good. You can avoid it by renaming i to thingThatIsIndexedIndex.

But at least, for(int i = 0; i < collection.size(); i++), is concise; therefore better than a equally-poorly-named NSEnumerator.

Also, there is something to be said for the idiom you can just use collection[i] over declaring an extra element variable.

The Right Choice

Everyone agrees informative names are good, yet poorly named enumerators are everywhere (just browse Apple’s sample code!) Poorly named enumerators persist because nobody really uses an enumerator per se, they are just part of an iteration idiom. (So stop declaring them and iterate smarter). When was the last time you saw code that did anything with an enumerator besides [enumerator nextObject] in a while loop?

But bad habits matter. Don’t pick up the habit of naming something poorly when it’s easy to do the right thing.

December 28, 2008

Bug: @encode(long double)

Filed under: Bug Bite,MacOSX,Objective-C,Programming | , , , ,
― Vincent Gable on December 28, 2008

UPDATE 2010-08-16: I ran a test today, on Mac OS X 10.6.4 (i386), and the issue has been resolved, @encode(long double) evaluates to "D". I’m not sure when this change happened, or what platforms it effects.

@encode(long double) returns the same string, "d", as @encode(double), at least on i386, Mac OS X 10.5.6 / 9G55. But a long double and a double are not identical:


double aDouble = M_PI;
long double aLongDouble = M_PI;
NSLog(@"sizeof(aDouble) = %d, sizeof(aLongDouble) = %d",

   sizeof(aDouble), sizeof(aLongDouble));
NSLog(@"aDouble = %f", aDouble);
NSLog(@"aLongDouble printed as a double (%%f) = %f", aLongDouble);
NSLog(@"aLongDouble printed as a long double (%%Lf) = %Lf", aLongDouble);

sizeof(aDouble) = 8, sizeof(aLongDouble) = 16
aDouble = 3.141593
aLongDouble printed as a double (%f) =
   -88796093704928900002674917893032219152220160.000000
aLongDouble printed as a long double (%Lf) = 3.141593

I haven’t tested another configuration. TYPE_LONGDOUBLE_IS_DOUBLE in ConditionalMacros.h is 1 under __ppc__, so this might not be a problem on PowerPC.

Submitted as radar://6468314.

December 26, 2008

Always Update the View From the Main Thread

Filed under: Announcement,Bug Bite,Cocoa,Interface Builder,MacOSX,Objective-C,Programming,Quotes | , ,
― Vincent Gable on December 26, 2008

I wish I’d read this years ago:

AppKit, the GUI framework, is not thread safe. In order for things to work properly, you (almost) always need to update GUI classes from the main thread

Dave Dribin (slightly edited)

I’ve run into UI + threading problem before, but I’d just never seen this limitation of AppKit spelled out.

Dave’s article explains how to call code on the main thread better then I can.

December 22, 2008

Resources in Unit Tests and Other Frameworks

Filed under: Bug Bite,Cocoa,MacOSX,Objective-C,Programming,Sample Code | , , , ,
― Vincent Gable on December 22, 2008

To load a resource inside a unit test or other bundle, do:

NSBundle *unitTestsBundle = [NSBundle bundleForClass:[self class]];
NSString *pathToResource = [unitTestsBundle pathForResource:name ofType:fileExtension];

[NSBundle mainBundle] points to the bundle for the current executable, so it’s handy in an application (where it will point to your .app bundle). But since unit tests are loaded into another program or test-harness to run, it’s not appropriate to use it in a unit test.

December 18, 2008

Automator Bugs

I’ve started using Automator in IMLocation to let people build a set of actions that are executed when they arrive somewhere (for example, muting their laptop’s speakers in a classroom).

Unfortunately, my impression of Automator.framework so far is: buggy.

Here are some issues I’ve run into so far.

-[AMWorkflowController stop:] does not stop the workflow! I do not yet have a workaround.

AMWorkflowController sends its delegate a -document message whenever an action is removed from the workflowView, by clicking the (x) button. If the delegate does not implement it, the action will not be removed!

The (x) button is enabled , but does nothing, in workflows that are not editable.

-[AMWorkflow initWithContentsOfURL:error:] throws an exception when the URL is nil.

This project demonstrates the first two issues.

December 16, 2008

isEmpty?

Checking if a Cocoa object is empty is a little harder then in other languages, say C++, (but easier in some ways). Because every object in Objective-C is actually a pointer to an object, there are two ways, obj, can be empty.

obj = {}

obj points to an object that is empty. Say an array with 0 items, or the string "", etc..

obj = nil

obj, the pointer obj, is NULL, nil, 0, or whatever you want to call it. You might argue that obj isn’t really an object, but it is empty, because there’s nothing in it.

Bug:

When I first started writing Objective-C, I made the mistake of writing code like: if([name isEqualToString:@""]){ ... }, to test for empty strings. And this code would work for a while until I used it in a situation where name was nil, and then, because sending any method called on nil “returns” NO, I would have mysterious errors. (Worse then a crash, because it’s harder to track down.)

Bug:

It’s tempting to avoid the previous bug, by explicitly testing for nil and {}. Say with code like:

if (email == nil || ![email isEqualTo:@""] )
   email = @"An email address is required";

But generally this is a bad idea. It means more code, which means more places for a bug. I know it’s only one trivial test, but I’m serious, when I say it’s asking for a bug — like the bug in the example above, which sets email to @"An email address is required", whenever it is not the empty string, rather then when it is empty. (Values have been changed tho protect the innocent but it’s a bug I’ve seen.)

Solutions:

Wil Shipley suggests using the global function:

static inline BOOL IsEmpty(id thing) {
    return thing == nil
        || ([thing respondsToSelector:@selector(length)]
        && [(NSData *)thing length] == 0)
        || ([thing respondsToSelector:@selector(count)]
        && [(NSArray *)thing count] == 0);
}

I’ve been using his IsEmpty() for about a year. I’ve had zero problems with it, while it’s made my code more readable and concise.

Another solution is to take advantage of what happens when you send a message to nil. (To over-simplify, you get back 0 or NO.) So you can just say “if ([obj count] == 0) then obj is empty.” This often means reversing your thinking, and testing “IsNotEmpty()” instead of “IsEmpty()”. I don’t think it’s as clear is IsEmpty() in general, but in cases where it is, there you have it.

December 12, 2008

One assert() You Need?

Accidently disconnected outlets in shipping Cocoa apps are legend.

Jonathan “Wolf” Rentzsch

From what I can tell assert() is slowly going the way of the goto in the programming world. Exceptions, unit-tests, and other modern software engineering practices, seem to have a better answer for testing something at runtime, and ensuring that you never enter a bad state.

But there is one case where I think you should put an assert() (and not an NSAssert()) in your code: in awakeFromNib assert() that every IBOutlet is connected.

It’s surprisingly easy to accidentally disconnect something in Interface Builder, or rename something in Xcode. I’ve done it before. More then once. assert()-ing IBOutlets has saved me a lot of debugging time.

Hard to Find In Code

An IBOutlet that is not connected is nil. Because Objective-C quietly ignores messages sent to nil, it’s very easy not to notice the problem for a while; then spend a long time debugging a side effect of the issue.

Hard to Find By Eye

A widget in a nib/xib file that is not connected will never change it’s state. But it typically it has a reasonable initial state. This makes it difficult to detect disconnected IBOutlets by eye, because things will look right until they are supposed have changed, but the eye is drawn to change, and de-emphasizes unchanging things.

Why assert()?

As Wolf says, the worst-case-scenario is that right before release you make a trivial change in Interface Builder (“We can’t ship with that window saying “Claculator”), an IBOutlet gets disconnected, and nobody notices in time.

But an assert() failing will be noticed with casual testing, and triggered if the nib is loaded at all, even if the series of interactions needed to cause the widget to change state are not performed.

An assert() is very light-weight and easy to do. It’s exactly one line of code, and you don’t have to add a unit testing framework to your project to do it. And that makes it perfect for test projects. I’m a believer in getting a feature working in a test project first, then copying it into your real product. (Honestly I don’t do it as much as I should, and I almost always kick myself for it.) It lets you test and learn without hacking-up your product. Plus, new things are often easier to try without the weight of a big code base.

Speaking of unit tests, I don’t have complete faith in them here. That’s because a disconnected IBOutlet in a shipping program is fundamentally an issue with the way the release-build is configured. So anything that’s not testing the the actual release build is not exhaustively testing for this problem. an OCUnit-test can catch a disconnected IBOutlet if it is loaded into the application. But in my experience, such tests are cumbersome. Running them involves launching your full application, and programatically manipulating it. This can take a while to run since all the UI will be displayed and animated. And you have to be careful about state since each test will be changing the state of your program.

Ultimately, assert() just seems to be optimal, in dependability and simplicity, for catching a disconnected IBOutlet.

But I suspect there’s a better way I don’t know.

How do you catch IBOutlets?

« Newer PostsOlder Posts »

Powered by WordPress