There has never been an unexpectedly short debugging period in the history of computers.
--Steven Levy
- Welcome to Debugging A programmer is a human trying to do a machine's job. Unlike computer execution, which occurs in the blink of an eye, the programming cycle occurs at our time scale. In the cycle, the programmer (or programmers) repeatedly modifies human-readable code. The hope is that the code can be converted to, or handled by, machine instructions that execute on a processing unit. And besides running, the hope is that the code will run correctly: it will do the job intended by the programmer. The poor human gets feedback from the system when the code does not translate to machine actions (a "compiler error"), or the machine actions fail to execute (an "execution error"), or the output is not correct (a "mistake"). All the while, the programmer cycles through the psychological states displayed below: In professional programming this job is done by a team of people who follow practices and standards. Programming can be satisfying and occasionally exciting when the machine finally does what you want (apparently). But it also tedious and frustrating because the machine and its programs cannot read the programmer's mind. Humans often forget how simple and dumb machines are. However, rest assured. The humans are still firmly in control. The machines and their program masters do not appear to have their own objectives yet. Instead, they are maids and chauffeurs waiting for us to want them to do something for us in a language they understand. We will expand on some of these stages, but below is the arc that you should expect to follow when setting out to do economic computation. When you are actually writing the program we will call it
- Stages of the Programming Cycle
- Stage 1:
CodeTime
- Stage 2:
CompileTime
Just as your parent might look at the draft of your paper and give you feedback, so you will ask the computer to execute your program. But before this happens the intent of the program must be clear as written in the language you are using. Often dumb mistakes of yours or misunderstanding of the language make your program invalid in the language. Mistakes found at this stage are often syntax errors, akin to grammatical and spelling errors in your draft term paper. When they exist it means your paper/program cannot be understood by the audience. Fixing these errors is referred to as debugging (why?).
You may be required to go back several times to fix the errors and submit the program again. Often fixing some errors leads the compiler to find other errors that were always there, or the fix itself may have errors. With patience you eventually get your program past - Stage 3:
RunTime
After the instructions the compiler inferred from your program are executed we get to running or executing the program. As discussed in details below, the difference between what we call - Stage 4:
TestTime
Once the program executes without errors there are results or output to look at. This may be a table of numbers or a graph or very large and complex output. Just because output is produced does not mean that it is the correct or the intended output for the input you provided. You cannot simply accept the output. Rather, you must question it, subject it and the program that created it to scrutiny. If we write computer programs to give us answers to problems we cannot solve by other means, then how can we check the output of our program.
The best way is to start by running it on cases (or using data) for which you know the correct output from other means. Suppose your model has a parameter, call it $\alpha$. The interesting case and the reason why you wrote the code is to get an answer for, say, $\alpha=1.5$. However, suppose when $\alpha=0$ the model simplifies and you can solve it with pencil-and-paper. You do so and get an answer (or output or prediction) of 2.5. You run your program setting the parameter - Stage 5:
Production Run
Finally, your program compiles, executes and gives correct answers for cases that can be verified by other means. You can now run your program for data that you cannot know the correct output for ahead of time. Of course, this change in the data can expose compile or runtime errors that were not detected before, so moving from test cases to production runs is not often immediate. Once you get output, how do you know it is correct? In general you will not know this, and you should always act as if there are undiscovered errors in the code. One way to detect these bugs is to check wether your code produces output consistent with your intuition or with something similar to comparative static results in economic theory.
Continuing the example from Stage 4. You've run your code for $\alpha=0$ and get the predicted output of 2.5 as well as the sign of the response near 0. Now you try setting Input/Output Parameter Value Correct Output to Verify --------------------------------------------------- α 0.0 2.5 0.01 something greater than 2.5 -0.01 something less than 2.5 1.5 ???
- A programming cycle for a modified "hello world" program. Here is an example of writing a program, finding syntax errors, then finding semantic errors and finally having correct output.
PROGRAM COMPILER SAYS RTE SAYS OUTPUT ==================================================================================== include "oxstandard.h" include main() { not understood decl x,v="hello worl"; println(x);
Oops. Forgot the # which must start a precompiler directive:PROGRAM COMPILER SAYS RTE SAYS OUTPUT ==================================================================================== #include "oxstandard.h" file not found main() { decl x,v="hello worl"; println(x); }
Oops. That's not the name of the standard library file.PROGRAM COMPILER SAYS RTE SAYS OUTPUT ==================================================================================== #include "oxstd.h" OK! x has no value main() { decl x,v="hello worl"; println(x); }
Oops, meant to print the other variable.PROGRAM COMPILER SAYS RTE SAYS OUTPUT ==================================================================================== #include "oxstd.h" OK! OK! hello worl main() { decl x,v="hello worl"; println(v);
Oops, spelling error only I can catch.PROGRAM COMPILER SAYS RTE SAYS OUTPUT ==================================================================================== #include "oxstd.h" OK! OK! hello world main() { decl x,v="hello world"; println(v); }
The point is that you have to fix bugs in order to find and discover all the bugs in your program. Get used to looking at the output Ox produces to see where it detects the bug and then try to understand the cause. Often the cause is not as obvious as the bugs here. Sometimes you will not understand the error message. At this point you can ask me and I will try to respond quickly. I won't try to find all your bugs but I will try to fix the one that is stopping you from getting any further.
CodingTime
. When you think the program does what you want you try to run it. This requires the computer to understand the instructions and then carry them out. We break this up into two stages: CompileTime
(translating the code into machine instructions) and RunTime
(carrying out the machine instructions by loading them into RAM and adding the first instruction to the running program list). In some languages these stages are very distinct, but in many languages the user may not be aware of these separate stages.
CodeTime
is when you are working on the text of your program in your chosen language. This is like writing a draft of a term paper. When writing a term paper you might write a draft and then hand it over to someone to read it and make comments. In the flowchart above, some stages are shaded, including CodeTime
. These filled-in stages are when you, the lowly human, are doing something. At the other times in the programming cycle the work is being done by the computer. What you should gather from the flowchart is that as the program develops you continually come back to Stage 1.
CompileTime
. At this stage the compiler can translate your program into something the computer can execute.
CompileTime
and RunTime
is not always obvious to you the programmer, especially in languages like Ox. But to understand what you are doing it is important to see the difference in timing.
Once your program is running it will usually produce runtime errors. That is, the program starts to run but then "crashes" and quits before doing everything you want it to do. The analogy is submitting a draft of your term paper to the teacher who finds mistakes in your reasoning. These are mistakes in meaning (or semantics) rather than mechanical issues of syntax. The teacher hands back the draft with required changes in red. This initiates another sequence of changes and edits, which usually require you to go back to Stage 1 and work on your code again.
In some cases something else causes a runtime error, like the format of the data your program uses is incorrect. But often you must change the program and resubmit it to the compiler, which may find new syntax errors, leaving you stuck at Stage 2 again. These new CompileTime
errors must be fixed before getting a shot at Stage 3 again.
alpha=0
. If the computer does not agree with your own calculation then you have to go back and look for semantic errors in your code and maybe even question your paper derivation.
Once your code also produces 2.5, great! You can move on to the next stage, but often there is more checking you can do. Suppose you also know from your analysis that for positive values close to 0 you should get a larger output than 2.5. And for small negative values output should be below 2.5. In other words, you can sign how output responds to $\alpha$ near 0. Then try values of $\alpha$ near 0 and confirm this behaviour.
Probably every programmer, and especially self-taught amateurs, spends too little time in TestTime
. The more you program the more you learn that your code will fail under many conditions (different input). Once code runs it is human nature to start executing it on data for which they cannot directly verify the output. Any time you spend testing your code and making it more robust to input values will usually pay off in less time later on trying to figure why your results seem wrong or the program crashes.
alpha=1.5
, the parameter value of interest to you. You can have some confidence that the output your code produces is correct, even though you cannot verify it directly.
Exercises
04-bang-head-wall.ox
1: &include "oxstd.h'
2: main {
3: decl y;
4: x = 5.0;
5: println("x = " x);
6: }