Software, technology, sysadmin war stories, and more. Feed
Friday, December 16, 2011

How I return from a function

I used some example code to demonstrate a sample class in C++ in yesterday's post. I also put a little note saying it was just for a demo and to not give me any grief about the return types.

Today, I want to say a bit more about how I prefer to arrange things like that. Basically, we have a situation where a single return is not going to cut it. If you're returning a string, how do you say "oh, I wasn't actually able to get that string for you"? You can't.

Some people try to make this work by using magic values. Ugh! Horrible. Don't do this. If you have the ability to be explicit in what's going on, then use it! Don't get clever. Clever code hurts.

Here's the sort of thing I would use.

bool GetKeyword(string* keyword);

This is simple and direct. You're asking it to get a keyword. It's going to try. If it fails, it's going to return false. If it succeeds, it'll return true and write into the string you provided. Easy.

You can (and should) then arrange a series of tests which exercise all of the routes through GetKeyword such that you manage to run all of the code and return all possible permutations. This also provides an excellent opportunity to write tests so that you can see if your code corrupts that string when it's not supposed to.

In other words, let's say you do this:

  string kw = "abcd";
  bool res = GetKeyword(&kw);

If "res" turns out to be false, "kw" better be "abcd" still! If it isn't, then you've done something evil in your code and need to fix it.

This kind of thinking leads me to write functions in a way where all of the tests which can happen first do happen first. If there's a way to check for insanity, I do it. If I detect something broken, I return false. It's easy. I haven't twiddled any other values yet, so I can just return false and that's it.

As the function goes along, it'll build up some representation of whatever it was asked to get. That'll go into some temporary storage place. Then, at the very end, there'll be something like this:

  *keyword = temp_keyword;
  return true;

That's the only place "keyword" (the pointer given to us by the caller) ever gets touched in that entire function. If I had a way to superglue it to that "return true" call so they always had to occur together, I would use it, but this is what we have.

Doing this, I obviously notice and then discard one thing: the explicit copy of a value from temp_keyword to *keyword. Yep, I copied it. Maybe I could have arranged things differently to avoid that copy. Sure. Maybe I could have arranged for an elephant to dance for me at lunch today. But I didn't.

If this kind of copy becomes an actual problem that is affecting something that I care about, then I might find another way to do it. Odds are, it won't ever come up, and I can leave it just the way it is. Better still, the compiler might get smart and have its own magic way to make it work out nicely without me changing a thing.

What about exceptions? I don't like exceptions. I don't use them. If an error in GetKeyword that would cause it to return false is something I actually care about, odds are I'll add some kind of logging call to clarify what happened.

It turns out if GetKeyword fails, the caller usually can't do anything to improve the situation, anyway. In those rare cases where the specific type of error actually changes what the caller does next, then I might rig some way to either inquire about status after the fact or provide it via another "out" variable. Again, that's rare.

I've seen people use pointers to ints and magic values like -1, 0, or 1, even though they're using C++. Get with the program and use well-named variables and booleans! Otherwise, you're just leaving little steaming code turds behind for some poor maintenance programmer down the road.

Some day, that maintenance programmer might be you. Think karma.