AmouAI Hub/Courses/Programming Fundamentals/Day 5
Debugging, Clean Code & Mini-Project 1
Bugs are not a sign that you are bad at this. They are the normal state of code in progress. Today you stop finding them by accident and start finding them on purpose.
By the end of today you can
- Tell syntax, runtime and semantic errors apart from the symptom alone
- Read a traceback from the bottom up and go straight to the cause
- Use labelled print statements and the VS Code debugger effectively
- Apply scientific debugging: hypothesis, minimal test, conclusion
- Recognise and fix the eight errors you will actually encounter this course
- Write code someone else can read: names, constants, structure
- Deliver Mini-Project 1
▶Today's videos
Watch each video, then work the matching sections below. Watching alone will not do it.
1Three families of error
The symptom tells you which family you are in, and that tells you where to look.
| Family | When you find out | How it announces itself | Difficulty |
|---|---|---|---|
| Syntax | Before anything runs | Python refuses to start. Points at a line. | Easy |
| Runtime | Part-way through | Crashes with a traceback naming the error type | Moderate |
| Semantic | Maybe never | Runs perfectly. Answer is wrong. | Hard |
# SYNTAX - Python cannot even read this
if x > 5
print("big")
# SyntaxError: expected ':'
# RUNTIME - reads fine, explodes when it gets there
numbers = [1, 2, 3]
print(numbers[5])
# IndexError: list index out of range
# SEMANTIC - runs, produces an answer, the answer is wrong
total = 0
for i in range(5):
total += i
print(f"Sum of 1 to 5 is {total}")
# prints 10. The correct answer is 15.A crash tells you where to look. A wrong answer tells you nothing. This is precisely why you test with values whose answer you already know - and why, from Day 15, you will write those tests down.
2Reading a traceback
Read it bottom-up. The last line is what went wrong; the lines above it are how you got there.
The eight errors you will actually meet
| Error | Plain English | Usual cause |
|---|---|---|
SyntaxError | I cannot read this | Missing colon, unbalanced bracket, stray quote. Check the line above the one reported — an unclosed bracket is reported on the next line. |
IndentationError | Your blocks do not line up | Inconsistent indentation, or a block body missing entirely |
NameError | I have never heard of this name | Typo, or using a variable before assigning it |
TypeError | You cannot do that to this kind of thing | "5" + 5. Almost always a missing int() or str() |
ValueError | Right type, impossible value | int("abc") |
IndexError | There is nothing at that position | Off-by-one; range(len(s) + 1) |
ZeroDivisionError | You divided by zero | An empty collection, or a count that never got incremented |
AttributeError | That kind of thing has no such method | "text".push(), or calling a string method on a number |
3A method, not a mood
Staring harder does not work. These four techniques do.
1. Print debugging, done properly
# useless - a column of numbers with no context
for i in range(len(data)):
print(data[i])
# diagnostic - this IS a trace table
for i in range(len(data)):
print(f"[loop] i={i} data[i]={data[i]} total={total}")And binary-search your program. If the bug is somewhere in 100 lines, put one labelled print at line 50. Is the state correct there? Now you only have 50 lines to search. Repeat. Seven prints locate a bug in a thousand lines.
2. The debugger
In VS Code: click in the gutter to the left of a line number to set a breakpoint, then press F5. Execution pauses there and you can inspect every variable.
| Key | Action | Use when |
|---|---|---|
| F5 | Continue | Run to the next breakpoint |
| F10 | Step over | Run this line, do not go inside function calls |
| F11 | Step into | Go inside the function on this line |
| Shift+F11 | Step out | Finish this function and come back |
The Variables panel shows everything currently in scope, updating as you step. Fifteen minutes learning this saves you many hours over the next twenty days.
3. Scientific debugging
- Observe precisely. Not "it does not work" but "it returns 10 when it should return 15."
- Hypothesise one specific cause. "The loop starts at 0 instead of 1."
- Predict what you would see if that were true. "Then the first value added is 0."
- Test the smallest thing that could confirm or kill it. One print inside the loop.
- Conclude, then fix. If the hypothesis was wrong, you have still eliminated something.
If you change four things and the bug goes away, you have learned nothing and probably introduced two new problems. Change one thing, re-run, observe.
4. Rubber-duck debugging
Explain your code, out loud, line by line, to an object that cannot help you. The bug usually surfaces mid-sentence, right around the moment you say "and then it just..." and stop. This is not a joke - it is the highest-yield technique on this list, because it forces you to state what each line actually does rather than what you assumed.
Prevention beats cure
- Small increments. Write five lines, run it. Not fifty lines, run it.
- Test the edges. Zero. One. Empty. Negative. The maximum. Bugs live at boundaries.
- Assert your assumptions.
assert count > 0, "count should never be zero here"costs one line and fails loudly at the right moment.
4Code someone else can read
Including you, in six weeks, with no memory of writing it.
Here is a working program. It is also unreadable.
d = [23,45,12,67,34,89,21]
t = 0
for i in d:
t = t + i
a = t / len(d)
c = 0
for i in d:
if i > a:
c = c + 1
print(a, c, c/len(d)*100)Same behaviour, rewritten:
PERCENT = 100
scores = [23, 45, 12, 67, 34, 89, 21]
total = 0
for score in scores:
total += score
average = total / len(scores)
above_average_count = 0
for score in scores:
if score > average:
above_average_count += 1
above_average_percent = above_average_count / len(scores) * PERCENT
print(f"Average: {average:.2f}")
print(f"Above average: {above_average_count} of {len(scores)}")
print(f"Percent above: {above_average_percent:.1f}%")| Rule | Bad | Good |
|---|---|---|
| Names state intent | d, t, c | scores, total, above_average_count |
| No magic numbers | x * 100 | x * PERCENT |
| Constants in CAPS | tax = 0.15 | TAX_RATE = 0.15 |
| One idea per line | print(a, c, c/len(d)*100) | Three labelled prints |
| Blank lines separate stages | One dense block | Input / process / output |
| Format your output | 41.57142857142857 | 41.57 |
Comments that earn their place
# Bad - restates the obvious
total = total + score # add score to total
# Good - explains WHY, which the code cannot
# Prices are held in cents because float arithmetic
# loses accuracy when totalling thousands of line items.
price_cents = 1250
# Good - flags a decision a reader would question
# We stop at the square root: any factor above it
# necessarily pairs with one below it.
while divisor * divisor <= n:PEP 8, enforced by tools
Four spaces per indent level. snake_case for variables and functions. CAPS for
constants. Spaces around operators. Lines under about 88 characters. Two blank lines between top-level
definitions.
pip install black ruff
black my_program.py # reformats the file in place, no arguments to bikeshed over
ruff check my_program.py # flags unused variables, undefined names, likely mistakesFormatting debates are a waste of a team's time. Run black, accept its answer, move on. Set VS Code to format on save and stop thinking about it.
5Mini-Project 1: Number Analyzer
Everything from Week 1, in one program. This is your first graded deliverable.
The brief
Write number_analyzer.py. It collects numbers from the user, then prints a statistical
report about them.
Required behaviour
- Repeatedly ask the user for a number. The sentinel
doneends input. - Reject anything that is not a valid number, with a clear message, and keep asking.
- Once input ends, print a formatted report containing:
- count of numbers entered
- sum, to 2 decimal places
- mean, to 2 decimal places
- minimum and maximum
- range (max minus min)
- how many were even and how many odd (whole numbers only)
- how many were above the mean
- Handle the empty case: if the user enters no numbers at all, say so and exit cleanly —
do not crash with a
ZeroDivisionError. - Handle a single number correctly (range is 0, one number, and it is not above its own mean).
Sample run
Enter a number (or 'done' to finish): 12
Enter a number (or 'done' to finish): 7
Enter a number (or 'done' to finish): abc
'abc' is not a number. Try again.
Enter a number (or 'done' to finish): 25
Enter a number (or 'done' to finish): 3
Enter a number (or 'done' to finish): done
========================================
NUMBER ANALYSIS
========================================
Count: 4
Sum: 47.00
Mean: 11.75
Minimum: 3.00
Maximum: 25.00
Range: 22.00
Even: 1
Odd: 3
Above mean: 2
========================================Constraints
- Use only what Week 1 covered: variables, types, operators, conditionals, loops, string methods, f-strings.
- No
sum(),min(),max(),len()on the collected numbers — build every statistic with your own accumulator. (You may uselen()on strings.) - No functions yet — those arrive tomorrow. This is deliberate: on Day 6 you will refactor this same program into functions and feel the difference.
- Named constants instead of magic numbers. PEP 8 naming. Run
blackbefore submitting.
Marking rubric (100 points)
| Criterion | Points |
|---|---|
| Input loop with working sentinel | 15 |
| Input validation that never crashes | 15 |
| All nine statistics correct | 25 |
| Edge cases: no numbers, one number, all negatives, mixed floats and ints | 15 |
| Formatted, aligned, readable report | 10 |
| Naming, constants, structure, PEP 8 | 15 |
| Comments where they earn their place | 5 |
Suggested build order
- Skeleton with a hardcoded list. Get the statistics right first, with no input at all.
- Replace the hardcoded list with an input loop and the sentinel. No validation yet.
- Add validation. Test it with letters, empty input, and symbols.
- Format the report. Line the columns up.
- Break it on purpose: no numbers, one number, all negatives, all identical. Fix what breaks.
- Rename everything for clarity. Pull out constants. Run
black. Read it once as a stranger.
Counting "above the mean" needs the mean, and the mean needs all the numbers. So you cannot do it in the same pass as the sum. You need to keep the numbers - build up a string of them, or wait for Day 8 and lists. Solving that with only Week 1 tools is the interesting part of this assignment.
Full specification and rubric also available as
week1-mini-project.md alongside this module.
>_Python playground
A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.
input(), comma separated →
✓Exercise set
Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.
Day 6 — Functions I: Definition, Parameters, Return
Turning the code you have written into reusable, testable parts.