3.1 — Syntax and semantic errors
Modern compilers have been getting better at detecting certain types of common semantic errors (e.g. use of an uninitialized variable). However, in most cases, the compiler will not be able to catch most of these types of problems, because the compiler is designed to enforce grammar, not intent.
3.2 — The debugging process
A general approach to debugging
Once a problem has been identified, debugging the problem generally consists of five steps:
1.Find the root cause of the problem (usually the line of code that’s not working)
2.Ensure you understand why the issue is occurring
3.Determine how you’ll fix the issue
4.Repair the issue causing the problem
5.Retest to ensure the problem has been fixed and no new problems have emerged
3.3 — A strategy for debugging
Finding problems via code inspection
Finding problems by running the program
Reproducing the problem
Homing in on issues
3.4 — Basic debugging tactics
Debugging tactic #1: Commenting out your code
Debugging tactic #2: Validating your code flow
Debugging tactic #3: Printing values
Why using printing statements to debug isn’t great
3.5 — More debugging tactics
Conditionalizing your debugging code
Using a logger
3.6 — Using an integrated debugger: Stepping
When you run your program, execution begins at the top of the main function, and then proceeds sequentially statement by statement, until the program ends. At any point in time while your program is running, the program is keeping track of a lot of things: the value of the variables you’re using, which functions have been called (so that when those functions return, the program will know where to go back to), and the current point of execution within the program (so it knows which statement to execute next). All of this tracked information is called your program state (or just state, for short).
The debugger
Stepping
Step into
Step over
Step out
A step too far
Step back
3.7 — Using an integrated debugger: Running and breakpoints
Run to cursor
Continue
Start
Breakpoints
Set next statement
A breakpoint is a special marker that tells the debugger to stop execution of the program at the breakpoint when running in debug mode.
3.8 — Using an integrated debugger: Watching variables
3.9 — Using an integrated debugger: The call stack
The call stack is useful in conjunction with breakpoints, when your breakpoint is hit and you want to know what functions were called to get to that specific point in the code.
3.10 — Finding issues before they become problems
Don’t make errors
Well, the best thing is to not make errors in the first place. Here’s an incomplete list of things that can help avoid making errors:
Follow best practices
Don’t program when tired
Understand where the common pitfalls are in a language (all those things we warn you not to do)
Keep your programs simple
Don’t let your functions get too long
Prefer using the standard library to writing your own code, when possible
Comment your code liberally
Refactoring your code
Key insight
When making changes to your code, make behavioral changes OR structural changes, and then retest for correctness. Making behavioral and structural changes at the same time tends to lead to more errors as well as errors that are harder to find.
An introduction to defensive programming
Defensive programming is a practice whereby the programmer tries to anticipate all of the ways the software could be misused, either by end-users, or by other developers (including the programmer themselves) using the code. These misuses can often be detected and then mitigated (e.g. by asking a user who entered bad input to try again).
Finding errors fast
An introduction to testing functions
An introduction to constraints
Shotgunning for general issues
3.x — Chapter 3 summary and quiz