GEOG 485:
GIS Programming and Software Development

2.2.1 Potential problems and quick diagnosis

PrintPrint

The secret to successful programming is to run early, run often, and don't be afraid of things going wrong when you run your code the first time. Debugging, or finding mistakes in code, is a part of life for programmers. Here are some things that can happen:

  • Your code doesn't run at all, usually because of a syntax error (you typed some illegal Python code).
  • Your code runs, but the script doesn't complete and reports an error.
  • Your code runs, but the script never completes. Often this occurs when you've created an infinite loop.
  • Your code runs and the script completes, but it doesn't give you the expected result. This is called a logical error, and it is often the type of error that takes the most effort to debug.

Don't be afraid of errors

Errors happen. There are very few programmers who can sit down and, off the top of their heads, write dozens of lines of bug free code. This means a couple of things for you:

  • Expect to spend some time dealing with errors during the script-writing process. Beginning programmers sometimes underestimate how much time this takes. To get an initial estimate, you can take the amount of time it takes to draft your lines of code, then double it or triple it to accommodate for error handling and final polishing of your script and tool.
  • Don't be afraid to run your script and hit the errors. A good strategy is to write a small piece of functionality, run it to make sure it's working, then add on the next piece. For the sake of discussion, let's suppose that as a new programmer, you introduce a new bug once every 10 lines of code. It's much easier to find a single bug in 10 new lines of code than it is to find 5 bugs in 50 new lines of code. If you're building your script piece by piece and debugging often, you'll be able to make a quicker and more accurate assessment of where you introduced new errors.

Catching syntax errors

Syntax errors occur when you typed something incorrectly and your code refuses to run. Common syntax errors include forgetting a colon when setting a loop or an if condition, using single backslashes in a file name, providing the wrong number of arguments to a function, or trying to mix variable types incorrectly, such as dividing a number by a string.

When you try to run code with a syntax error in PyScripter, an error message will appear in the Python Interpreter, referring to the script file that was run, along with the line number that contained the error.  For example, a developer transitioning from ArcMap to ArcGIS Pro might forget that the syntax of the print function is different, resulting in the error message, "SyntaxError: Missing parentheses in call to 'print'. Did you mean print(x)?"

Dealing with crashes

If your code crashes, you may see an error message in the Python Interpreter. Instead of allowing your eyes to glaze over or banging your head against the desk, you should rejoice at the fact that the software possibly reported to you exactly what went wrong! Scour the message for clues as to what line of code caused the error and what the problem was. Do this even if the message looks intimidating. For example, see if you can understand what caused this error message (as reported by the Spyder IDE):

  runfile('C:/Users/detwiler/Documents/geog485/Lesson2/syntax_error_practice.py', wdir='C:/Users/detwiler/Documents/geog485/Lesson2')
Traceback (most recent call last):

  File ~\AppData\Local\ESRI\conda\envs\arcgispro-py3-spyd\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec
    exec(code, globals, locals)

  File c:\users\detwiler\documents\geog485\lesson2\syntax_error_practice.py:5
    x = x / 0

ZeroDivisionError: division by zero

The message begins with a call to Python's runfile() function, which Spyder shows when the script executes successfully as well.  Because there's an error in this example script, the runfile() bit is then followed by a "traceback," a report on the origin of the error.  The first few lines refer to the internal Python modules that encountered the problem, which in most cases you can safely ignore.  The part of the traceback you should focus on, the part that refers to your script file, is at the end; in this case, we're told that the error was caused in Line 5: x = x / 0. Dividing by 0 is not possible, and the computer won't try to do it.  (PyScripter's traceback report for this same script lists only your script file, leaving out the internal Python modules, so you may find it an easier environment for locating errors.)   

Error messages are not always as easy to decipher as in this case, unfortunately.  There are many online forums where you might go looking for help with a broken script (Esri's GeoNet for ArcGIS-specific questions; StackOverflow, StackExchange, Quora for more generic questions).  You can make it a lot easier for someone to help you if, rather than just saying something like, "I'm getting an error when I run my script.  Can anyone see what I did wrong," you include the line number flagged by PyScripter along with the exact text of the error message.  The exact text is important because the people trying to help you are likely to plug it into a search engine and will get better results that way.  Or better yet, you could search the error message yourself!  The ability to solve coding problems through the reading of documentation and searching online forums is one of the distinguishing characteristics of a good developer.

Ad-hoc debugging

Sometimes it's easy to sprinkle a few 'print' statements throughout your code to figure out how far it got before it crashed, or what's happening to certain values in your script as it runs. This can also be helpful to verify that your loops are doing what you expect and that you are avoiding off-by-one errors.

Suppose you are trying to find the mean (average) value of the items in a list with the code below.

#Find average of items in a list

list = [22,343,73,464,90]

for item in list:
    total = 0
    total += item    

average = total / len(list)
print ("Average is " + str(average))

The script reports "Average is 18," which doesn't look right. From a quick visual check of this list you could guess that the average would be over 100. The script isn't erroneously getting the number 18 from the list; it's not one of the values. So where is it coming from? You can place a few strategic print statements in the script to get a better report of what's going on:

#Find average of items in a list

list = [22,343,73,464,90]

for item in list:
    print ("Processing loop...")
    total = 0
    total += item    
    print (total)

print (len(list))
average = total / len(list)
print ("Performing division...")
print ("Average is " + str(average))

Now when you run the script you see.

Processing loop...
22
Processing loop...
343
Processing loop...
73
Processing loop...
464
Processing loop...
90
5
Performing division...
Average is 18

The error now becomes more clear. The running total isn't being kept successfully; instead, it's resetting each time the loop runs. This causes the last value, 90, to be divided by 5, yielding an answer of 18. You need to initialize the variable for the total outside the loop to prevent this from happening. After fixing the code and removing the print statements, you get:

#Find average of items in a list

list = [22,343,73,464,90]
total = 0

for item in list:
    total += item    

average = total / len(list)
print ("Average is " + str(average))

The resulting "Average is 198" looks a lot better. You've fixed a logical error in your code: an error that doesn't make your script crash, but produces the wrong result.

Although debugging with print statements is quick and easy, you need to be careful with it. Once you've fixed your code, you need to remember to remove the statements in order to make your code faster and less cluttered. Also, adding print statements becomes impractical for long or complex scripts. You can pinpoint problems more quickly and keep track of many variables at a time using the PyScripter debugger, which is covered in the next section of this lesson.