This commit is contained in:
Miloslav Ciz 2024-03-24 21:52:08 +01:00
parent 25bef6df75
commit c0e7392173
17 changed files with 1930 additions and 1861 deletions

View file

@ -1,10 +1,8 @@
# Debugging
WORK IN PROGRESS
Debugging is a term predominantly related to [computer](computer.md) technology (but sometimes also extended beyond it) where it stands for the practice of actively searching for [bugs](bug.md) (errors, design flaws, defects, ...) and fixing them; most typically it happens in software [programming](programming.md), but we may also talk about debugging [hardware](hardware.md) etc. Debugging is notoriously tedious and stressful, it can even take majority of the programmer's time and some bugs are extremely hard to track down, however systematic approaches can be applied to basically always succeed in fixing any bug. Debugging is sometimes humorously defined as "replacing old bugs with new ones".
Debugging is a term usually related to [computer](computer.md) technology (but sometimes also extended beyond it) where it stands for the practice of actively searching for [bugs](bug.md) (errors, design flaws, defects, ...) and fixing them; most typically it happens in software [programming](programming.md), but we may also talk about debugging [hardware](hardware.md) etc. Debugging is notoriously tedious and stressful, it can even take majority of the programmer's time and some bugs are extremely hard to track down, however systematic approaches can be applied to basically always succeed in fixing any bug. Debugging is sometimes humorously defined as "replacing old bugs with new ones".
Fun fact: the term *debugging* allegedly comes from the old times when it meant LITERALLY getting rid of bugs that broke computers by getting stuck in the relays.
[Fun](fun.md) fact: the term *debugging* allegedly comes from the old times when it meant LITERALLY getting rid of bugs that broke computers by getting stuck in the relays.
**Spare yourself debugging by testing as you go** -- while programming it's best to at least quickly test the program is working after each small step change you make. Actually you should be writing **[automatic tests](automatic_test.md)** along with your main program that quickly tests that all you've programmed so far still works (see also [regression](regression.md)). This way you discover a bug early and you know it's in the part you just changed so you find it and fix it quickly. If you don't do this and just write the whole program before even running it, your program will just crash and you won't have a clue why -- at this point you most likely have SEVERAL bugs working together and so even finding one or two of them will still leave your program crashing -- this situation is so shitty that the time you saved earlier won't nearly be worth it.
@ -31,6 +29,10 @@ The following are some of the most common methods used to debug software, roughl
Quick way to spot small bugs is obviously to just look at the code, however this really works for the small, extremely obvious bugs like writing `=` instead of `==` etc.
### Searching The Internet
Even for some less common errors just copy pasting the error message to the search engine usually reveals its most common cause on [Stack Overflow](stack_overflow.md). You will do this a lot when learning a new language/library/environment etc.
### Manual Execution
In this method you try to go through the program yourself step by step, just as the computer would. By this you will find out just WHY and WHERE your program gets to a wrong result or to a line that makes it crash.
@ -45,6 +47,10 @@ The advantage of this is that you don't need any extra debugger, the method work
Sometimes a bug can be super nasty and make the program crash always in random places, even depending e.g. on where you put the print statement, even if the print statement shouldn't really have an effect on the rest of the program. When you spot something like this, you probably have some super nasty bug related to undefined behavior or optimization, try to mess with optimization flags, use static analyzers, reduce your program to a minimum program that still bugs etc. This may take some time.
### Logging
[Logging](logging.md) is similar to the debug prints but it's something you just do automatically as you program (see also asserts below), logging system is a permanent part of the program, i.e. something that will stays as the program's feature rather than a temporary way of finding and fixing a specific bug. Logging means your program records what it's doing by printing it to the command line or into some text file -- this creates a log that will be useful for many things, including debugging. The advantage here is that if a user encounters a bug, he can just send the programmer his log file which the programmer can read and get some idea about what happened. For this logs should adhere to some rules and be a bit more sophisticated than mere quick printouts: firstly log outputs should be nice and more verbose (i.e. output e.g. `step 225: variable x = 342` instead of `asdf 225 342`) so as to be understandable to anyone, they should be nicely formatted because a log will likely be long so it should be friendly to be filtered with [regexes](regex.md) etc., it should also be possible to turn logs off. With bigger project there are also options to set different log levels (e.g. the highest level will print almost everything the program is doing, lower level will print only important steps and so on), set where to store the log (i.e. print to console, store to some specified file, ...) and so on.
### Rubber Duck
Rubber duck debugging works like this: you try to explain your code to someone -- even someone who doesn't understand programming, for example rubber duck -- and in doing this you often spot some error in reasoning. Explaining code to a programmer may have a further advantage as he may ask you clever questions.
@ -53,6 +59,26 @@ Rubber duck debugging works like this: you try to explain your code to someone -
When dealing with a super nasty bug in a complex program that's dodging solutions by the simpler methods, it is useful to just copy your program elsewhere and there strip down everything off of it while still keeping the bug in place. I.e. you just keep deleting functions and all the program does while making sure the bug you're after is still happening. This will firstly eliminate places where you have to look for the bug but mainly will usually lead you to reducing the program to just a few lines of code that behave extremely weirdly, like a function whose behavior depends on where you put a print statement of if you use a wider data type etc. Then you usually find the problematic line or whatever it is that's causing the bug and once you know the line, you can look at it really carefully, google the behavior of each operator etc. to really find the bug.
### Asserts
Assertions are checks for conditions that should always hold, for example if you're programming some game, it should always hold that the player is within the level boundaries at all times, so you can just regularly keep checking this condition in your program -- if this assert fails, there is probably some bug (maybe you calculated the position wrong, maybe some pointer overwrote your value, ...), and the location of this condition can also help you locate the bug (you will know approximately when and where in the code it happened). Similarly you can just watch all important variables and their relationships. In bigger projects adding asserts on the go is sometimes considered a "[good habit](good_habit.md)" or is even a required practice, i.e. it is not something you start doing only when you discover a bug -- the purpose of asserts is more to discover bugs early and prevent disasters (running a code that's internally working bad) rather than help fix them (but they'll help with that too). Asserts can be implemented with special debuggers or [libraries](library.md), however a more [KISS](kiss.md) way is to simply [do it yourself](diy.md), it's a simple condition check -- you should just make it so that you can disable all assert check easily because while you will use them in debugging, for the [release](release.md) build you'll want more performance, so you'll want to turn unnecessary condition checks off. For example in [C](c.md) you can make an assert macro like:
```
#ifdef DEBUG
#define ass(cond,text) if (!(cond)) printf("ASSERT FAILED: %s!\n",text);
#else
#define ass(c,t) ;
#endif
...
// assert correct player position:
ass(abs(playerPos.x) <= WORLD_BOUND && abs(playerPos.y) <= WORLD_BOUND,"player position")
...
```
Here if you don't define the `DEBUG` macro, the assert macro will just be an empty command that does nothing.
### Debuggers And Other Debugging Tools Like Profilers
There exist many software tools specialized for just helping with debugging (there are even physical hardware debuggers etc.), they are either separate software (good) or integrated as part of some development environment (bad by [Unix philosophy](unix_philosophy.md)) such as an [IDE](ide.md), web browser etc. Nowadays a compiler itself will typically do some basic checks and give you warning about many things, but oftentimes you have to turn them on (check man pages of your compiler).
@ -78,6 +104,15 @@ Furthermore there many are other useful tools such as:
- **[emulators](emulator.md), [virtual machines](vm.md), ...**: Running your program on different platform often reveals bugs -- while your program may [work perfectly fine](works_on_my_machine.md) on your computer, it may start crashing on another because that computer may have different integer size, [endianness](byte_sex.md), amount of RAM, timings, file system etcetc. Emulators and VMs allow you to test exactly this AND furthermore often allow easy inspection of the emulated machine's memory and so on.
- ...
### Other Tips
Additionally these may help deal with bugs as well:
- With weird bugs try to rebuild everything from scratch -- delete all object files, intermediate and temporary files and compile the whole project from scratch, turn it on an off again, it may be that there's some peculiar bug and/or weirdness in the build system or something.
- If you had a working build, added something and it's bugging and you're stuck debugging it, it may be faster to just scratch your changes, revert to the working code and implement the thing again, more carefully. First time you may have just made some stupid oversight that's hard to find, you won't make it again the second time.
- When stuck also maybe try the program on different computer, use different compiler, interpreter etc. -- this can at least give you more information, i.e. like that the bug is specific to your operating system or that it doesn't happen if there is more RAM etc.
- ...
### Shotgun Debugging
This is kind of an improper YOLO way of trying to fix bugs, you just change a lot of stuff in your program in hopes a bug will go away, it rarely works, doesn't really get rid of the bug (just of its manifestation at best) and can at best perhaps be a simple [hotfix](hotfix.md). Remember **if you don't understand how you fixed a bug, you didn't actually fix it**.