TDD Pro-Tip: Suspect Sentinel Returns

TDD Pro-Tip: I suspect sentinel returns, and though I still use them, it’s generally because I haven’t found the right formulation yet.

I’m working on the TSD project today, and I’ve got a nasty little chunk of code I wish weren’t nasty.

(It’s in this file, and of course, you’re welcome to grab the whole repo, which will enable you to really make fun of me.)

You don’t have to look at the code, I’m not going into it, but as I glance at it, I notice there’s an awful lot of sentinel returns.

A sentinel return is a place where you call a function, and it gives you an answer of some type X, unless there’s a problem, in which case you still give me an answer of some type X, but the answer isn’t the answer, it’s an object of type X that means "no love here".

Most sentinel returns involve looking for things, again, of type X. If the thing is found, return it. If it’s not found, return a borked instance of type X. The grandaddy of all sentinels is the bog-standard "tell me the position of this character in that string".

If you find that character, return its index. If you don’t find that character, return -1.

-1 is not a valid position. It’s never a valid position (in most programming languages). It’s a sentinel. It’s telling you "there is no answer".

Now, there are no bad dogs. Sentinel returns aren’t inherently evil: there are two reasons why they’re ubiquitous down there at the bottom of your stack, a cut or two above the metal.

First, for performance. Sentinels combine two answers in one, which can be more efficient than asking two questions.

Second, for full control. By providing those two answers, we provide maximum info for the coder to decide what does and doesn’t matter.

But every sentinel return in your code is a guaranteed explicit branching construct in that code. In the string find case, for instance, you can never use the answer blindly. You have to branch around the possibility of the sentinel saying "no love here".

I don’t desire explicit branches. They make me think, and mama dint raise no thinkers. When I can find a way around them, I do.

The trick, as with most coding constructs one’s unhappy with, is to see if you can wrap it so that it works in your context but so the branch is effectively hidden from view almost immediately, level-wise.

That is the immediate caller of the sentinel return has the branch, but its callers in turn, don’t branch explicitly at all.

A simple case of this: in kotlin maps there’s a .getOrDefault(key,default) method. It gets the value associated with the key, or it returns a default answer if there’s no value already associated. Internally, it branches, but externally, none the wiser.

(There’s also .getOrElse(key) { lambda }, where the lambda can do cool things like go ahead and put the key in the map if its the first de-reference, useful when you’re using the map to increment key counts.)

Another case: a method that’s supposed to find things and return them, in a naive implementation might return the thing or a null. But you could also make it return a list. If the thing isn’t found, the list is empty. This lets callers iterate blindly, hiding/killing the branch.

Another case that’s easy to write in kotlin, a findAnd(target) { lamda } that executes the lambda iff target is found.

The thing you notice when you do these sorts of things: the question in the coder’s mind changes from "what do I want to know?" to "why do I want to know it?" You can’t choose one of these mechanisms w/o a context to suggest which one works best.

Anyway, I’m looking at this ugly code, and there’s several things I don’t like about it, but nearly all of them are the operational result of code-tweaking to go green-to-green test-wise. At the time I wrote the sentinel-return stuff, I just didn’t have enough context.

Now, though, my test set is as complete as I can make it. The current algorithm, branching all over hell’s half-acre, will hopefully give me the context I need to come up with a simpler and more expressive way to do this w/o all those sentinel returns.

(BTW, the TSD is the subject of the first Real Programming video season, so if I do bring this bad boy under control, you’ll be able to see & hear how I managed it. The series launch is planned for December!)

Listener Support and How to Be On the GeePaw Podcast

If you love the GeePaw Podcast, show your support with a monthly donation to help keep the content flowing. Support GeePaw Here.
You can also show your support by sending in voice messages to be included in the podcasts. These can be questions, comments, etc. Submit Voice Message Here.

Subscribe to the Podcast Here

Leave a Reply