I get to do some command line interface design work here at Sun. This sometimes is surprisingly valuable. The overal range of issues around command line interface design is much smaller than in graphical design. You don't need to worry about spacing or colors, dialogs or windows, asynchronous messages or dynamic layout. The "skeletal" nature of the design work makes the issues between architecture design and interface design much more visible. This, in turn, gives me a much clearer window into the classic programmer/interface-design differences. This fascinates me because it is often the vast differences between these two necessary disciplines that produces difficult-to-use software.
(I also won't deny that one of the fun things about doing design work in the command line arena is that there seems to be very little knowledge about this topic in the UI field, which makes every usability study I do a rich source of new insight into how people use software).
Anyway.
Recently I had my programmer's hat on. Which is to say I was coding. I was actually writing a small command line utility that I was going to hook into a set of scripts I have for doing web page creation.
I wrote this:
int main(int argc, char** argv) {
int result = 0;if (argc < 2) {
printf("%0: Usage: %0 file1 file2\n", argv[0], argv[0]);
} else {
Let's ignore that I should have been using getopt() or getopt_clip() or getopt_long() here. This utility will never be used by anyone but me(tm), so it's OK(tm).
I couldn't help but look at that printf() statement and wonder about the usability monster I'd just created. Personally, I really don't like these utilities that, when you give them some kind of bad syntax, report back some very terse usage message.
$ gstlcfg -a suger
gstlcfg: Usage [-abcdefgh] type
If you are familiar with the utility, these usage messages are great, because usually you just did something foolish and the message will remind you in a way that will allow you to quickly fix the problem.
If you aren't familir with the utility, however, the message can be useless. Or, worse, it can make you feel insecure about the utility and even the whole command line environment. Bad user experience.
So, giving a better message there might be good:
printf("%0: You must pass two file names to this utility, but you appear to have inclued less than two.\n", argv[0]);
I suppose that's more useful (but, we've just lost the benefit of terseness to the frequent user of the utility). But, there's another problem here. There are other error conditions that can happen later in the utility. For instance, I might have included two names, one of which doesn't refer to any file. Later on in the code I'd discover that, and probably report a different error message (e.g. "The file (%s) does not exist.").
Now, if you look at these messages from the end user's standpoint, you'll realize you'll get two very different messages for what are almost the same error condition to the user. It isn't that the two messages couldn't be almost the same, it is that when coding I'd lost sight of the fact that these two points in the code, which were many lines apart and created far apart in time, were close together to the user:
$ myutility foo
myutility: Usage: file1 file2
$ myutility foo bar
myutility: The file (bar) does not exist.
As a developer, of course, I understand that the latter message is different because it is happening at a different time in the parsing process. But, not all CLI users are developers. This is just "randomly" different messages.
The point is that while I was typing in the code here, I "discovered" the first error condition and I wanted to deal with it as quickly as possible and get on with writing additional logic in my utility. So, I dealt with it in the best way I could at that moment. While I was concerned about the user's needs, I wasn't concerned enough to step back and say "What is the overall set of experiences the user will have at this point in using the utility? And what's the best way of reporting the message". Indeed, my message was actually pretty self-focused. I wanted two filenames, and I didn't get them so I was complaining "you didn't give me to filenames".
Having satisfied myself that I was letting the user know something so they could correct the problem, I turned my attention back to adding more logic. When I found the second error condition, I did much the same thing ("Yo! I need a REAL filename here"). These conditions were far apart in my code, and because my attention was strongly focused on the point I was in the code I didn't really realize that this was the same to the user as the condition I'd dealt with earlier.
So, the next question is: how could I have prevented this small problem. How could I have made sure that as a developer I would have generated better error messages for the end user without disrupting my goal of creating functionality?
Part of the answer to that would be to have had an interface design in my hand before I started. That way, when I found the error condition I could look up in the spec "Oh, I'm supposed to give this message". Lacking a spec, I could have relied on some general guidelines (e.g. "Check all command line arguments before starting actual processing, and when there is an error report the message in this format." In both cases I could have then reported "the right" thing and gotten on with my work well.
At the heart of this issue, the fact of the matter is that identifying all the error conditions, and how they will appear to the user as the user uses the utility is actually a non-simple task. It requires looking at the software as a whole, while ignoring how it will be implemented. Good design would mean considering the needs of the various classes of users and making sure the information presented meets all of their needs adequately. In this case, doing that well would take almost as long as it took to write the code for the utility. As a developer, in the flow of coding, I am simply not going to step away from my work long enough to do all that analysis. It ruins the flow and (as I've pointed out earlier in the blog) takes a substantially different mindset to accomplish.
As a result, it is natural (even necessary) to downplay the importance of the interface issues to focus on getting the logic created. And, from this we get software that is difficult to use.
I guess the moral here is that interface design should be done not while coding, but separately. That way, each task can take advantage of the different mental skills involved in each, and neither disrupts the others flow. Of course, when you are dying to start coding the design work seems like a burden and nuisance. And when you are dying to do the design, the coding seems like it's leaping before looking.