To throw or not to throw? Doing something that has already been done

This post is more than 6 years old.

Posted at 09:00 on 08 February 2018

The general rule for throwing exceptions (or, in Go, returning an error code) is that your function could not do what its name says that it does. This is pretty clear-cut most of the time, but there are corner cases where it's ambiguous as to whether your function was actually successful or not.

Attempting to do something that has already been done.

Is it an error condition when your method is trying to do something that has already been done? You could argue it both ways. On the one hand, your method hasn't been able to do what it was asked to, so it could be regarded as an error. On the other hand, the outcome of your method is what you wanted anyway, so in that sense it isn't.

Returning success in such a condition is called idempotency. It is convenient, since it allows you to just call the method without including a superfluous and unnecessary check, and personally, it's the option that I would prefer.

Unfortunately, there doesn't seem to be a clear consensus on this one.

The task of creating a directory (together with any missing parents) in your filesystem is a case in point. By default, Python's os.makedirs function throws an OSError exception, though Python 3.2 added an extra argument, exist_ok, to allow you to specify idempotent behaviour.

On the other hand, in .NET, the System.IO.Directory.CreateDirectory method takes the idempotent approach. If the directory already exists, it does not throw an exception, but merely returns a DirectoryInfo instance for the existing directory.

What was done previously was not exactly what you wanted.

Unfortunately, it isn't always that clear cut, and things can get a bit messy. What happens, for example, if you have asked your method to create a directory with some security rules that don't match what's already there? Both Python and .NET (in later versions of the .NET Framework, though not in .NET Core) give you this option.

In .NET, the method simply returns without doing anything. Python 3.4.1 or later does the same when the exist_ok argument is set to True. However, this was not always the case. Python 3.2 through 3.4.0 threw an exception in this case. In theory, this should have been the correct thing to do, but unfortunately it introduced a race condition and it was not possible to fix it safely, so this was removed.

Recommendation: Always check the documentation for any methods that you call, because there is not much in the way of consistency here. But if your own function is seeking to do something that has already been done, return success by default.