How and why return values work for me

1

FEB

11

How and why return values work for me

In this article I want to talk about how and why return values (rv) are my preferred way of handling errors. I will also try my best not to contribute to the religious war taking place between the two prevalent error handling factions, i.e. exceptionalists and value returners. I rather want to counter misinformation about return values and help to overcome some persistent prejudice.

How rv works for me

The basic idea of the concept is that functions return a data structure, namely a hash (rv), indicating either success along with its actual return value(s) or failure together with a meaningful error message. The requesting method receives the hash, checks the status and proceeds according to the application logic which in case of failure might be to prevent any further action or attempts to save the day. A piece of code is worth a thousand words:

sub readFile {
   my $fl = shift(@_);
   local $/=undef;
   if( ! open FILE, $fl ) {
      my %rv = (
         bOK => 0,
         sErrMsg => sprintf( "Could not read file: %s, %s", $fl, $! ),
      );
   } else {
      binmode FILE;
      $sContent = <FILE>;
      close FILE;
      my %rv = (
         bOK => 1,
         sContent => $sContent,
      );
   }
   return %rv;
}
Implementation of an "rv"ed function that reads a file in Perl.

The function above tries to get the content of a file. In case everything went fine it will send back an rv with key "bOk" set to true and a key "sContent" populated with the content of the file. However, if an error happens "bOk" is set to false and the key "sErrMsg" will tell us what went wrong. This function is called by the piece of code below which backs up a database by creating a dump file, replacing an old backup file after positively checking the new one, then clean up.


sub backupDatabase {
	my %rv = createDatabaseDump();
	if( $rv{ 'bOK' } ) {
		%rv = readFile();
	}
	if( $rv{ 'bOK' } ) {
		%rv = checkFile();
	}
	if( $rv{ 'bOK' } ) {
		%rv = replaceBackup();
	}
	if( $rv{ 'bOK' } ) {
		%rv = deleteTempFiles();
	}
	return %rv;
}
The controlling method for backing up the database.

We check the "bOk" key after every function call which means if e.g. reading the dump file fails we skip the analysis of the file as well as the replacing and cleanup actions. The rv is returned to the invoking function which then decides on further actions, e.g. notifiying the administrator.

Looking at the above code snippets we see, that we will never continue in the process of backing up once an irregularity occurs thus avoiding possible damage done to the system otherwise e.g. replacing an existing backup with a broken file. We use the proper place to create a meaningful error message namely the very function it happens. We use the proper place to handle success or failure that is the requesting, thus controlling function. All in all a simple yet solid concept.

Why rv works for me

In the following I list the key benefits you gain when using this technique.

What you see is what you get

You see what the application is doing right in front of your nose. You do so not only in case everything works fine but also - and especially - if things go wrong. This puts you in a position to read through the controlling function and see all possible paths the application was conceived to take, all in one place.

What you don’t see is what is missing

If you review the code an spot a missing rv assignment for a function you immediately smell a potential bug. Again, you just have to open your eyes and see wrong code right in front of the same nose.

Support by any language

Any language supports the rv pattern since its only requirement is support for hashes. You don’t have to fall back on built-in error handling who’s implementation is likely to be different or lacking at all. Being a true least common denominator you can use rv for every piece of code in your projects. No more switching of your mindset because of different error handling techniques.

Uniform handling of all situations

Success and failure is handled exactly the same way namely creating and analysing an rv hash. You don't have to switch paradigms handling one or the other. In addition this has a psychological side effect: Irregularities are not considered an exceptional thing but a natural part of an application. This decreases the chance of spending too much, too little or no attention to either one of those "brothers in code".

It works well across system boundaries

Since an rv is a simple hash it is perfectly suited to be encoded as JSON string. As such we can happily pass it around among all sorts of systems that are also "rv"ed. As soon as it enters a new environment that JSON string simply gets decoded and treated like any other rv returned by a function. There is no need for a special interface to interpret or massage results from a different system; any successful or erroneous result is seamlessly fed into the receiving system.

Take this blog: you clicked on the link to load this article. The content of the HTTP package that is sent back in response would contain a JSON string like {"bOk":"true","htmPost":"bla fah sel"} the client evals that JSON and integrates it into the application logic, i.e. writing the value of rv[ 'htmPost' ] in a respective DOM node if rv[ 'bOk' ] is true.

In case the DB layer triggers an error it composes a different rv e.g. {"bOk":"false","sErrMsg":"The record with id 123 does not exist in table 'Post'."} and passes it back trough the server side apps right to the front controller being the intermediate requester. She in turn puts the entire rv "as is" into the HTTP response and replies to the client who then takes the error path e.g. informing the user about the oops. She might even use the content of the sErrMsg key to further explain the issue.

