The Principle of Least Astonishment
states that the result of performing some operation should be obvious, consistent, and predictable, based upon the name of the operation and other clues.
For example, the name of a function should reflect what it does. Otherwise, a user of the function will be unpleasantly surprised:
int multiply(int a, int b)
return a + b;
int multiply(int a, int b)
return a * b;
int add(int a, int b)
return a + b;
int write_to_file(const char* filename, const char* text)
printf("%s\n", text); /* Note that 'filename' is unused */
In addition to function naming, the principle applies to user interface design. Menu items and other controls should have an effect that is obvious based upon their text labels and placement relative to other controls.
This principle seems highly dependent upon the culture of a language, domain, and product. I suspect it rather strongly subject to psychology, fad, idioms, etc. That doesn't (by itself) make it a bad principle, but it does make it a difficult
principle to apply in any meaningful long-term sense, especially in such long-lived entities as language design and language standard libraries. Things that astonish programmers today might not astonish them in a decade... and vice versa. In thirty years, perhaps they'll be teaching rings in high school math courses, and it won't be at all to think of add as a 'multiply' for a particular topology in a ring of numbers.
I would suggest there are much better principles for user interface design than attempting to minimize astonishment. I don't mean that you should maximize astonishment, just that one could rank it much lower than most other features. Having a system that is 'transparent' in the sense used by EricRaymond
(chapter 6) is probably more relevant: Software systems are transparent when they don't have murky corners or hidden depths. Transparency is a passive quality. A program is transparent when it is possible to form a simple mental model of its behavior that is actually predictive for all or most cases, because you can see through the machinery to what is actually going on.
The examples above are a little weak, as they are about a very simple function which has one obviously right behaviour and one obviously wrong behaviour. Perhaps an example of the GUI case that was mentioned would be more useful.
I'll keep an eye out for surprises and jot them down here when i find them. -- TomAnderson
All of the major Smalltalk language bugs are violations of the principle of least astonishment. Smalltalk spends a lot of time building up expectations in users that the system will behave in a certain way. The major language bugs are where it violates its own expectations.
- collections #add: and #at:put: messages return the operand. The expectation is that a non-querying operation returns self.
- if you add instance variables to a subclass of Collection, these variables will periodically be clobbered because #grow only copies predefined instance variables instead of intelligently copying all of them. This violates the expectation that behaviour is local; you shouldn't have to override #grow just because you add an instance variable.
- initialize isn't defined in Object. Methods which are used everywhere should have some default definition in a common superclass (ie, Object in this case).
- Not necessarily true, initialize is defined in ProtoObject? in Squeak, providing exactly the default definition you'd expect.
- adding a period to the last expression in a method makes it return self if evaluation proceeds to the end. The expectations are that a bloody useless period shouldn't have major consequences, and that a method should return the value of the last expression it actually evaluates.
- Actually, I'd expect the method to return self unless something else was explicitly returned with ^, so it works exactly as expected.
- there should be a method to recursively copy an object and all of its subparts, and this method should be called deepCopy. The actual deepCopy doesn't do that; it does something pretty useless.
there's a couple more like that.
Now meditate on the fact that Smalltalk's violations of the principle of least astonishment are actually enumerable.
Joel Spolsky has written a text about user interface design in which he refers to this principle.
An example of a function that does not follow this principle is the standard C++ remove()
function template. When applied to a collection, someone who has not studied the documentation might expect it to "remove" (that is, delete) things from the collection. But it doesn't--remove()
) instead reorder elements so that the "unremoved" elements are at the head of the collection, and the size of the collection does not change. It makes sense if you read the docs, but it can be astonishing to those who haven't.
C++ is astonishing in that Stroustrup wrote a book (TheDesignAndEvolutionOfCpp) full of examples where PrincipleOfLeastAstonishment is violated, yet after reading it they mysteriously make sense...
Violating the PrincipleOfLeastAstonishment
leads to odd questions. A "real world" example of such questions is W. C. Fields, "Who put pineapple juice in my pineapple juice?"
Real example, from real code:
xor ax, ax # put 1 in ax
This puts 0 in ax, not 1; the code is correct but the comment is wrong. I was fortunate enough to be privy to the etymology of this snippet. ax held a command code: 0 did one thing, 1 did another. The line originally read "mov ax, 1" but later it was decided to use the other command code. It was changed to "mov ax, 0", and in a criminal bit of neglecting documentation, the comment was left. Later, an Old School Hacker saw the "mov ax, 0" and changed it to "xor ax, ax", because old 8086 machines could do it in 1 clock but the mov took 2 clocks. Never mind that it violates the RulesOfOptimization
(since the days of the 386 "mov register, immediate" took 1 clock), that comment is terrible (we tend to believe documentation no matter what). Since the comment tells you what the code is doing and not what it's supposed to be doing or why, I decided the correct action was to remove the comment entirely. -- DavidBrady
Note that using xor to zero a register is still an optimization: the instruction itself takes up less space. (for Intel CPUs)