INTRODUCTION |
There is no absolute perfection. You can assume that practically every software has its faults. Instead of calling these mistakes, in programming we call them bugs. These manifest themselves when, for example, the program produces incorrect results under certain circumstances or when it even crashes. Troubleshooting, called debugging, is therefore almost as important as writing code. It is understood, of course, that anyone who writes code should do their best to avoid bugs. Knowing that bugs are everywhere, you should be careful and program defensively. If you are not sure if an algorithm or a piece of the code is correct, it is better if you engage with it especially intensely instead of rushing through it as quickly as possible or putting it off for later. Nowadays there is a lot of software whose main function is dealing with large sums of money or even human lives that are at stake. As a programmer of such mission critical software, you need to be absolutely sure and take the responsibility that every line of code works correctly. Quick hacks and the principle of trial and error are not appropriate there. The crucial objective of developing algorithms and large software systems is to produce programs that are as error-free as possible. There are many approaches for this: You could try to prove the correctness of programs in a mathematically precise manner without using the computer, although this can only be done with short programs. Another possibility that exists is to limit the syntax of the programming language so that the programmer can definitely not make certain mistakes. The most important example is the elimination of pointer variables (pointers) that may refer to undefined or wrong objects when not used properly. This is why there are programming languages with many restrictions of this kind, and others that give the programmer a considerable amount of freedom and which are therefore less secure. Python belongs to the class of more liberal programming languages and is based on the motto: "We are all adults and decide for ourselves what we do and what we shouldn't." |
ASSERTIONS |
Since A, the producer of the module, certainly does not really trust the user B, A incorporates tests into their software, which check the required preconditions. These are called assertions. If the assertions are not observed, the program usually terminates with an error message. (It is also conceivable that the error is only caught without the program terminating.) In this case, the following principle of software development comes into play: Forcing a program termination with a clear description of the possible cause (an error message) is better than an incorrect result.
from gpanel import * from math import pi, sin def sinc(x): y = sin(x) / x return y makeGPanel(-24, 24, -1.2, 1.2) drawGrid(-20, 20, -1.0, 1, "darkgray") title("Sinus Cardinalis: y = sin(x) / x") x = -20 dx = 0.1 while x <= 20: y = sinc(x) if x == -20: move(x, y) else: draw(x, y) x += dx
from gpanel import * from math import pi, sin def sinc(x): assert x == 0, "Error in sinc(x). x = 0 not allowed" y = sin(x) / x return y makeGPanel(-24, 24, -1.2, 1.2) drawGrid(-20, 20, -1.0, 1, "darkgray") title("Sinus Cardinalis: y = sin(x) / x") x = -20 dx = 1 while x <= 20: y = sinc(x) if x == -20: move(x, y) else: draw(x, y) x += dx
from gpanel import * from math import pi, sin def sinc(x): if x == 0: return 1.0 y = sin(x) / x return y makeGPanel(-24, 24, -1.2, 1.2) drawGrid(-20, 20, -1.0, 1, "darkgray") title("Sinus Cardinalis: y = sin(x) / x") x = -20 dx = 1 while x <= 20: y = sinc(x) if x == -20: move(x, y) else: draw(x, y) x += dx This code, however, contradicts the basic rule that equality tests with floats are dangerous due to possible rounding errors. Even better would be to test it with an epsilon boundary: def sinc(x): epsilon = 1e-100 if abs(x) < epsilon: return 1.0 Your function sinc(x) is still not secure, however, since another precondition should be that x is a number. If you, for instance, call sinc(x) with the value x = "python", it results again in a nasty runtime error. from math import sin def sinc(x): y = sin(x) / x return y print(sinc("python"))
|
WRITING OUT DEBUGGING INFORMATION |
Successful programmers are known for their ability to eliminate errors quickly [more... There is a whole software development based on the Test Driven Development (TDD)]. Since we all learn from our mistakes, consider the following program that should exchange the values of two variables. There is a bug, because it outputs 2,2. def exchange(x, y): y = x x = y return x, y a = 2 b = 3 a, b = exchange(a, b) print(a, b) A well-known and simple strategy for finding bugs is to write out the current values of certain variables to the console: def exchange(x, y): print("exchange() with params", x, y) y = x x = y print("exchange() returning", x, y) return x, y a = 2 b = 3 a, b = exchange(a, b) print(a, b) Hence, it becomes obvious where the error occurred and how you can easily fix it. def exchange(x, y): print("exchange() with params", x, y) temp = y y = x x = temp print("exchange() returning", x, y) return x, y a = 2 b = 3 a, b = exchange(a, b) print(a, b) Now that the error is fixed, the additional debug lines in the code are unnecessary. Instead of deleting them, you can simply comment them out using the # symbol since you might need them again later. def exchange(x, y): # print("exchange() with params", x, y) temp = y y = x x = temp # print("exchange() returning", x, y) return x, y a = 2 b = 3 a, b = exchange(a, b) print(a, b) It is smart to use debug flags with which you can activate or deactivate debugging information in different places. def exchange(x, y): if debug: print("exchange() with params", x, y) temp = y y = x x = temp if debug: print("exchange() returning", x, y) return x, y debug = False a = 2 b = 3 a, b = exchange(a, b) print(a, b) In Python, as you probably already know, you can swap variable values in an elegant way without using an auxiliary variable. For this, you can use the automatic packing/unpacking of tuples. def exchange(x, y): x, y = y, x return x, y a = 2 b = 3 a, b = exchange(a, b) print(a, b) |
USING THE DEBUGGER |
Debuggers are important tools for developing large program systems. You can run a program slowly with them, and even run it step by step. So to speak, the enormous execution speed of the computer is adjusted to the limited human cognitive ability. A confortable debugger is ncluded in TigerJython which can help you to better understand the program sequence in a correct program. You are now going to analyze the above defective program with the debugger.
If you now run the program with the fixed bug, you can observe how the variables are assigned to one another in exchange(), which finally leads to the correct results. The short life span of the local variables x and y are clearly visible, as opposed to the global variables a and b. You can also set breakpoints in the program so that you do not have to run through tedious non-critical parts of the program in the single step mode. To do this, click on the far left of the line number column. A flag icon appears which marks the breakpoint. When you press the Run button, the program runs up to this point and then stops.
|
CATCHING ERRORS WITH EXCEPTIONS |
The method of using exceptions to catch errors is classic. To do this, you put the critical program code in a try block. If the error occurs, the block is abandoned and the program continues to run in the except block, where you can react to the error in an appropriate way. In the worst case, you can stop the program execution by calling sys.exit(). You can, for example, catch the error if the parameter in sinc(x) is not of a numeric data type. from sys import exit from math import sin def sinc(x): try: if x == 0: return 1.0 y = sin(x) / x except TypeError: print("Error in sinc(x). x =", x, "is not a number") exit() return y print(sinc("python")) The postcondition for sinc(x) could also mean that the returned value is None if the parameter has an incorrect type. It is then up to the user to deal with this error accordingly. from math import sin def sinc(x): try: if x == 0: return 1.0 y = sin(x) / x except TypeError: return None return y y = sinc("python") if y == None: print("Illegal call") else: print(y) |