Core Python / Debugging

Contents

Core Python / Debugging#

Review Questions#

  1. What is the goal of debugging?

    To fix problems that have been identified with a program.

  2. Describe a systematic debugging process. Why should such a process be used over just guessing?

    The systematic debugging process described in the material follows the scientific method:

    • Study the data/failure - Look at the test input, incorrect output, failed assertions, stack traces, etc. Reproduce the issue.

    • Hypothesize - Based on the data, form a hypothesis about where the bug might be located or what might be causing it.

    • Experiment - Devise an experiment to test your hypothesis, like adding print statements, assertions, or using a debugger.

    • Repeat - Based on the results of your experiment, update your hypothesis and run a new experiment. Keep iterating.

    This systematic process should be used instead of just randomly guessing and making code changes because:

    • Ensures you are methodically gathering data before making changes.

    • Prevents you from just making blind guesses that could mask real issues.

    • Hypothesis-driven approach helps narrow down where to look for the bug efficiently.

    • Build increasing confidence in your understanding of the root cause before attempting a fix.

    • Writing down hypotheses and keeping an audit trail prevents you from losing track of what you’ve tried.

    In contrast, guessing and randomly changing code is inefficient and doesn’t build a solid understanding of the issue. Following a systematic, scientific process is crucial for effective debugging, especially for complex issues.

  3. Why is it important to find a small, repeatable test case that causes the failure?

    • It makes debugging easier by allowing you to run the failing case over and over again while hunting for the bug.

    • After fixing the bug, you can keep the small test case in your regression test suite, so the bug never crops up again.

    • Having a minimal test case makes it easier to share and reproduce the issue when asking others for help debugging.

    • Reducing the test case to its essentials helps isolate exactly what inputs/conditions are triggering the buggy behavior.

  4. What technique involves comparing successful runs to failing runs to isolate bug causes?

    Delta debugging involves comparing successful runs to failing runs in order to try to isolate the causes of a bug.

  5. Which parts of a system are generally more trustworthy and less likely to contain bugs?

    • Older, well-tested code

    • Standard libraries and code that is part of the core programming language

    • The programming language compiler and runtime

    • The operating system

    • The hardware

    The reasoning is that these lower levels of the system stack have been more thoroughly tested and used across many applications over long periods, giving them a chance to get exercised thoroughly and have bugs shaken out.

    On the other hand, your own application code, especially recent code you’ve just written, is more likely to contain bugs since it hasn’t gone through the same depth of testing and real-world use yet. From a class perspective, solutions have also been written by instructors, TAs, and fellow students.

    Using new modules/packages that are not widely adopted may be a source of errors, though. Look at recent issues or perform web searches to see if others have experienced the same problems.

    So when debugging, you should generally trust the lower levels like languages, libraries, OS, and hardware until you find concrete evidence that the bug lies in those components. Your own code should be the higher priority area to scrutinize for bugs.

  6. Describe three examples of probes.

    • print statements: Output the values of variables or other diagnostic information at key points in the code’s execution. This allows you to inspect the state of the program as it runs.

    • assertions: Check conditions about the program state that should be true unless there is a bug. If an assertion fails, it raises an exception.

    • debugger: Pause execution at certain lines of code and examine the current state by inspecting variables, single-stepping through code, etc.

  7. What does “slicing” refer to when debugging?

    “Slicing” refers to the technique of analyzing the code to narrow down which lines could potentially be contributing to a buggy value or bad program state. The idea is to systematically eliminate code sections or statements that definitely cannot affect the variable/state you are investigating. This allows you to reduce or “slice” the amount of code you need to scrutinize for the bug’s cause.

  8. How can design choices like immutability and minimal scope help slicing?

    Immutability

    1. Consistency and Predictability:

      • Stable State: Since immutable objects do not change after they are created, their state remains consistent throughout the execution of the program. This means that when you inspect a slice of data, you can be confident that its state is exactly what it was when the slice was created.

      • Deterministic Behavior: Debugging is easier when the behavior of data is predictable. Immutable objects ensure that once a slice is created, it won’t be altered by any part of the program, making it simpler to track the flow and transformations of data.

    2. Elimination of Side Effects: With immutability, you can rule out the possibility of side effects from unexpected modifications, which narrows down the potential causes of bugs. This helps in isolating the exact point where something went wrong.

    Minimal Scope

    1. Reduced Complexity:

      • Localized Variables: When variables and data structures are confined to a minimal scope, they are easier to manage and understand. This reduced complexity means that when you are debugging, you have fewer variables and states to keep track of, making it easier to pinpoint issues.

      • Focused Debugging: Minimal scope limits the number of places where variables are used, allowing you to focus your debugging efforts on smaller, well-defined sections of code.

    2. Improved Clarity:

      • Clear Boundaries: Variables and data within a minimal scope have clear boundaries of use. This clarity helps in understanding how data is being manipulated and makes it easier to trace the source of errors when inspecting slices of data.

      • Simplified Tracing: With minimal scope, the flow of data and its transformations are easier to trace. When a bug arises, you can more readily follow the data’s path and identify where things might have gone wrong.

    3. Efficient Resource Management: Minimal scope ensures that resources are allocated and deallocated in a timely manner, reducing the risk of stale or leftover data causing confusion during debugging. This helps in maintaining a cleaner state of the program, which is easier to inspect.

  9. What is the main downside of using print statements for debugging?

    The main downside of using print statements for debugging is that they can clutter the code and make it harder to maintain and read. While print statements can be a quick and simple way to gain insight into what a program is doing, print will also need to be removed/commented.

    Ideally, you should use an interactive debugger.

  10. What does “swapping components” refer to when debugging? Why is this generally not preferred?

    “Swapping components” refers to replacing a suspected buggy component or module of your program with an alternative implementation that satisfies the same interface. This technique is generally not preferred for a few reasons:

    • It can waste a lot of time swapping out components that end up not being faulty.

    • Lower-level components like core language libraries, OS, hardware etc should be trusted until proven guilty - they are very well-tested.

    • Replacing key components could mask the real bug or even introduce new bugs in integration.

    So while swapping can be useful if you have strong evidence implicating a specific component, it’s usually better to first use other debugging techniques like probes, logging, slicing etc to understand the bug before making larger swaps.

  11. What is “rubber duck debugging”?

    “Rubber duck debugging” is an informal term used to describe the act of explaining your code line-by-line to an inanimate object like a rubber duck, teddy bear, or even a human who doesn’t understand the code. The idea behind rubber duck debugging is that by going through your code very explicitly and having to verbalize each step, you often end up catching logical flaws or assumptions in your reasoning that you may have missed before. Even though you’re just talking to an object that cannot respond, the act of vocalizing and explaining the code forces you to slow down and truly analyze each part more carefully.

  12. How does a “debugger” fit into the debugging process?

    A debugger helps you to gather information and test hypotheses without modifying source code.

  13. Explain the difference between a breakpoint and a watchpoint for a debugger.

    A breakpoint causes execution to stop at a particular line while a watchpoint causes execution to stop when a condition is met such as a variable changing.

  14. What does an assert statement do?

    An assertion is a “statement of fact”. If that condition (the expected state of the program) is false, then the Python interpreter raises an AssertionError.

  15. What is required for a hypothesis to be testable?

    It must make specific predictions about the program’s behavior on some category of inputs.