I don’t know how many hundreds of functions I’ve dealt with with either a void or boolean return type. While a boolean return type at least tells us whether the function ever completed properly, a void return carries absolutely no meaning. Where’s the debugging or error-handling value in that?
Constructor functions take a number of arguments and return a pointer to an allocated space. Even so, there is a simple rule for meaningful returns:
Constructors return either a newly allocated pointer or NULL
Of course, if you work with a slightly more complex constructor, you can return the pointer in a parameter. In these cases, you should still make the constructor return something meaningful.
Personally, in these cases, I use a tri-state return type. If the function is successful, I’ll return the size of the allocated space (in bytes). If it has an error, I’ll return a negative value correlating to the type or location of the failure. However, if the function works EXCEPT that the malloc does not successfully allocate any space, I’ll return 0.
Simple Access Functions
A simple access function returns some meaningful data value from a structure. In these cases, we return the value that we retrieved.
Anything that can possibly fail (even when it can’t)
If it’s possible for a function to fail, we return that value inside of a parameter and use the basic rule:
Return 0 on success and non-zero on error
This basic rule applies generally, across the board. Even when the operation has no chance of failure (a state which is less common as I construct better code), we return the 0 value.
Debugging and Logging errors
As systems grow in complexity, the number of locations where an operation could fail increases.
Further, as the size of a function increases, the number of ways it could fail increases.
When we employ meaningful return types, we create a path directly to the area where the problem occurred. So long as we know that function panda failed with an error value of -5, we know where the error was triggered (even if the system is thousands or millions of lines long). Even better, if we designed our return codes around specific tests, we know exactly how the function failed.
This means that, without ever touching the debugger, we have identified the location of our failure and can immediately begin determining the sequence of events that led to failure.
As Martha Stewart would say, “It’s a good thing”.