Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Making Python Code More Reliable

Reliable code comes from checking, observing, and fixing

Once code is spread across functions, modules, and packages, trust matters as much as structure. Testing checks expected behavior, debugging finds the cause of wrong behavior, and logging leaves a trail you can inspect when systems become harder to reason about.

Why this matters

A project can be well organized and still be unreliable. Reliability practices matter because they help you:

  • catch mistakes before users or teammates do

  • understand why a function behaved differently than expected

  • trace what happened in a longer workflow or pipeline

  • make growing codebases safer to change over time

Continuity from the modules notebook

Modules and packages organize the system. Testing and debugging help you trust that the organized system still works.

Core Explanation

Testing asks, Does this code produce the expected result? Debugging asks, Why did this code fail or behave unexpectedly? Logging asks, What happened while the program was running?

These are related but different habits. Testing checks outcomes. Debugging diagnoses causes. Logging records signals that help you understand behavior over time.

Three practical reliability moves

Testing
Debugging
Logging

Write small checks that compare actual results with expected results.

def safe_divide(a, b):
    if b == 0:
        return "Cannot divide by zero"
    return a / b

print(safe_divide(10, 2))
print(safe_divide(10, 0))
5.0
Cannot divide by zero

Worked Example: test the normal case and the edge case

A function becomes easier to trust when you check more than one kind of input.

Example mindset

Instead of assuming a function works, try a normal case, an edge case, and an invalid case when possible. That habit catches many mistakes early.

Logging idea

Even a simple print statement can act like a beginner-friendly log when you are tracing a workflow.

def process_order(order_id):
    print(f"Starting order {order_id}")
    print("Checking inventory")
    print("Finishing order")

Guided Check

What is the main goal of debugging?

To find and fix the cause of incorrect behaviorCorrect. Debugging is about understanding and repairing problems.
To rename all variablesRenaming may help readability, but it is not the core goal of debugging.
To avoid writing testsTesting and debugging often support each other.

Exercises

Exercise 1: Write a simple test

Create a short check for a function that adds two values and prints whether the result matches what you expected.

Hint

Pick one input pair such as 2 and 3, compute the function result, and compare it to 5.


Exercise 2: Describe a debugging plan

A program gives the wrong output but does not crash. Describe the first three things you would inspect before changing code.

Hint

Think in order: expected output, actual output, and the smallest part of the program where they begin to differ.


Exercise 3: Explain logging

Give one example of something you would log in a business workflow such as order processing, scoring, or reporting.

Hint

Useful logs often mention what step started, what key value changed, or why a branch was taken.

Quick Summary
  • testing checks expected behavior

  • debugging identifies the cause of failures

  • logging helps trace what happened during execution

  • reliability practices save time as projects and teams grow

The next notebook is a short self-check covering the chapter.

def is_even(value):
    return value % 2 == 0

print(is_even(4))
print(is_even(5))
True
False

Practice Lab

Expected output
True
False
Expected output
5.0
Cannot divide by zero
Extension idea

Add a third check that confirms safe_divide(9, 3) returns 3.0, or add a print-based log that shows which branch the function took.

Guided Practice

Why are tests useful?

They remove the need to think about logicTests help verify logic, but they do not replace reasoning.
They check whether code behaves as expectedCorrect. Tests verify expected behavior.
They only matter for web appsTesting matters across many software types.
They automatically optimize performanceTests do not automatically improve speed.

What should `safe_divide(10, 0)` return in the example above?

0The function is designed to return a message instead.
10That would not make sense for division by zero.
Cannot divide by zeroCorrect. The guard handles the invalid case explicitly.
InfinityThat is not what this function returns.

Key Takeaway

Reliable code is rarely produced by luck. It comes from checking assumptions, observing behavior, and improving the program deliberately.