
Picture yourself as a detective, walking into a crime scene where something has gone wrong with your code. The clues are there β you just need to know where to look and how to interpret them. Welcome to the art of debugging and error handling!
The Mystery of the Missing Data
Let’s start with a story that happens all too often in the real world. Sarah, a junior developer, wrote a function to process customer orders:
def process_order(customer_data):
total = customer_data['price'] * customer_data['quantity']
return f"Processing order for {customer_data['name']}: ${total}"
# Sarah's first attempt
order = {'price': 29.99, 'quantity': 2}
print(process_order(order))
When she ran this code, she encountered her first mystery:
KeyError: 'name'
Learning from the First Case π
This is a perfect example of why error handling is crucial. Let’s solve it together:
def process_order(customer_data):
try:
total = customer_data['price'] * customer_data['quantity']
return f"Processing order for {customer_data['name']}: ${total}"
except KeyError as e:
return f"Error: Missing required field - {str(e)}"
except Exception as e:
return f"Unexpected error: {str(e)}"
# Now let's test it
order1 = {'price': 29.99, 'quantity': 2} # Missing name
order2 = {'price': 29.99, 'quantity': 2, 'name': 'Alice'} # Complete data
print(process_order(order1)) # Handles the error gracefully
print(process_order(order2)) # Works as expected
The Case of the Mysterious Data Type
Here’s another real-world scenario. Tom was building a function to calculate discounts:
def apply_discount(price, discount_percentage):
try:
final_price = price - (price * discount_percentage / 100)
return round(final_price, 2)
except TypeError:
return "Error: Price and discount must be numbers"
except ZeroDivisionError:
return "Error: Discount percentage cannot be zero"
# Test cases
print(apply_discount("50", 10)) # String instead of number
print(apply_discount(50, "ten")) # String instead of number
print(apply_discount(50, 10)) # Correct usage
Debug Like a Pro: The SHERLOCK Method
Let me introduce you to a debugging methodology I call SHERLOCK:
- Spot the error message
- Halt and read carefully
- Examine the variable values
- Reproduce the error
- Log strategic points
- Observe patterns
- Check documentation
- Keep it simple
Real-world Debugging Tools
1. Print Debugging
Sometimes the simplest tool is the most effective:
def calculate_total(items):
total = 0
for item in items:
print(f"Processing item: {item}") # Debug print
try:
total += float(item['price'])
except (KeyError, ValueError) as e:
print(f"Error processing item: {e}") # Debug print
continue
return total
2. Using Python’s debugger (pdb)
import pdb
def complex_calculation(data):
result = 0
for i, value in enumerate(data):
pdb.set_trace() # Debugger will pause here
result += value * (i + 1)
return result
3. Exception Hierarchy
Understanding Python’s exception hierarchy is crucial:
try:
# Potentially dangerous operations
file = open("nonexistent.txt")
data = file.read()
except FileNotFoundError:
print("The file doesn't exist!")
except PermissionError:
print("You don't have permission to access this file!")
except IOError as e:
print(f"An I/O error occurred: {e}")
finally:
print("This will always execute")
Best Practices for Error Handling
- Be Specific: Catch specific exceptions rather than using bare
except
clauses:
# Good
try:
value = int(user_input)
except ValueError:
print("Please enter a valid number")
# Bad
try:
value = int(user_input)
except: # Too broad!
print("Something went wrong")
- Custom Exceptions: Create custom exceptions for your application’s specific needs:
class InvalidOrderError(Exception):
pass
class OutOfStockError(Exception):
pass
def process_order(item_id, quantity):
try:
if not is_valid_item(item_id):
raise InvalidOrderError("Invalid item ID")
if not check_stock(item_id, quantity):
raise OutOfStockError("Not enough items in stock")
except (InvalidOrderError, OutOfStockError) as e:
log_error(e)
raise
Common Debugging Scenarios and Solutions
Scenario 1: The Silent Failure
def divide_numbers(a, b):
try:
result = a / b
return result
except ZeroDivisionError:
print("Cannot divide by zero!")
return None
except TypeError as e:
print(f"Invalid input types: {e}")
return None
Scenario 2: The Mysterious Loop
def process_list(items):
processed = []
for i, item in enumerate(items):
try:
processed.append(item.strip())
except AttributeError:
print(f"Warning: Item at position {i} is not a string")
processed.append(str(item))
return processed
Summary
π Get the complete demo script on GitHub
=== Debugging and Error Handling Demonstration ===
π Testing Order Processing:
Order 1: {'price': 29.99, 'quantity': 2}
2024-11-22 12:34:56,789 - ERROR - Missing field in order data: 'name'
Result: Error: Missing required field - 'name'
Order 2: {'price': 29.99, 'quantity': 2, 'name': 'Alice'}
Result: Processing order for Alice: $59.98
Order 3: {'price': '29.99', 'quantity': '2', 'name': 'Bob'}
Result: Processing order for Bob: $59.98
π Testing Discount Calculator:
Testing price=50, discount=10
Result: 45.0
Testing price='50', discount=10
Result: Price and discount must be numbers
Testing price=50, discount=ten
Result: Price and discount must be numbers
Testing price=50, discount=0
Result: Discount percentage cannot be zero
Testing price=50, discount=150
Result: Discount percentage must be between 0 and 100
π Testing Item Processing:
Processing items: [{'price': '25.99', 'quantity': 2}, {'price': 'invalid', 'quantity': 1}, {'price': 15.99, 'quantity': '3'}, {'price': 10.50, 'quantity': 2}]
2024-11-22 12:34:57,123 - DEBUG - Processing item 1: {'price': '25.99', 'quantity': 2}
2024-11-22 12:34:57,123 - DEBUG - Successfully processed item 1. Subtotal: $51.98
2024-11-22 12:34:57,124 - DEBUG - Processing item 2: {'price': 'invalid', 'quantity': 1}
2024-11-22 12:34:57,124 - ERROR - Error processing item 2: could not convert string to float: 'invalid'
2024-11-22 12:34:57,125 - DEBUG - Processing item 3: {'price': 15.99, 'quantity': '3'}
2024-11-22 12:34:57,125 - DEBUG - Successfully processed item 3. Subtotal: $47.97
2024-11-22 12:34:57,126 - DEBUG - Processing item 4: {'price': 10.50, 'quantity': 2}
2024-11-22 12:34:57,126 - DEBUG - Successfully processed item 4. Subtotal: $21.00
Final total: $120.95
Conclusion
Remember, debugging is not just about fixing errors β it’s about understanding your code deeply. Each error is an opportunity to learn and improve your coding skills. Keep these tools and techniques in your detective toolkit, and you’ll be solving coding mysteries like a pro in no time!
Quick Reference: Debugging Checklist
- β Read the error message carefully
- β Check variable types and values
- β Use print statements strategically
- β Implement proper error handling
- β Test edge cases
- β Document your findings
- β Learn from each bug
Remember: Life is like programming β we learn, debug, and upgrade every day! π