« Archives in June, 2011

Dangerously Confusing Interfaces

confused.jpgDesigning intuitive interfaces that are easy to use and easy to learn is hard, often very hard; and for economic reasons it might not always be possible to strive for perfection. Nevertheless, in my view, at the very least, interfaces should be designed such that obvious, day-to-day usage doesn’t lead to damage.

In his classic book “Writing Solid Code”, Steve Maguire calls confusing interfaces that lead to unexpected bugs “Candy Machine Interfaces”. He tells a story from a vending machine at Microsoft that used to cause him grief: The machine displayed “45 cent” for “number 21”, but after he had finally inserted the last coin he would sometimes enter “45” instead of “21” (and would get a jalapeño flavored bubble-gum instead of the peanut butter cookie that he wanted so much — Ha Ha Ha!). He suggests an easy fix: replace the numeric keypad with a letter keypad and no confusion between money and items would be possible anymore.

The other day I did something like this:

My goal was to recursively copy the ‘gamma’ folder to my home folder. What I expected was a ‘gamma’ folder within my home directory, but instead I ended up with hundreds of files from the ‘gamma’ directory right at the top-level of my home directory — the ‘gamma’ directory simply wasn’t created!

I have to confess that similar things sometimes happen to me with other recursive-copy-like tools, too — this seems to be my candy machine problem. Now you know it.

As for ‘rsync’, there is a feature that allows you to copy just the contents of a directory, without creating the directory, flat into a target directory. Granted, this is sometimes useful, but do you know how to activate this mode? By appending a trailing slash to the source directory! That’s what happened in my case. But I didn’t even add the slash myself: if you use Bash’s TAB completion (like I did) a trailing slash is automatically appended for directories…

But good old ‘cp’ puzzles me even more. If you use it like this

it will copy ‘from3’ to a folder named ‘to2’ under ‘to1’ such that both directories (‘from3’ and ‘to2’) will have the same contents, which is more or less a copy-and-rename-at-the-same-time operation. Unless ‘to2’ already exists, in which case ‘from3’ will be copied in ‘to2’ resulting in ‘to1/to2/from3’. Unless, as an exception within an exception, there is already a ‘from3’ directory under ‘to2’; in this case ‘cp’ will copy ‘from3’ flat into the existing ‘to2/from3’ which might overwrite existing files in that folder.

Both, ‘cp’ and ‘rsync’ suffer from fancy interfaces that try to add smart features — which is normally good — but they do it in an implicit, hard-to-guess, hard-to-remember way — which is always bad. Flat copies are sometimes useful but they might be dangerous as they could inadvertently overwrite existing files or at least deluge a target directory. A potential cure could be an explicit ‘–flat’ command-line option.

To me, a wonderfully simple approach is the one taken by Subversion: checkouts are always flat and I’ve never had any problems with it:

This copies (actually checks-out) the contents of the ‘trunk’ flat into the specified destination directory — always, without any exceptions. That’s the only thing you have to learn and remember. There are no trailing backslashes or any other implicit rules. It will also create the target parent directories up to any level, if needed.

Naturally, dangerously confusing interfaces exist in programming interfaces, too. Sometimes the behavior of a method depends on some global state, sometimes it is easy to confuse parameters. The ‘memset’ function from the C standard library is a classic example:

Does this put 40 times the value of 32 in ‘buffer’ or is it the other way around?

I have no idea how many programmers don’t know the answer to this question or how many bugs can be attributed to this bad interface, but I suspect that in both cases the answer must be “way too many”. I don’t want to guess or look up the specification in a manual — I want the compiler to tell me if I’m wrong. Here is an alternative implementation:

Now you write

If you confuse the fill character with the length parameter the compiler will bark at you — a parameter mix-up is impossible. Even though this is more to type than the original (dangerous) interface: it is usually worth the while if there are two parameters of the same (or convertible) type next to each other.

Like I said in the beginning: designing intuitive interfaces is hard but spending extra effort to avoid errors for the most typical cases is usually a worthwhile investment: don’t make people think, make it difficult for them to do wrong things — even if it sometimes means a little bit more typing.

The Principle of Proximity

pebbles.jpgIt is common wisdom that opposites attract. In programming, however, it is desirable to keep things that are related together — that’s at least what the “Principle of Proximity” states.

This principle has many manifestations, some of which are well known by most software developers, for instance:

-Keep the documentation (comments) as close as possible to the code;
-Initialize variables as close as possible to the point where you use them;
-Limit the scope of declarations (i. e. use namespaces and don’t make constants public if private is sufficient);

As opposed to opposites, related things not always attract, or — as a matter of fact — attract in a suboptimal way.

Here is an example. Assume that you have to process a list of different objects (let’s call them “boxes”, for the sake of this example) that you have just received, maybe over a socket connection. This list always consists of a blue box, a red box, and a green box, exactly in that order. These boxes are encrypted and protected by an integrity checksum. Before actually processing them, you need to perform decryption and integrity checking. (Also assume that the boxes are completely different. They have different content, different security mechanisms, and require different processing.) Below is one way to go about it:

At first glance, this code doesn’t look bad at all. It is grouped in such a way that the three steps are clearly visible: 1. get a box; 2. apply security to box; 3. process box. If you zoom out a little, the structure looks like this:

Is this the principle of proximity in action? Are related things close together?

Not really. The things that are close together are the objects under each operation, but the objects themselves have little in common. Contrast this with this approach:

The structure is now inverted:

The objects and their operations are close together; in fact, they are completely encapsulated. I like to call this ‘encapsulation at runtime’, which is not to be confused with traditional object-oriented encapsulation where you put data and its related operations close together at coding time, in a class. (Which is another instance of the principle of proximity, BTW.)

What I don’t like about onReceiveBoxes1 is that it mixes up things that are unrelated: order of boxes and order of box actions. Just because the boxes are ordered in a particular way, doesn’t mean that we have to perform the box actions in that particular box-order. Unnecessary dependencies are usually bad for maintenance.

Ah, maintainability, that’s where the second implementation really shines! If you have to add a yellow box someday, you just copy and paste the block of an existing box and do some minor modifications. And if the order in which boxes arrive changes, adapting onReceiveBoxes2 is likewise trivial. Better maintainability means that the risk of introducing an error is much lower, which in turn means that you spend less time debugging and have more time for doing code katas.

Honoring the principle of proximity almost always gives you better efficiency, either. Notice that in the first implementation, the pointers to all boxes have a fairly long lifetime and must be kept in memory (or CPU registers) as they are needed until operation 3 has finished. onReceiveBoxes2 only needs a pointer to the box that is currently worked on, which means that the compiler only needs to allocate one pointer.