Design Patterns – Principle of Least Astonishment (POLA) and Interfaces

designdesign-patternsinterfaces

A good quarter of a century ago when I was learning C++, I was taught that interfaces should be forgiving and as far as possible not care about the order that methods were called since the consumer may not have access to the source or documentation in lieu of this.

However, whenever I've mentored junior programmers and senior devs have overheard me, they've reacted with astonishment which has got me wondering whether this was really a thing or if it has just gone out of vogue.

As clear as mud?

Consider an interface with these methods (for creating data files):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

Now you could of course just go thru these in order, but say you didn't care about the file name (think a.out) or what header and trailer string were included, you could just call AddDataLine.

A less extreme example might be omitting the headers and trailers.

Yet another might be setting the header and trailer strings before the file has been opened.

Is this a principle of interface design that is recognised or just the POLA way before it was given a name?

N.B. don't get bogged down in the minutiae of this interface, it is just an example for the sake of this question.

Best Answer

One way in which you can stick to the principle of least astonishment is to consider other principles such as ISP and SRP, or even DRY.

In the specific example you've given, the suggestion seems to be that there's a certain dependency of ordering for manipulating the file; but your API controls both the file access and the data format, which smells a bit like a violation of SRP.

Edit/Update: it also suggests that the API itself is asking the user to violate DRY, because they will need to repeat the same steps every time they use the API.

Consider an alternative API where the IO operations are separate from the data operations. and where the API itself 'owns' the ordering:

ContentBuilder

SetHeader( ... )
AddLine( ... )
SetTrailer ( ... )

FileWriter

Open(filename) 
Write(content) throws InvalidContentException
Close()

With the above separation, the ContentBuilder doesn't need to actually "do" anything apart from store the lines/header/trailer (Maybe also a ContentBuilder.Serialize() method which knows the order) . By following other SOLID principles it no longer matters whether you set the header or trailer before or after adding lines, because nothing in the ContentBuilder is actually written to file until its passed to FileWriter.Write.

It also has the added benefit of being a little more flexible; for example, it might be useful to write the content out to a diagnostic logger, or maybe pass it across a network instead of writing it directly to a file.

While designing an API you should also consider error reporting, whether that's a state, a return value, an exception, a callback, or something else. The user of the API will probably expect to be able to programmatically detect any violations of its contracts, or even other errors which it can't control such as file I/O errors.