I am writing a perl framework with many modules. Now for any such module there is a SUCESS and many levels of FAILURE.
A Failure could be because it does not satisfy a condition or it may be due to some library failure or a remote machine not working.
In my present implementation I am passing different return codes (0 , 1 , 2 etc) to signify different scenarios.
I wanted to know if there is a more graceful way as the current logic looks as if it is hardcoded.
As mentioned many times in the comments, instead of overloading the return value, what you really want to do is throw an exception. Mixing successful values and error codes in the return value complicates using the function, both getting the value and detecting error, which generates bugs. There are several modules on CPAN to make throwing and catching exceptions easier. Try::Tiny, TryCatch, Throwable and Exception::Class are examples.
There is a seductive, false economy to using error codes. It is assumed that these two things are equivalent.
Exceptions look like so much more code! Except its a false equivalency. If all you’re going to do is die on error, and most of the time it is, these are the actual equivalent return value & exception samples.
If you return an error flag, the user always has to check for an error even if all they’re going to do is immediately die. If you use exceptions, the user only has to check for error if they want to do something special about it. Something which is an exception to the norm, you might say.
And that is the great economy and safety of exceptions. Its less work for the user, and they cannot be accidentally missed.
Now that’s been said, and really you want to use exceptions, here’s several ways to do what you want. This is both for completeness and to illustrate where each of the falls down. This is an old, old, old problem in computer APIs, long predating exceptions, so there’s lots of “clever” ways to solve it. All of them have many serious issues compared to exceptions. I’m sure I will miss some, but this is more about illustrating just how far down the rabbit hole you can go.
1) Return false and set a global variable to the error code.
This is pretty common in a lot of libraries such as DBI (which also, btw, recommends you use exceptions instead) before the Perl community embraced exceptions.
Its also how Perl does it.
This has the universal problem of returning an error code: it fails quiet. If the user forgets to check for error, the program runs without so much as a peep failing somewhere else down the line, possibly mysteriously, possibly taking an agonizingly long time to debug. Exceptions fail loud. Forget to check for an exception and at least you still know the source of the problem.
One of the biggest mistakes (newbie and experienced) is to forget to check for an error, which is why modules like autodie are so incredibly awesome.
The second problem it has is it only works if you immediately check the error flag. The flag is a global variable and if another error happens it will blow over yours. You might think “well that’s no problem, it’s always
function() or die $error_flag“. Unless…What if
another_functionalso has an error? Now the internals of the library must be careful to always set the error flag and immediately return. Any system which involves “being careful” is doomed to failure.This problem plagues
$!in Perl. Many things can set it and sometimes Perl calls multiple internal functions which may set and reset it in a single Perl function call.2) Return a simple success code and a complex error code.
This is how shell does it. 0 is success, everything else is an error code.
I hope the problem is obvious: you can’t return an interesting value. It also reverses the natural true == success, false == failure convention which is confusing and error prone. See also
system.It is also easy to confuse a really bad error which causes
undefto be returned with success.3) Positive value is success, negative value is failure.
This is 2, but now you can return interesting values… as long as they’re numbers. Also all your error values have to be numbers which you must then look up in some table somewhere. Joy.
At least true is aligned with “success”, and both 0 and undef are failure, that’s something. But negative numbers are also true. The user is very likely to make a mistake, just check for a true value, and miss the error code.
4) Return an overloaded object which is false.
This is about the best you’re gonna get without exceptions.
All true values indicate success. All false values indicate error. Error is indicated by returning an object which has its boolean value overloaded to always be false and its string value overloaded to return its error code.
True is success, false is error. Errors can’t overwrite each other and you can rich and interesting error information. Its not perfect, one of the problem is you cannot have a successful value which is false, restricting what you can return, but it’s pretty good.
However, since you need the return value, you can’t use the
or dieidiom.It still suffers from the universal problem of returning an error code, the user has to check for it. This is the biggest problem any error handling system will have, and its one only exceptions solve.
5) Pass in a reference for the return value.
I’m including this one to show just how far you can go with this. Instead of having your function return a value, it returns an error code. How do you return the value? You pass in a reference which the function sets with the value! Sound crazy?!
Oh.
This is a recommended style in many languages which don’t support exceptions.
You can tell if there’s an error without interfering with the return value, at the expense of an awkward and unchainable interface, but you’re left unable to return any information about the error. You either have to invert true/false success/failure to return an error code or string (so false is success), or you do what open does and have a side global variable.
It makes sense, but only in relative terms, to return the value and instead pass in a reference to catch the error.
This I have never seen in the wild, but it works out pretty well. If the biggest problem with returning error codes is that the user will ignore them, forcing them to pass in an error reference as the first argument would be a nice way to shove it in their face.