Works well with asynchronism.

As a consequence of the above mentioned rv is perfectly suited to work in asynchronous situations such as Ajax calls. For readability I prefix callback functions that serve as re-entry points with "rv".

Why rv doesn’t work for others

In the following the most popular counter arguments I came across along with my notes. Please feel free to add the ones that I missed.

Poor error information

This argument most probably stems from the mere fact that people rarely consider returning more than an integer when thinking about return values. In fact, the use of a hash makes the error information as rich as it can be.

There is no stack

Unfortunately quite an argument. Using exceptions you get the entire error stack for free, listing all participating functions along with values for variables and arguments depending on the implementation. Applying plain rv you are lacking this feature.

If you need a stack you have to build it yourself. E.g. instead of returning the rv directly at the end of a function you could return a method with the rv as argument: return rvReturn( %r ); This method would store all relevant data and return the (unchanged) rv. Usually you would fall back on an independent class or object that holds the stack, its getters and setters respectively.

Here it gets a little filthy. In some languages you can easily insert this aspect by augmenting the function object, in others you might not even be able to retrieve the function name, forcing you to pass it on to the rvReturn method. For the sake of consistency I recommend to analyse the languages in your projects and identify the least common denominator; even if some would provide more elegant solutions.

Where there is shadow, there is also light. There are two positive side effects. Firstly, you have the freedom to include whatever info you like in that stack (e.g. you could even think of adding another aspect at the beginning of functions to also measure execution times). Secondly, since rv treats success and failure the same we can as well use that stack information in any situation. This makes it a perfect candidate for testing your application even applying TDD if you want: you model the entire application logic by composing the desired stack and code against it. This works great for me.

rv looks frumpy, simple-minded, plain, naive, ....

I kind of like this argument because it reminds me of myself in former days. It is true, when applying rv you won't win a price for the most elegant and sophisticated looking code. However, now in my late thirties, I no longer try to craft filigree butterflies that basically are best to show off. I try to give my artistic expression to the design of the system and rely on hammer and chisel to carve frumpy but rock solid code that even my mom would be able to comprehend. The simpler the better. So, yes, rv looks frumpy and yes, that is good; if you are afraid to lose your face in front of your friends do not use rv, get a little older and try again, it helped in my case.

Impact of omitting checks

The argument: if you forget to check rv[ 'bOk' ] the application will happily continue to muddle further in exceptional cases potentially doing severe harm to your system, your data in particular. However, a raised exception cannot be forgotten to deal with. It would escalate itself to system level if not handled. This is very true.

Yet, with the same argument you could claim that it is better to not put sugar in your donut dough because if you confuse sugar with salt your customers are likely to vomit. Granted, salty donuts won't make a business but hey, donuts without sugar won't sell any better and after all don't you want to make the damn best donuts in town? I sure do.

So, take the initial argument with grain of salt. We all have to aim at well written and properly reviewed code and if you do come to the conclusion that a sweet donut is what you want then you'll find how easy it is to detect the omission of an rv check. Truth is you can't miss it unless you close your eyes on purpose.

Long execution paths

The argument: You have a layered architecture, lets say layer 1, 2 and 3. If an error occurs in layer 1 it may only be of concern in layer 3, layer 2 might not even know how to handle it, hence, we senselessly cross cut through latter. The solution is to shortcut from layer 1 to layer 3 or whatever layer can best handle the error.

If this is a problem for you, you really have a problem with rv. It is inherent to the concept that function calls trickle down through all layers and then bubble up their rv to the requester. Any breach of this rule will destroy the idea of rv.

However, I seem to have gotten used to follow down the execution path and then back up again. I get rather confused if I have to leave this beaten track and follow some exceptionally secret paths to see what happens next. After all in case of failure an rv gets waved through back up to the requester without any special logic playing a role.

The environment is exceptional

The argument: API’s and 3rd party applications you rely on, legacy code you don’t want to or can’t refactor or the language itself do raise exceptions. You can treat those components just like any other external call that doesn’t send back an rv e.g. the system call in the readFile function above. You literally embrace these exceptions and factor them to your library. Your application remains fully "rv"ed by only interfacing latter. Here an examples of such an rv "hug":

