This article discusses a coding practice that I discovered while an undergrad a while back, maybe 1994. After doing some code reviews recently, I am reminded that this particular practice still needs more publicity. When writing code, a developer typically likes to worry about edge cases first, then handles the normal case. This practice has some benefits: the cases you worry about most get handled early. And, this practice creates maintenance nightmares. This practice has a some detrimental effects:

  • Code readability is reduced
  • Precious cycles are wasted on the uncommon cases
  • Code complexity frequently increases

People remember and read positives easily. They have more issues with negatives. Consider the following C# code:

1 FileStream fs = null;

2 const string filename = “someFile.txt”;

3 if (!File.Exists(filename))

4 {

5     fs = new FileStream(filename, FileMode.CreateNew, FileAccess.ReadWrite);

6 }

7 else

8 {

9     File.Delete(filename);

10    fs = new FileStream(filename, FileMode.CreateNew, FileAccess.ReadWrite);

11 }

Consider what happened when a developer wrote the code above. They thought, “OK, if this file does not exist, then I can create it. However, if the file does exist, I want to make sure I start with a clean slate, so I’ll delete the file, then create it.” The code above is confusing. The thoughts are equally confusing. While I wish this code were an anomaly, it isn’t. This is something I see on a regular basis: in support forums, in code that didn’t have time for a proper review, and elsewhere. Consider what would have happened if the developer had chosen to use positive logic instead of negative:

1 FileStream fs = null;

2 const string filename = “someFile.txt”;

3 if (File.Exists(filename))

4 {

5     File.Delete(filename);

6 }

7 fs = new FileStream(filename, FileMode.CreateNew, FileAccess.ReadWrite);

Here, the code is more readable and the intent is clearer: if the file exists, delete it. Then, open the file for read/write. Note that the code is now smaller and maintenance is easier too.

Here is another example, this one a PowerShell script from TechNet magazine. Here, we have an example where readability is diminished thanks to negative logic. Line 15 just bugs me. Read the code:

1. function Get-WmiInventory {
2.  param (
3.  $wmiclass = “Win32_OperatingSystem”
4.  )
5.  PROCESS {
6.   $ErrorActionPreference = “SilentlyContinue”
7.   $computer = $_
8.   trap {
9.    $computer | out-file c:errors.txt -append
10.    set-variable skip ($true) -scope 1
11.    continue
12.   }
13.   $skip = $false
14.   $wmi = Get-WmiObject -class $wmiclass -computer $computer -ea stop
15.   if (-not $skip) {
16.    foreach ($obj in $wmi) {
17.     $obj | Add-Member NoteProperty ComputerName $computer
18.     write $obj
19.    }
20.   }
21.  }
22. }

OK: If I should not skip then I should do work. With a little better naming, we could make this all more readable. How about this instead:

1. function Get-WmiInventory {
2.  param (
3.  $wmiclass = “Win32_OperatingSystem”
4.  )
5.  PROCESS {
6.   $ErrorActionPreference = “SilentlyContinue”
7.   $computer = $_
8.   trap {
9.    $computer | out-file c:errors.txt -append
10.    set-variable shouldWriteComputerInfo ($false) -scope 1
11.    continue
12.   }
13.   $shouldWriteComputerInfo = $true
14.   $wmi = Get-WmiObject -class $wmiclass -computer $computer -ea stop
15.   if ($shouldWriteComputerInfo) {
16.    foreach ($obj in $wmi) {
17.     $obj | Add-Member NoteProperty ComputerName $computer
18.     write $obj
19.    }
20.   }
21.  }
22. }

Here, readability goes up because I can now see exactly why we DO go into the loop instead of seeing why we do NOT.

If you write negative logic, do a pass after writing things to fix up the code. Do that pass immediately. I frequently write code like:

if (not X){

&#
160; Do A

}

else {

Do B

}

I immediately turn around and rewrite this code as:

if (X){

Do B

}

else {

Do A

}

I have found that I can read this kind of code long after writing it and remember what I meant. I have difficulty doing the same thing with negative logic paths.