The area of design has a very elegant principle named The principle of least astonishment. Sometimes it is also referred to as The principle of least surprise. It means that you should not do something that surprises the user, such as having a close button opening a new page, a trick that many spammy websites use.
This principle is very important in design because it helps guide the creation process. It guarantees that the design that is produced is easier for the user to use.
A great thing about this principle is that it is very easy to understand and we immediately can relate to situations where we felt it was violated.
There are principles in the area of software whose goal is also to guide the final product into a state where it has some desired properties, usually that it is easy to read and that it can easily withstand change.
Unfortunately software is more abstract than design. It is harder to visualise. Its principles are frequently misunderstood, and people tend to learn them from word of mouth. For example, the SOLID principles. Would you say most developers you know have read the original papers where they were introduced?
I, for example, have seen the single responsibility principle being invoked for the most bizarre things. One of the ones I’ve heard most often is for justifying putting everything that is remotely related in a single class, almost the opposite of what the principle means.
Maybe we need simpler principles.
Not that we don’t need the ones we already have, but maybe we can look at other areas and adapt their principles to software, especially the ones that we can visualize and relate to. These might be especially useful for novice programmers because they are easy to understand.
The principle of least surprise is one of them surely, and often people already use it in the context of software design.
Another principle that might be useful (and this one as far as I know hasn’t been used in software) is the principle of reasonable expectations.
It is a legal principle in that a contract should be interpreted as how a reasonable person (who is not trained in the law) would interpret it. It favours the objectively reasonable expectations of the weaker party (the reasonable person) even when the language of the contract does not explicitly supports them.
This principle emphasizes the “consumer” of the contract, in software we could draw a parallel with the person who has to maintain or use the code. And this person is not necessarily a different person than who wrote it. Given enough time, even the code that we wrote becomes as alien as anybody else’s.
The principle says that we should not violate reasonable expectations. Here are some examples:
In older versions of jQuery UI (1.8 for example) if you would check if a dialog was open, and it wasn’t, the method would return the DOM object that was queried instead of false, i.e.:
var isOpen = $('#theDialogContainer').dialog("isOpen");
if (!isOpen) //isOpen is a DOM object
$('#theDialogContainer').dialog();
If this was the click handler for a button, it would never open the dialog because of how JavaScript evaluates conditions. It uses falsy and truthy values, and a DOM object is a “truthy” value.
One could almost argue that the concept of falsy and truthy values is a violation of the principle of reasonable expectations, however, because it is common knowledge for JavaScript developers it falls into a grey area. Nevertheless, no one would lose if the snippet above was rewritten this way:
var isOpen = $('#theDialogContainer').dialog("isOpen");
if (isOpen === false) {
$('#theDialogContainer').dialog();
Other examples include things that the method does that it is not reasonable for someone to expect, for example, imagine you are using an API to control grids of elements (imagine a webpage with a dynamic grid/table of results). If this function is called: grid.select(element);
, the row that contains the element
gets highlighted and the page scrolls to it. The second part, the page scrolling to the row, is not something that a consumer of the API would expect just by reading the method name.
Let me just give you an example of how this can become problematic.
Say that you want to implement filtering. Every time the user filters the results, the previously selected element can be filtered out. If this happens you want the first element of the grid to become selected. It would be more than reasonable to use the select
function to achieve this.
Up to this point, even after adding filtering it is unlikely that anyone using the select
function would notice that it not only highlights the row where the element is at, it also causes the page to scroll to that element. This is because the filters are on top of the page, when they are used the first element is also visible, so the scroll function has no visible effect.
Time passes and a new requirement arrives for having keyboard shortcuts for commonly used filters. Now the user can cause the results to be filtered anywhere from the page. If the user scrolls to the bottom of the page and uses the shortcut to filter the results, the page sometimes (when the selected element is filtered out) jumps to the top.
The person tasked with resolving this bug will likely look for it first in the code that handles the keyboard shortcut that triggers the filtering. When that fails, it is also likely that eventually when the code that handles the filtering gets looked at, the select(element)
method is not considered to be the culprit. This is because it is not reasonable to expect (unless the person solving the bug is familiar with the implementation) that selecting a element causes the page to scroll to the row that contains that element.
This problem could be solved by separating the function into two other functions and name them in a way that what they do is clear, for example, grid.highlight(element)
and grid.scrollTo(element)
.
This example nicely illustrates why I think this principle has a place even though it may seem similar to the principle of least astonishment. It is easy to imagine whoever wrote the grid.select
function to argue that it is not a surprise for the page to scroll to the row that contains the element when that element is selected. However, it is not reasonable to assume that someone who is not familiar with the implementation of the select
method to expect it to have that behaviour.
Another useful thing that this principle surfaces is that if something provides no reasonable expectations, then it is a violation of the principle. Consequently, naming functions
as for example handleTriggerFailed
will violate the principle since there is not much that the reader of the code can infer from that method name. Other examples that apply to class names: AbstractService
, SomethingManager, SomethingHelper
, etc.
The principle of reasonable expectation puts the focus on the interpretation that the reader of the code is likely to have. It underlines the fact that the code’s main target audience is first the people and then the compiler. If that wasn’t the case we would all still be writing assembly.