Skip to content
AI Coding

Debugging and Refactoring Code with AI

Reproduce a shared-state bug, write a regression test, and refactor within a fixed API.

Overview

For learners who want to complete this practical exercise with inspectable sample data and explicit checks.

  • Give an AI assistant a minimal reproducible failure.
  • Write a regression test before fixing the code and preserve the API.
  • Separate a bug fix from style changes.

You will build: A function without shared default-list state and a regression test.

Prerequisites

  • Python 3.10+ and a coding assistant; basic functions and lists.

The example runs offline with the standard library. The analysis assistant is optional and may have account limits or fees.

Step-by-step tutorial

Conceptual practice workflow; not measured results.

Step 1. Reproduce the failure before asking AI

Save the first snippet as bug.py and run it before editing so the original failure remains observable. The default list is created when the function is defined, not separately for every call.

PYTHON
def add_task(task, tasks=[]):
    tasks.append(task)
    return tasks

print(add_task("A"))
print(add_task("B"))

Step 2. Provide evidence and bound the fix

PROMPT · INSTRUCTIONS
add_task(task, tasks=[]) retains earlier tasks across calls. Calls without tasks should get a fresh list; calls with an explicit list should still mutate that list. Write a regression test that fails before the fix. Then propose the smallest change, preserving the function name and return type. Do not restructure the project.

Step 3. Fix and verify both contracts

Replace the file with the complete example below. The first assertions check independent calls; the later assertions preserve explicit-list mutation. Avoid tasks = tasks or [] because that would discard an empty list supplied by the caller.

PYTHON
def add_task(task, tasks=None):
    if tasks is None:
        tasks = []
    tasks.append(task)
    return tasks

assert add_task("A") == ["A"]
assert add_task("B") == ["B"]
existing = []
assert add_task("C", existing) is existing
assert existing == ["C"]
print("regression checks passed")

Step 4. Refactor with evidence

Once the checks pass, request one independent change, such as a docstring describing mutation. Rerun the checks and inspect the diff to confirm that documentation did not alter behavior. Ask what remains untested rather than asking the assistant to guarantee bug-free code.

Verification

  • The original reproduces the failure; the fix passes all four assertions.
  • An explicitly passed list keeps its identity, including an empty list.

Troubleshooting

Tests check only a single call

Cause: There is no sequence that exposes shared state.

Fix: Call the function twice without tasks and compare both results.

The assistant changes the API and several modules

Cause: The refactoring request is unbounded.

Fix: Discard only unaccepted practice changes, restate the contract, and request a smaller diff.

Exercises & extensions

  • Convert assertions to unittest and test a list with existing entries.
  • Repeat the workflow with negative-input handling in the Copilot tutorial.

This illustrates mutable defaults and regression testing. A small test does not establish correctness on all paths; production input validation should not rely on assert.

References

  1. Python default arguments
  2. Python unittest

Keep practicing