Defensive Design: An Experiment

by Zoran Horvat

We can start from traditional defensive coding techniques and improve code stability significantly. Adding else branch to every if instruction, not forgetting default branch in the switch instruction, checking for null before accessing an object, to name just a few.

But in this article, I plan to oppose these techniques to the defensive design. Primary trait of defensive design is that it removes explicit defensive code. Take a look at the piece of code below. The data variable is, supposedly, some collection of integers.

void ReportSuccess(int min) { ... }
void ReportFailure() { ... }
...
if (data.Any())
{
    int min = data.Min();
    ReportSuccess(min);
}
else
{
    ReportFailure();
}

Which part is defensive in this if-else instruction? – Its else branch is the defensive part. I have no dispute with the if block. I have dispute with part under the else branch. That is what makes the whole situation special. Forgetting the else part would cause erratic behavior. Yet, if-else without else would still be perfectly correct from syntactic point of view.

If only I could make those two sequences of operations same, I would be able to merge if and else branches into one. That would render the condition irrelevant, and I could remove it entirely, together with the if-else instruction which contains them.

And, just to make it clear, condition makes perfect sense here. I could never find minimum out of no data – that operation would fail. Now that situation, that inability to find minimum out of no data, reminds me of a trick, an algorithm called Quick sequential search. Let’s cover that first.

Motivational Example: Quick Sequential Search Algorithm

Below is a straight-forward implementation of sequential search. The function is telling whether the array contains the search value or not.

bool Contains(int[] array, int searchValue)
{
    for (int i = 0; i < array.Length; i++)
    {
        if (array[i] == searchValue)
        {
            return true;
        }
    }
    return false;
}

Loop in this implementation has two branching instructions, and both execute with every iteration of the loop. Now one of these branching instructions, the one inside the loop, is coming from the fact that we don’t know whether the array contains the value at all. And then, when said that way, there comes the Quick sequential search algorithm and says – why don’t we put the search value into the array ourselves? If we do that, then the array will certainly contain the search value.

Look at the alternate implementation. Don’t let it frighten you:

bool Contains(int[] array, int searchValue)
{
    int lastIndex = array.Length – 1;

    int last = array[lastIndex];
    array[lastIndex] = searchValue;

    int index = 0;
    while (array[index] != searchValue)
    {
        index = index + 1;
    }

    array[lastIndex] = last;

    return index < lastIndex || last == searchValue;
}

This algorithm is putting the last value from the array aside, and then replacing it with the search value. The point here is that now we know for sure that the array contains the search value. Then the algorithm simply searches for the value, remembering the index at which it was found. Search will always be successful, hence entire loop boils down to one branching instruction. That is how we have simplified the loop by adding the search element to the array. After the loop, we are reconstructing the original array’s content. And then we are simply testing whether we have found our search value, or the value which was there in the original array.

That was quite a lot of code to reduce complexity of the loop. But you get the point. It is possible to tweak the execution context in such way that two execution scenarios become merged into one unified scenario which doesn’t care about differences in the original context.

That idea is greater than this example, this quick sequential search algorithm, which I doubt that anyone has ever really implemented in production. Who knows, maybe it was used somewhere.

Now back to the question of finding the minimum of an array.

Redesigning to Include Defensive Design

Here is the previous piece of code again:

void ReportSuccess(int min) { ... }
void ReportFailure() { ... }
...
if (data.Any())
{
    int min = data.Min();
    ReportSuccess(min);
}
else
{
    ReportFailure();
}

I will start redesigning the code. In the end, I hope to reach a design which is defended out of the box.

The problem is that we can’t take the minimum out of no data. This will not work when data collection is empty. But if we put something into the collection then there will never be the case with no data, right?

int potentialMin = data.DefaultIfEmpty(0).Min();

This call will never fail, and therefore I don’t have to guard it. The only problem is that from time to time it will return my arbitrarily selected value zero. I will have to keep that in mind for the next step – choosing one out of two possible functions.

Let’s put it precisely that way:

void ReportSuccess(int min) { ... }
void ReportFailure() { ... }
...
int potentialMin = data.DefaultIfEmpty(0).Min();
Action todo = data
    .Take(1).Select(_ => () => ReportSuccess(potentialMin))
    .DefaultIfEmpty(ReportFailure)
    .Single();

todo();

Here is the explanation how this code works. If there is at least one element in the collection, then I want the first function to run. If there is not even one piece of data in the collection, then I want the other function to run. And then, after selecting the function, I just need to collapse this sequence containing exactly one action into the proper action variable.

In the end, the todo variable will contain the precise operation that should be executed. That is why I am simply invoking the todo function unconditionally in the last line of code. Whatever the operation it is, I am safe to invoke it unconditionally.

Don’t judge this code yet. There are better, shorter, more readable ways to accomplish the same goal. I only wanted to show you, as a kind of a mind experiment, that it is possible to completely remove one defensive if-else instruction from code.

What have I got in the end? I have got a unified flow. There is no branching anymore and no defense. Each instruction is producing one single result. Defense has moved deeper under the skin.

The first instruction, which calculates potential minimum, acknowledges that there might be no data to take minimum of, and then it admits, through the name of the variable, that its result might not really be the minimum I needed.

The second instruction also acknowledges that there might be no data, and therefore the expected action, ReportSuccess, might disappear in the process. That asks for a corrective action, and that is to include the negative function, ReportFailure, if needed. And then I am collapsing the sequence into one single action. This leaves no doubt about what to do next, and I am simply invoking the designated action.

Improving the Defensive Design

Is this code defended better? It almost is. Its strongest point is in the middle part, determining the action. I could improve it a bit by calculating the minimum only in place where it’s already known for sure that the minimum exists:

void ReportSuccess(int min) { ... }
void ReportFailure() { ... }
...
Action todo = data
    .Take(1).Select(_ => () => ReportSuccess(data.Min()))
    .DefaultIfEmpty(ReportFailure)
    .Single();

todo();

If you forgive me the utterly ugly syntax, which obviously contradicts usual suggestion that the code should be readable, you will agree at least about one thing: About how hard it has become for me to make a mistake. This code is defended by design.

And readability can be fixed. Here is a better syntax which does the same thing:

void ReportSuccess(int min) { ... }
void ReportFailure() { ... }
...
Action todo = data
    .FirstOrNone()
    .Map(_ => () => ReportSuccess(data.Min()))
    .Reduce(ReportFailure);

todo();

This implementation is coming from a custom Option type. You can learn more about Options from Option Functional Type and Understanding the Option (Maybe) Functional Type .

The point of this experiment is that we can acknowledge existence of two execution paths and then merge them into one, so that downstream operations have no idea how they got that one execution stream coming their way. You will be better off on more than one account if you can accomplish that.


If you wish to learn more, please watch my latest video courses

About

Zoran Horvat

Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.

  1. Pluralsight
  2. Udemy
  3. Twitter
  4. YouTube
  5. LinkedIn
  6. GitHub