Tips & Tricks

The Inspection Connection – Issue #2, Proposition Transformation

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.

image description