“But People Never Check Return Values!”

The interface of a function, method or procedure, is composed of a number of inputs and output channels, and it is important to choose the correct type of channel to guarantee the function’s contract is being met by callers.

input channels can be roughly classified as follows:

  1. A set of values it explicitly receives through arguments
  2. The state of the object or application at the time it is invoked

and outputs:

  1. Explicit return values (including, for languages that support that, “out parameters”)
  2. Possibly some exceptions
  3. A new state of the object or application after returning
  4. A new state of the parameters

In this article I will focus on output channels. To each output may be associated some expectation in the function’s contract.

Errors and Other Unexpected Problems

For instance how do you inform the caller that there was a problem executing the functions, and how do you ensure they actually do something about it?

  • Some use return values, and it is well known that we always forget to check them. That’s one of the motivations for inventing exceptions in the first place.
  • Some use global error codes (after calling the function you are supposed to call another function to know the status) which is even worse.
  • Exceptions are one good way. You can ignore or swallow exceptions but at least it’s explicit in the code. Unlike the other two channel types, if you forget to handle the code then things will break loudly at runtime (or at compilation in case of checked exceptions).
  • Callbacks are another way which is useful in case you want the caller to handle errors while still having a chance to resume normal execution of the function (sax ErrorHandler is an approximate example of this although parsing methods do not take it as a parameter — it is set voluntarily with a setErrorHandler method)

Do not blame users of your API for not checking return values! Use another channel to signal errors instead, where they can’t ignore error conditions.

Remaining Work Left for the Caller

The previous point is rather well-known and well-understood by the programming community (I rarely see errors being carried through return values in languages that support exceptions) but the following one is less so.

Suppose your function is creating some sort of resource or doing some change in the global state that is supposed to be temporary. As part of your function’s contract, callers must eventually restore the original state. Examples:

  • Functions that allocate a resource like opening a file for reading, opening a database transaction, allocating some memory, etc, require the resource to be eventually released to avoid leaks.
  • Functions that register an event handler require the event handler to be released when it is no longer needed.
  • Functions that elevate the program’s privileges require the privileges to be released after they are no longer needed.

This can be implemented in several ways:

  1. The function return an object with a method such as close, release, unregister or somesuch.
  2. Same, but letting the returned object implement an interface telling the compiler it should be released (Autocloseable in Java).
  3. The function takes a callback, then acquires the resource, runs the callback and releases the resource (see Enforcing State Transitions)
  4. The function takes a scope object that is capable of notifying the function when its resource is no longer needed.

The first approach is Level 7: users will not close it until they notice and investigate performance problems.

The second approach is Level 2: compilers will warn programs that fail to close the returned object (either with a try-with-resources-type structure or explicitly). However this is not an option if the method calling the function is not the one that will be releasing it, as is typical for event handlers: the handler is registered from some initialisation method and must remain active even after the method return. It should be released from some cleanup method instead.

The third approach is Level 1, but again is only applicable if the resource is to be released from the same method that acquired it.

In such cases, the fourth approach, which is Level 1 should be used instead.

Given a Resource type that represents what must eventually be released…

public interface Resource {
  void release();

… and a Scope that defines when it should be released:

public interface Scope {
  void register(Resource r);

Any function that creates a Resource must receive a Scope:

public void addHandler(Scope s, Handler h) {
  Registration r = /* bind handler... */;

That code assumes that Registration implements Resource. Client code would then look like this:

public void MyPage extends Page {
  public void init() {
    element.addHandler(this, (... handler code ...));

where Page is assumed to implement Scope.

This design is Level 1, as client code can’t “forget” to pass a Scope object without having compilation fail. Obviously it is possible to create a Scope implementation that never releases the resources, but that can’t be done by mistake. It would be useful for things that must really stay up for the whole lifetime of the application.

In other words: don’t blame your callers when they forget to process the return values; if they are required to do something then just don’t use return values.

So, when should we use return values?

This post may make it look like return values are always bad and that using callbacks is always better. Obviously that’s not the case. The rule is simple:

Use return values for everything you want to return to the caller, for which the contract has no requirements.

Callers can “forget” to use the result of a complicated calculation, but aside from wasted processing power there’s nothing harmful in that. A possible exception would be methods modifying an immutable structure (by returning a new object). For those, not using the returned object is almost certainly a mistake by a programmer thinking he was modifying the object in place. Preventing such mistakes would be a topic for another article…

Leave a comment

Your email address will not be published. Required fields are marked *