Debugging
Debugging and testing exist because the proof covered in the previous topic is the gold standard for certainty, but writing a full proof for every real program is rarely practical, so most software is instead debugged and tested against specific cases until it can be trusted. Finding errors starts with noticing a symptom: the `calculate_total_size` function from programming fundamentals might report a folder is larger than it actually is, and debugging is the process of working backward from that wrong output to the specific line of code responsible for it. Tracing execution means following a program step by step, the same instruction-by-instruction spirit as the fetch-decode-execute cycle from the very first topic in this series, except done by a person instead of a CPU: printing out the value of `total` after every loop iteration shows exactly where it starts growing by the wrong amount, which immediately narrows the search from "somewhere in this function" down to one specific line, often the moment a subdirectory's own size gets added in by mistake instead of being skipped. An assertion is a statement that must be true at a specific point in a program, written directly into the code and checked automatically every time that point is reached — `assert total >= 0` after the loop is a logical proposition in exactly the sense the previous topic described, and if it's ever false the program stops immediately with a clear signal of exactly where things went wrong, rather than continuing to run and producing a silently incorrect answer somewhere downstream. A test case is a specific input paired with the output it's already known to produce, run against the function to check whether it still behaves correctly: calling `calculate_total_size` on an empty folder should return 0, calling it on a folder containing one 10-byte file should return 10, and calling it on a folder containing a subdirectory should return only the sizes of the actual files, not the subdirectory itself — and this is precisely where testing and proof diverge, since a test case only confirms the function is correct for the specific inputs actually tried, while a proof would confirm it for every possible input at once, which is why a thorough set of test cases is the practical, achievable substitute for a proof that usually isn't. Running the fixed function against all three of those test cases after the bug is found and corrected is what actually confirms the fix worked, and keeping those same test cases around afterward catches the exact same bug immediately if it is ever accidentally reintroduced later.