The Forkless Philosopher
Result Types
void vs. bool vs. integer vs. class
What is a good return value for a method? Think of a method that
deletes a license-plate number from a database:
void deleteFromDatabase(string lpn) { [...] }
How
will you know if the operation was successful? Perhaps connecting
to the database failed? Then the license-plate number will still
be in the database while you (or your code's logic) assumes it is
not. void
is definitely not a good return value.
Quite common is
boolean deleteFromDatabase(string lpn) { [...] }
,
with the convention that true
means that the operation
succeeded, whereas false
indicates failure. This is
better.
However, this is rarely expressed with an explicit boolean
return type; instead, int
is used, relying on the implicit
conversion of integers into bools that the C++ standard provides. The
definition of which integers constitute false
and which
true
can be found on page 71 of
The C++ Programming Language (3rd edition) by Bjarne
Stroustrup (Addison-Wesley, 2002): "nonzero integers convert
to true and 0 converts to false".
This allows constructs like
if (deleteFromDatabase("S12345F")) { [...] }
which is completely legal code, although perhaps not the most legible
one. To prevent the usage of such constructs, some people revert the
logic so that 0 indicates success and all other values indicate an
error.
Another reason to revert the logic so that 0 indicates success might be to mimic the behaviour of shell commands.
The third reason is more practical in nature. If you use 0 to indicate
failure and all other integers to indicate success, all you can do is
report the fact that the function call failed, but not the reason. But
what if Fred from the other department calls and says: "I need to
know why the operation failed. With some failures, I can
eliminate the cause and try again". Which is a legitimate and
sensible request, since it will make the software more robust.
This is the main reason to use a sort of logic where 0 indicates
success and all other integers indicate failure - you then have the
capability of communicating what exactly went wrong.
You can now also use an enumeration to use plain english instead of
cryptic error codes: EDBServerUnreachable
is so much more
descriptive than, say, -1
.
But if you are willing to create your own return type anyway, why not
go for a class (don't use a struct
; with a class you can
supply some methods like equals()
that might be useful in
unit tests)?
CDBOpResult deleteFromDatabase(string lpn) { [...] }
does not only leave no room for ambiguity in the interpretation of the
result (i.e. it is type safe), it offers you flexibility as well (e.g.:
CDBOpResult::getErrorMessage()
).
And be sure to hide the implementation details - don't use static
attributes to communicate results: public static int
CDBOpResult::Success = 0;
might lead developers to rely on
the implementation detail that CDBOpResult::Success
equals
0. Instead, provide methods like
bool CDBOpResult::opSucceeded()
and
bool CDBOpResult::opFailedNoConnection()
to communicate
the result of the operation.