De Morgan's Laws: Boolean Logic Is Serious Business

Today, Alex and I were pairing on a bug in our system dealing with an editor screen. Some properties of the item being edited are not editable based on the value of other properties of the item. This is implemented in the system fairly easily as a series of "can edit" like methods on the item's view model.

The original code containing the bug looked like this:

private static bool IsInitializedAvailable(ModuleClass moduleClass, Indices indices)
{
    return moduleClass != ModuleClass.Liability && indices != Indices.Constant;
}

The bug stated that ArbitraryIndex was also a invalid value for indicies. This sounded like a simple enough fix to Alex and I. And we quickly implemented this:

 private static bool IsInitializedAvailable(ModuleClass moduleClass, Indices indices)
 {
     return moduleClass != ModuleClass.Liability && (indices != Indices.Constant || indices != Indices.ArbitraryIndex);
 }

This failed our unit test. In attempting to figure out why, I used a technique I have used in the past for figuring out boolean logic. I broke the comparisons out into separate pieces. So I could understand the logic of them better. Like so:

 private static bool IsInitializedAvailable(ModuleClass moduleClass, Indices indices)
 {   
    var isLiability = moduleClass == ModuleClass.Liability;
    var isConstantOrArbitraryIndex = indices == Indices.Constant || indices == Indices.ArbitraryIndex;

    return !isLiability && !isConstantOrArbitraryIndex;
}

This actually passed the tests, so we reconstituted the one-liner to get rid of the unneeded variables. Like so:

 private static bool IsInitializedAvailable(ModuleClass moduleClass, Indices indices)
 {   
    return moduleClass != ModuleClass.Liability && !(indices == Indices.Constant || indices == Indices.ArbitraryIndex);
}

Both Alex and I were uncertain why there was a difference between the original bug fix implementation and the final correct implementation. The two statements appear on the surface to be the same at a glance. Take a look yourself, can you spot the difference?

    moduleClass != ModuleClass.Liability && !(indices == Indices.Constant || indices == Indices.ArbitraryIndex); //wrong
    moduleClass != ModuleClass.Liability && (indices != Indices.Constant || indices != Indices.ArbitraryIndex);//correct

Alex, and I were stumped. That is when Alex's CompSci degree from U of W woke up and he diagrammed out a Truth Table for the boolean expressions. It looked like this:

He went on to explain that boolean logic actually has clearly defined laws known as De Morgan's Laws.

The rules can be expressed in English as:

"The negation of a conjunction is the disjunction of the negations." and
"The negation of a disjunction is the conjunction of the negations."

I am going to have to read up on both as they sound like a significant hole in my developer tool box. Thanks, Alex!

Follow me on Mastodon!