PY-1.2-BP6-MQ01

Objective

Welcome to the "What is a Bug?" micro-quest!

Our objective is to understand what bugs are (errors in code that cause unexpected behavior) and introduce the idea of debugging. We'll use a simple example of a function that is intended to add two numbers but currently has a mistake.

Check the Specification

Before we start coding, let's run the tests to see the current state of the project and understand the engineering specification we need to meet.

Run pytest in your terminal.

Test Results:

  • test_add_numbers_various_cases

Implement `main.py`

Now, let's implement the required functionality by following the TODO comments in the main.py file.

Step by step checklist:

  1. Implement the logic to return the sum of num1 and num2.

The following documentation sections are going to be helpful:

  • What is a Bug?

Validate Your Work

You've implemented the required code. Now, let's run the tests again to validate that your solution meets the specification.

Run pytest in your terminal.

Test Results:

  • test_add_numbers_various_cases All tests passed!

Prepare for Code Review

Great job! You've successfully fixed the bug and made the function work as intended.

Now, prepare for your code review with your mentor. Be ready to discuss:

  • What the original bug was.
  • How you identified the bug (even in this simple case).
  • How you fixed the bug.
  • How the tests helped confirm your fix.

Documentation

Blueprint PY-1.2-BP6: The Inspector - Debugging and Refactoring

This document provides a quick reference for key concepts and techniques covered in this blueprint, focusing on finding and fixing errors (debugging) and improving code structure (refactoring).

What is a Bug?

A bug is an error in a computer program that causes it to produce an incorrect or unexpected result, or to behave in an unintended way. Bugs are a normal part of the software development process.

Debugging is the process of finding and resolving defects or problems within a computer program.

A simple diagram showing an input going into a 'buggy' process and producing a wrong output, then going into a 'fixed' process and producing the correct output.

Debugging Methods

Debugging involves investigating your code to understand why it isn't behaving as expected.

Method 1: Print Statement Debugging

Using print() statements is a fundamental technique to inspect the state of your program and understand its execution flow.

  • Purpose: Check the value of variables at specific points, confirm if certain parts of the code are being reached, or track the order of execution.
  • How to use: Insert print() calls with descriptive messages and variable values where you suspect an issue might be occurring.
# Example: Checking a variable's value inside a loop
count = 0
while count < 5:
    print(f"Current count: {count}") # Check the value of count
    # ... other code ...
    count += 1
print("Loop finished.") # Confirm the loop completed

Method 2: The IDE Debugger

Your code editor (like VS Code) has powerful, built-in tools for debugging.

  • Breakpoint: A marker you place on a line of code. When you run the debugger, your program will pause its execution at that exact line.
  • Step Over: A control that executes the currently paused line and immediately pauses again on the next line. This lets you walk through your code one line at a time.
  • Variable Inspector: A panel that shows you the live values of all your variables at the moment the program is paused.

A GIF showing a breakpoint being set in VS Code, the debugger launching, and the 'Step Over' button being clicked to advance line-by-line, with the Variable Inspector panel visible.

What is Refactoring?

Refactoring is the process of restructuring existing computer code without changing its external behavior. The goal is to improve nonfunctional attributes of the software, such as readability, maintainability, and simplicity.

Refactoring is like simplifying an algebraic expression; the value is the same, but the form is cleaner and easier to work with.

Image comparing a messy, unrefactored code snippet with a clean, refactored version that produces the same output.

Refactoring Techniques

Technique 1: Extract Function

If a block of code performs a single, well-defined task, move it into its own function.

  • Purpose: Makes the original code (e.g., a main loop) shorter and easier to read. Allows the extracted logic to be reused elsewhere.
  • How to use:
    1. Identify a block of code that does one thing.
    2. Create a new function.
    3. Move the code block into the new function.
    4. Identify any variables the code block needs from its original context (inputs) and any results it produces (outputs). Define these as function parameters and return values.
    5. Replace the original code block with a call to the new function, passing the necessary inputs.
# Before Refactoring (example)
# ... inside a loop ...
# Calculate area
width = 10
height = 20
area = width * height
print(f"Area: {area}")
# ... rest of loop ...

# After Refactoring (example)
def calculate_area(width, height):
    """Calculates the area of a rectangle."""
    area = width * height
    return area

# ... inside the loop ...
width = 10
height = 20
area = calculate_area(width, height) # Call the new function
print(f"Area: {area}")
# ... rest of loop ...

A diagram showing a large block of code with a smaller section highlighted, and an arrow pointing to a separate function box containing that smaller section, with the original block now showing a call to the new function.

Technique 2: Improve Readability

Write code that is easy for others (and your future self) to understand.

  • Purpose: Reduces the time and effort needed to read, understand, and modify code.
  • How to use:
    • Use clear, descriptive variable and function names (e.g., total_price instead of tp).
    • Break down complex calculations or logic into smaller, named steps using intermediate variables.
# Less readable (example)
# Calculate final price with tax
p = 100
t = p + (p * 0.08)
final = round(t, 2)

# More readable (example)
# Calculate final price with tax
subtotal = 100
tax_rate = 0.08
tax_amount = subtotal * tax_rate
total_price = subtotal + tax_amount
final_price_rounded = round(total_price, 2)

Pygame/Turtle Specifics

Handling Events

In Pygame, the event loop processes user input and system events.

  • Check the event.type attribute to identify the type of event (e.g., pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN).
  • For key events (pygame.KEYDOWN, pygame.KEYUP), use event.key to identify which key was pressed (e.g., pygame.K_r).
  • For mouse button events (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP), use event.pos to get the mouse coordinates.
# Example Pygame event loop structure
import pygame
# ... setup ...
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            # Check event.key here
            pass
        if event.type == pygame.MOUSEBUTTONDOWN:
            # Access event.pos here
            pass
    # ... drawing and update ...

Drawing Order

In graphics programming, the order in which you draw is crucial. Objects drawn later appear on top of objects drawn earlier.

  • Always draw the background first to clear the screen from the previous frame.
  • Then, draw all other game elements (shapes, sprites, text) on top of the background.
  • Finally, update the display (pygame.display.flip() or pygame.display.update()) to show the completed frame.
# Example Pygame drawing order
# ... inside the game loop ...
# 1. Draw background
screen.fill(BACKGROUND_COLOR)

# 2. Draw game elements
pygame.draw.rect(screen, PLAYER_COLOR, player_rect)
# ... draw other objects ...

# 3. Update display
pygame.display.flip()
# ...

Using Global Variables

Variables defined outside of any function are called global variables. To modify a global variable from inside a function, you must use the global keyword.

  • Reading a global variable inside a function does not require the global keyword.
  • Writing to (assigning a new value to) a global variable does require the global keyword inside the function.
# Example using global
counter = 0 # Global variable

def increment_counter():
    global counter # Declare intent to modify the global 'counter'
    counter += 1

def read_counter():
    # No 'global' needed to just read the value
    print(f"Current counter value: {counter}")

# ...
increment_counter()
read_counter() # Output: Current counter value: 1

Adding Sound with Pygame Mixer

Adding sound effects can make an application more engaging.

  • Initialize: Start the mixer module.
    import pygame
    pygame.mixer.init()
    
  • Load: Load a sound file into a pygame.mixer.Sound object.
    # Assumes 'sound.wav' is in the same directory
    sound_effect = pygame.mixer.Sound("sound.wav")
    
  • Play: Call the .play() method on the sound object when the corresponding action occurs.
    # Example inside a function triggered by an event
    def on_action():
        # ... perform action ...
        sound_effect.play() # Play the sound