runTemplateEngine : function( oView ) {
	var rv = { "bOk" : true };
	try {
	var html = Mustache.to_html(
		oView.sTemplate, oView.oData, oView.oParts
	);
	rv.html = html;
	} catch (e) {
		rv.bOk = false;
		rv.sErrMsg = "Can’t create template: " + JSON.stringify( e );
	}
	return rv;
}
I currently use mustach.js as preferred JavaScript templating engine which utilizes exceptions.

Performance

As far as performance is concerned opinions differ as to which error handling technique is superior.
To tell you the truth I don’t really care. 10 milliseconds here even 50 (sic!) there... same difference. Don’t waste your time nitpicking. In almost all cases it is the overall design not the implementation that is the cause for your performance problems. So analyse your architecture and big Os in your loops before you even think of dropping exceptions for rv or vice versa. I could never solve a performance issue by tweaking my error handling, ever.

Extraordinary verbosity

It is a fact that rv will inflate your code big style. Not only the checking part but also the integration of the failure paths contribute to this abundance of code.

If this is of special concern to you and you don't feel like giving it a try nonetheless rv is nothing for you. Full stop. On the other hand, if you are curious you might find that its verbosity is highly structured which comes with nice side effects. Firstly you can easily have your text editor provide the handful snippets you need (if you are using vim try snipMate). Secondly, our brains are quite good in dealing with patterns after a very short adaptation phase. This helps a great lot in detecting any aberration. BTW: exceptions also come with some overhead, fortunately not in places you immediately see them :)

In this context I don’t want to hold back a comment Stephen Thomas made in a discussion on metafilter: "...error handling is the second most bug-inducing part of any design process (the first is any logic that deals with dates and times). If error handling is done thoroughly, it makes code verbose. If it's not, it turns code unreliable. So if your project is mission-critical, and you have enough design time to get it right: handle error conditions explicitly via function return codes, and allow your code to reflect all the explicit error-handling decisions designed into it. If you're whipping up a quick piece of shit to deal with some pressing issue, use exceptions. Do not, under any circumstances, allow a quick ly whipped up piece of shit to turn into a component of something you'll seriously rely on."

A last word

I do join the majority and recommend to handle errors at all cost, however, choose one paradigm and adamantly stick to it in every code you touch. As for rv, I hope I could provide some additional perspectives and I do invite you to join the minority to enjoy the advantages of this concept which I am using ever since my colleague Curro recruited me to the doomed army of value returners back in 2004 :)

Curro said on 2011-02-08 20:52:10

Hi Juergen,

Two quick notes on the code in your first block:

1) The message in the sErrMsg variable should perhaps contain the word "not": "Could NOT read the file..."
2) The line "$sContent= ;" is not so clear. I think you are missing something of the type "", as in "$sContent=;".

Cheers,

Curro

Juergen Riemer said on 2011-02-08 21:15:00

Curro,

thanks for pointing me to it, I already corrected the article as well as my perl library :)

Curro said on 2011-02-10 17:12:00

Jürgen,

This mini-text field for typing in the message is way too small for paragraphs like the ones I've written above. Why don't you make it at least as wide as the text on this page?

Cheers,

Curro

Juergen Riemer said on 2011-02-18 13:00:00

done

Julian said on 2011-02-18 13:30:00

The "rv" idea is great, but I only see it working in non-strictly typed languages like Javascript and PHP :)
That's why a lot of people still tend to stick to exception-oriented approach, which seems to be more generic and widely implementable.

Juergen Riemer said on 2011-02-20 11:18:00

Hi Julian,

it is true that in some languages you might need some thinking about how to construct the rv data structure, e.g. in PL/SQL you need a construct that passes back a boolean value as well as e.g. a record set not that straight forward to implement.
However, this extra mile is worth the effort for sure... since you then have one true pattern to rule them all... give it a try, you see how it pays off.

Fred said on 2011-10-10 16:12:12

Jurgen,

I do not know whether perl (nor javascript) even have the concept of exception propagation, but the idea between those and your $rv concept are identical.

If I take an example of our work environment, then oracle employs exactly the same paradigm.... but on a more global scale....

the normal flow would be something like:

begin ... some operation
..... stuff ....
...do some danagerous thing here, perhaps some error occurs...
... COME HERE ONLY WHEN PREVIOUS WENT OK
exception handler
when ... some known exception .....
... do something to handle the known exception ...
when .... etcetera .....
end

Even here you have a unique error code and a error string.
The language is extendable with custom, programmable exceptions.

Assuming your perl/javascript does not know this general exception structure then your approach is a appealing one.
However, if they do know exceptions, then this sounds like reinventing the wheel and you might consider only doing this in your interface functions which are exposed to other languages only...
is the sum of one and eight.