1AI-Assisted Debugging
▶
Debugging is one of the most important skills an engineer-programmer can develop. Even the most carefully written code will occasionally fail, and the ability to diagnose, understand, and correct those failures systematically separates effective developers from frustrated ones. Modern AI tools — such as large language model assistants — have become powerful allies in this process, capable of explaining cryptic error messages, suggesting probable causes, and proposing fixes. But to use them well, you must first develop the habit of reading and understanding errors yourself. This module covers how to read Python error output, how to write prompts that get genuinely useful AI assistance, which errors appear most often in object-oriented engineering code, and how to follow a disciplined debugging strategy that keeps you in control of the process.
Reading and Interpreting Error Messages
When Python encounters a problem it cannot recover from, it raises an exception and prints a traceback — a structured report that tells you exactly what the program was doing at the moment of failure. New programmers often skim tracebacks or skip straight to the last line, but reading the whole thing carefully provides far more information than any single line alone.
The last line of a traceback is the most immediately actionable: it states the error type followed by a colon and a short human-readable description of the problem. For example:
AttributeError: 'Beam' object has no attribute 'length'
This one line tells you the category of problem (AttributeError) and the specific detail Python noticed ('Beam' object has no attribute 'length'). Knowing the error type alone lets you narrow the space of likely causes dramatically before you look at a single line of your own code. An AttributeError almost always means an attribute was never created or its name was typed incorrectly. A TypeError almost always means the wrong number or type of arguments was supplied to a function or method.
Above that final line, Python prints the traceback chain — a sequence of "frames" listed from the outermost call down to the innermost. Each frame shows the file name, line number, and the source line that was executing at that moment. Consider this example:
Traceback (most recent call last):
File "bridge_analysis.py", line 42, in <module>
results = analyze(bridge)
File "bridge_analysis.py", line 18, in analyze
span = bridge.get_span()
File "bridge_analysis.py", line 9, in get_span
return self.length * self.factor
AttributeError: 'Beam' object has no attribute 'length'
Reading from top to bottom: the main script called analyze() at line 42, which called get_span() at line 18, which tried to access self.length at line 9 — and that is where the failure occurred. The file name and line number in each frame pinpoint exactly where to begin your investigation. The innermost frame (the one closest to the error message) is typically the right place to start looking, but the frames above it reveal the context: who called this code, with what object, and under what conditions.
Crucially, you should make a genuine effort to understand the error independently before reaching for an AI tool. Ask yourself: What does this error type mean? What line did it occur on? What was the code on that line trying to do? What object or value was involved? Spending even two minutes answering these questions before you prompt an AI will make your prompt far more specific and the AI's response far more useful. It also builds the diagnostic intuition that makes you faster over time.
Prompting AI Tools to Explain Errors
AI assistants are remarkably capable at interpreting error messages and suggesting fixes, but the quality of the output depends almost entirely on the quality of the input. A vague prompt produces a vague answer. A well-constructed prompt produces a precise, actionable explanation.
The single most important habit is to include both the relevant code and the full traceback in your prompt. Do not paraphrase the error — paste it verbatim. Do not paste only the last line — paste the entire traceback. And do not paste your entire 500-line program — identify the class or function that appears in the traceback and include that section. A prompt shaped like this works extremely well:
Here is the relevant code:
class Beam:
def __init__(self, material, factor):
self.material = material
self.factor = factor
def get_span(self):
return self.length * self.factor
Here is the full traceback I receive when I run it:
Traceback (most recent call last):
File "bridge_analysis.py", line 42, in <module>
results = analyze(bridge)
File "bridge_analysis.py", line 18, in analyze
span = bridge.get_span()
File "bridge_analysis.py", line 9, in get_span
return self.length * self.factor
AttributeError: 'Beam' object has no attribute 'length'
Can you explain what is causing this error?
Notice the final question: "Can you explain what is causing this error?" — not "Fix this for me." This is a deliberate and important distinction. Ask the AI to explain the cause before asking for a fix. An explanation forces you to engage with the logic of the problem. When you understand why the error occurs, you can evaluate whether the proposed fix actually addresses the root cause or merely silences the symptom. A fix that suppresses an error without correcting the underlying design flaw will often create more subtle bugs later.
For the example above, a good AI explanation would note that self.length is referenced in get_span() but was never assigned anywhere in __init__ — the constructor only sets self.material and self.factor. The root cause is a missing attribute assignment. Once you understand that, the fix is obvious: add self.length = length to __init__ and add length as a parameter. You could also evaluate whether length should be computed from other data rather than passed in directly.
After applying any fix, always test and verify. Run the program again and confirm the original error is gone. Then check adjacent behavior — did the fix break any other part of the program? Did it introduce a new error? Did the output change in unexpected ways? This verification step is not optional; it is the moment when you confirm that your mental model of the problem was actually correct.
Common OOP Errors in Engineering Code
Object-oriented programming introduces a specific set of recurring errors that appear again and again across engineering codebases. Recognizing these patterns by name and cause dramatically speeds up debugging.
AttributeError is the most common OOP error. It means the object does not have the attribute the code tried to access. This almost always happens for one of two reasons: either the attribute was never defined (it was supposed to be set in __init__ but wasn't), or its name was misspelled somewhere (perhaps defined as self.lenght but accessed as self.length). Consider:
class Pipe:
def __init__(self, diameter):
self.diamater = diameter # typo: 'diamater' instead of 'diameter'
def area(self):
import math
return math.pi * (self.diameter / 2) ** 2 # AttributeError here
The definition spelled it diamater; the usage spelled it diameter. Python sees two different names: one that exists and one that does not. The fix is to ensure consistent spelling throughout the class.
TypeError in a method context usually signals one of two issues. First, self was omitted from the method definition. If you write def area(diameter): instead of def area(self, diameter):, Python treats diameter as the implicit instance argument, and the actual diameter you pass becomes an unexpected extra argument — resulting in a confusing TypeError: area() takes 1 positional argument but 2 were given. Second, the call arguments do not match the definition: you defined the method to take three parameters but called it with two, or vice versa.
class Truss:
def __init__(self, height, width):
self.height = height
self.width = width
def volume(length): # missing 'self'
return self.height * self.width * length
t = Truss(3, 4)
t.volume(10) # TypeError: volume() takes 1 positional argument but 2 were given
The fix is simply to add self as the first parameter: def volume(self, length):.
Forgetting super().__init__() is a subtler and particularly dangerous error in inheritance hierarchies. When a child class overrides __init__ without calling the parent's __init__, all the attributes the parent was supposed to create are never set up. The error does not necessarily occur at the point of object creation — it occurs later, when code tries to use one of those missing parent attributes:
class Component:
def __init__(self, material, safety_factor):
self.material = material
self.safety_factor = safety_factor
class Column(Component):
def __init__(self, material, safety_factor, height):
# forgot: super().__init__(material, safety_factor)
self.height = height
def safe_load(self, load):
return load / self.safety_factor # AttributeError: 'Column' has no attribute 'safety_factor'
The Column object is created without error, but the moment safe_load tries to use self.safety_factor, Python raises an AttributeError because that attribute was never initialized. The fix is to call super().__init__(material, safety_factor) as the first line of Column.__init__.
IndentationError within a class body means that code was not correctly nested. Python uses indentation to define scope, and within a class, every method must be indented consistently beneath the class header. If a method accidentally has incorrect indentation, Python may treat it as outside the class, leading to an IndentationError or, more insidiously, to a function that exists at module scope rather than as a method. Always ensure that all method definitions inside a class share the same indentation level (typically four spaces or one tab), and that the body of each method is indented one level further.
class Wall:
def __init__(self, thickness):
self.thickness = thickness
def resistance(self): # IndentationError: unexpected indent (or wrong scope)
return self.thickness * 2.5
Systematic Debugging Strategy
Individual techniques for reading errors and prompting AI tools are most powerful when embedded in a consistent, disciplined strategy. Ad hoc debugging — changing things at random until something works — is slow, unreliable, and dangerous because it can introduce new bugs while appearing to fix old ones. The following process provides a reliable framework.
Reproduce the error consistently before attempting any fix. A bug you cannot reliably trigger is a bug you cannot reliably fix. Run the code in a clean environment, with the exact inputs that caused the failure, and confirm the error appears every time. If the error is intermittent, that itself is a diagnostic clue — intermittent errors often arise from uninitialized variables, ordering dependencies, or state that carries over between runs.
Isolate the smallest code section that still produces the error. This step — sometimes called creating a minimal reproducible example — is transformative. Start by commenting out or temporarily removing parts of the program that are not involved in the traceback chain. If the error still occurs with half the code removed, remove half of what remains. Continue until you have the shortest possible snippet that still fails. This process often reveals the cause before you even ask anyone for help, and when you do ask an AI, it gives the AI a focused, clean problem rather than a tangled web of unrelated code.
Form a specific hypothesis about the cause before applying any change. Based on the error type, the traceback, and your understanding of the code, write down (or at least think through) a precise statement like: "I believe this AttributeError occurs because the __init__ method never assigns self.length, even though get_span tries to read it." A hypothesis is testable. A vague feeling that "something is wrong with the class" is not. When you use AI assistance, you can ask it to confirm or challenge your hypothesis, which produces more targeted and trustworthy responses than simply asking it to fix your code.
Verify that the fix resolves the error and does not break other parts of the program. After applying a change, run the full program (not just the isolated snippet) and check that the original error is gone. Run any tests you have. Check the output against expected values. Look at every place in the codebase that uses the function or class you modified — your change may have ripple effects. A fix that silently changes the behavior of other features is often worse than the original bug, because it may go unnoticed until it causes a failure in production or produces incorrect engineering results.
Use AI to generate hypotheses when stuck, but evaluate each one critically before applying. AI tools are not infallible. They can misread context, make incorrect assumptions about what your code is supposed to do, or suggest fixes that work mechanically but are wrong for your specific engineering problem. Treat AI suggestions as informed proposals from a knowledgeable colleague — worth considering seriously, but not worth implementing blindly. Ask the AI to explain its reasoning. If the explanation makes sense given your understanding of the code and the domain, proceed. If it doesn't, ask a follow-up question or investigate further before making any change. The goal is always to understand the code better, not merely to make the error message go away.
When this strategy is followed consistently, debugging becomes a methodical engineering process rather than a frustrating guessing game. AI assistance amplifies your ability to diagnose and fix problems quickly, but the engineer's judgment — applied at every step — is what ensures that the fixes are correct, the code is sound, and the software behaves as intended.