Double negatives should not not be avoided

Posted by : at

Category : kotlin   refactoring   boolean


Booleans are the bread and butter of decision making in most modern programming languages and in Kotlin this is no exception. I will discuss with you some ways to clean up your boolean logic and you will explore some best practices in giving good positive names to our binary friends.

Double negations are not not bad.

You should not use double negations in your code because they are hard to read and understand. They also significantly increase the amount of bugs and errors that can hide in your program. I invite you to try the following exercise to better understand why Double negatives form a problem.

Which numbers will be printed to the console?

val isNotTyping = false
val isTyping = true

fun main() {
    doubleNegation()
    singleNegation()
    noNegation()
}

fun doubleNegation() {
  if(!isNotTyping)
    println(1)
  else
    println(2)
}

fun singleNegation() {
  if(isNotTyping)
    println(3)
  else
    println(4)
}

fun noNegation() {
  if(isTyping)
    println(5)
  else
    println(6)
}

Most programmers take longer and make more mistakes interpreting the code that uses double negations compared to the code where less negations are used. This is why you should always try to write boolean variables and parameters in their positive form.

Here is the solution. Did you get it right?

1
4
5

Parameters and variables are not the only places where problematic usage of double negations can be found. Methods, that have a boolean as return type, suffer from the same problems.

fun isNotOdd(value:Int):Boolean {
  // Implementation
}

Will be less clear and should be replaced with:

fun isEven(value:Int):Boolean {
  // Implementation
}

Try to start positive

If you really need the negative form of a method consider providing the positive part first and let the negative form call the positive form. Most of the time it is easier to implement the positive form anyway. Letting the negative form call the positive form has the added benefit of avoiding duplication, which limits the amount of code that has to be touched when requirements change.

fun isNotDone():Boolean = !isDone()

fun isDone():Boolean = progress == 100

Clean up existing code

Often you have to deal with code that has already been written by you or an other person. As per the Boy Scout Rule we should Leave our code better than we found it.

Let’s say you found this piece of code that deals with boxes containing integers.

Which steps would you take to improve this code?

You can use the Kotlin Playground to test the code in your browser.

fun main() {
    Box(7).print()
    Box(null).print()
}

private fun Box.print() {
    if(!isNotEmpty())
    	println("box is empty")
    else
    	println("box contains: ${value}")
}

class Box(val value:Int?) {
    fun isNotEmpty() = value != null
}

Make sure you check if your code still compiles and behaves the same way after each step.

Step 1: Introduce positive form.

fun main() {
    Box(7).print()
    Box(null).print()
}

private fun Box.print() {
    if(!isNotEmpty())
    	println("box is empty")
    else
    	println("box contains: ${value}")
}

class Box(val value:Int?) {
    fun isNotEmpty() = value != null
    fun isEmpty() = !isNotEmpty() // Introduce positive form which calls the other form.
}

Step 2: Replace each double negative with a call to the new function.

fun main() {
    Box(7).print()
    Box(null).print()
}

private fun Box.print() {
    if(isEmpty()) //Replace all double negatives
    	println("box is empty")
    else
    	println("box contains: ${value}")
}

class Box(val value:Int?) {
    fun isNotEmpty() = value != null
    fun isEmpty() = !isNotEmpty()
}

Step 3: If negative form is only used in the positive form use the Inline Function Refactor method to move to merge the body of the negative form (isNotEmpty()) to the positive form (isEmpty()).

fun main() {
    Box(7).print()
    Box(null).print()
}

private fun Box.print() {
    if(isEmpty())
    	println("box is empty")
    else
    	println("box contains: ${value}")
}

class Box(val value:Int?) {
    //Remove unused isNotEmpty() method. If you still need it make sure it calls isEmpty()
    fun isEmpty() = !(value != null) // replace method call by implementation.
}

Step 4: Clean up the positive form a little further

fun main() {
    Box(7).print()
    Box(null).print()
}

private fun Box.print() {
    if(isEmpty())
    	println("box is empty")
    else
    	println("box contains: ${value}")
}

class Box(val value:Int?) {
    fun isEmpty() = value == null // making method implementation more expressive
}

Instead of using a method named isNotEmpty to see whether a box is full it would probably make more sense to introduce a method called isFull. Often you have to search a little longer for names that better convey the right meaning.

fun main() {
    Box(7).print()
    Box(null).print()
}

private fun Box.print() {
    if(isFull()) //Replaced isEmpty() with isFull()
    	println("box contains: ${value}") // Swap
    else
    	println("box is empty") // Swap
}

class Box(val value:Int?) {
    fun isFull() = !isEmpty() // Introduced new method in its positive form
    fun isEmpty() = value == null
}

There’s no reason to use complex and confusing double negatives in your code. So do us all a favor. Don’t not avoid double negations.


About Gideon Hoogeveen

Hi, my name is Gideon Hoogeveen. I talk about clean code in Kotlin

Useful Links