The Inspection Connection – Issue #2, Proposition Transformation

Breandan Considine

Boolean algebra can be difficult to reason about, which is why IntelliJ IDEA provides several inspections and intentions to simplify boolean expressions, improve readability and help make your code less prone to bugs. Here are just a few you may encounter while writing Java.

1. De Morgan’s Laws

De Morgan’s laws are (1) ¬a ∧ ¬b ≡ ¬(a ∨ b) and (2) ¬a ∨ ¬b ≡ ¬(a ∧ b). We can use these laws to transform boolean expressions programmatically, saving you the hassle of drawing a truth table to confirm your own good intuition.

public void deMorgans(boolean p, boolean q, boolean r) {
  if (!p && !q && !r) {
    System.out.println("p and q and r are *all* false");
  } else {
    System.out.println("at least one of p, q or r is true");
  }
}

By placing your cursor on line 2, then pressing Alt+Enter,w to select Replace ‘&&’ with ‘||’, IntelliJ IDEA will apply De Morgan’s first law, by first replacing conjunctions with disjunctions and negating the whole clause, leaving us:

public void deMorgans(boolean p, boolean q, boolean r) {
  if (!(p || q || r)) {
    System.out.println("p and q and r are *all* false");
  } else {
    System.out.println("at least one of p or q or r is true");
  }
}

As you can see, this may not always be the shortest or most natural form, so we can easily swap back and forth by between them by pressing Ctrl/Cmd+Z and Ctrl/Cmd+Shift+Z, or by repeating the inspection to apply De Morgan’s first law in reverse.

2. Rearranging control flow

IntelliJ IDEA provides two intentions for merging nested Ifs with conjunction and equivalent Ifs with disjunction, and two for separating them, via Split if and Extract if condition, respectively. For example, consider the following scenario:

if (a) {
  if (b) {
    x = 1;
  }
}

By applying the first intention Merge nested ‘if’s, this can be reduced to:

if (a && b) {
  x = 1;
}

Likewise, sequential ‘if’ conditions with identical bodies can be joined with disjunction.

if (a) {
  x = 1;
}
if (b) {
  x = 1;
}

By placing your cursor over the ‘i’ or ‘f’ on line #1 and applying the intention Merge sequential ‘if’s, this can be reduced to:

if (a || b) {
  x = 1;
}

Now, if you want to break up a complex ‘if’ statement, you can place the cursor over a boolean expression, and apply the intention Extract if (…), where ‘…’ indicates the expression to be extracted.

if (a || b && c) {
  x = 1;
} else {
  x = 2;
}

Whichever disjunct your cursor is covering when the inspection is applied, that condition will be extracted into a new If statement and either nested inside the existing one or placed directly afterwards.

if (b && c) {
    x = 1;
} else if (a) {
    x = 1;
} else {
    x = 2;
}

Keep in mind, if that if the condition contains a function call, this refactoring may alter the sequential structure of your program.

3. Flip Comparison

By reversing the traditional order of conditionals, Yoda notation claims two benefits: reducing null pointer errors and preventing accidental assignment. Many programmers follow this style in order to call attention to the boundary value:

public boolean isAcademic(String email) {
  if ("".equals(email)) {
    return false;
  } else if (6 > email.length()) {
    return false;
  }
   return email.contains(".edu");
}

However we can see that Yoda notation hides a null pointer that may potentially reappear later (in this case on line #4). The second motivation, that Yoda notation prevents ‘=’ where ‘==’ is intended, is demonstrated below.

public boolean shortCircuitAnd(Boolean a, Boolean b) {
  if(a = false) { //a = false -> no error
    return false;
  } 
  if(false = b) { //false = b -> compiler error
    return false;
  }

  return true;
}

In Java, there are very few cases where accidental assignment is also syntactically valid, and IntelliJ IDEA will detect those cases where assignments are used inside a condition. So we can safely swap Yoda conditions with the more traditional variation.

public boolean isAcademic(String email) {
  if (email == null) {
    return false;
  } else if (email.equals("")) {
    return false;
  } else if (email.length() < 6) {
    return false;
  }

  return email.contains(".edu");
}

No matter which convention your code adopts, is important to use one consistently across your project. IntelliJ IDEA supports annotations for nullity inference, so the rationale behind Yoda notation in Java is less clear, but the choice is yours.

4. Avoiding negative conditionals

In Clean Code, Robert Martin writes, “Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives.” (Martin, [G29]). IntelliJ IDEA has three inspections to help you stay positive.

public boolean isAcademic(String email) {
  if (email != null && !email.isEmpty()) {
    return email.contains(".edu");
  }

  return false;
}

When conditionals are negative, this is a good indication that control flow can be refactored. When refactoring your code, you should apply the intention Invert If Condition. Notice how De Morgan’s law is implicitly used to refactor this clause.

public boolean isAcademic(String email) {
  if (email == null || email.isEmpty()) {
    return false;
  }

  return email.contains(".edu");
}

IntelliJ IDEA can detect when a boolean method is always inverted, giving you the opportunity to rename these functions. In order to trigger a global inspection like this one, you will need to explicitly run it. You can find this inspection under Analyze|Inspect Code.

class Applicant {
    public boolean isNotAcademic(String email) {
      if (email == null || email.isEmpty()) {
        return true;
      }

      return !email.contains(".edu");
    }

    void updateEmail(String email) {
      this.email = email;
      isEligible = !isNotAcademic(email);
    }

    String email = "intellij@jetbrains.edu";
    boolean isEligible = !isNotAcademic(email);
}

This warning can be resolved by clicking “Invert method” on the right hand side of the Inspection Results Window, under “Problem resolution”. IntelliJ IDEA will prompt us to rename the method and then remove all negated invocations across the scope of the inspection.

isAlwaysInverted

Similarly, the inspection boolean variable is always inverted will help discover negated variables within a class, and IntelliJ IDEA 13+ offers an inspection for detecting negatively named boolean variables, like disabled, hidden, or isNotChanged. You can enable this inspection under Inspections|Data flow issues.

Comments below can no longer be edited.

5 Responses to The Inspection Connection – Issue #2, Proposition Transformation

  1. Michael Glockenstein says:

    September 28, 2014

    the first example in #3 repeats the code from #1 and does not show yoda style

    • Breandan Considine says:

      September 28, 2014

      Michael, thanks for catching this. I have updated the example in #3 with the correct sample.

  2. Alejandro Hdez. Angeles says:

    September 29, 2014

    In the Merge sequential ‘if’s example, i call intentions but doesn’t appear the option of merging sequencial if’s, i’m using IntelliJ Idea 14 Preview

    • Breandan Considine says:

      October 4, 2014

      Alejandro, sometimes intentions can be a little finicky – your cursor needs to be directly over one of the characters in `if (`. I’ve updated the Merge sequential ‘if’s example to reflect this and added a ticket to YouTrack to make Show Intention Actions a little more flexible (this seems to be a recurring issue). Gracias por los comentarios!

  3. Alejandro Hdez. Angeles says:

    September 29, 2014

    The example:
    public boolean shortCircuitAnd(Boolean a, Boolean b) {
    if(a == false) { //a = false -> no error
    return false;
    }
    if(false == b) { //false = b -> compiler error
    return false;
    }

    return true;
    }

    should be:
    public boolean shortCircuitAnd(Boolean a, Boolean b) {
    if(a = false) { //a = false -> no error
    return false;
    }
    if(false = b) { //false = b -> compiler error
    return false;
    }

    return true;
    }

Subscribe

Subscribe for updates