Day 5 / 25 Debugging, Clean Code & Mini-Project 1 0/0 exercises Exercises ↓

AmouAI Hub/Courses/Programming Fundamentals/Day 5

Week 1 · Python Core Foundations · 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.

Study time
4 hours + project
Reading
Think Python, Appendix A
Focus
Errors, method, craft

By the end of today you can

  1. Tell syntax, runtime and semantic errors apart from the symptom alone
  2. Read a traceback from the bottom up and go straight to the cause
  3. Use labelled print statements and the VS Code debugger effectively
  4. Apply scientific debugging: hypothesis, minimal test, conclusion
  5. Recognise and fix the eight errors you will actually encounter this course
  6. Write code someone else can read: names, constants, structure
  7. Deliver Mini-Project 1

Today's videos

Watch each video, then work the matching sections below. Watching alone will not do it.

5A
How to Debug (120 min)
The three families of error · reading a traceback · the eight errors you will actually hit · print debugging done properly · the VS Code debugger · scientific debugging · rubber-ducking, unedited.
5B
Clean Code + Mini-Project Build (120 min)
Naming, magic numbers, comments that earn their place · PEP 8, black and ruff · decomposition before typing · the full Mini-Project 1 build, thinking out loud, wrong turns included.

1Three families of error

The symptom tells you which family you are in, and that tells you where to look.

FamilyWhen you find outHow it announces itselfDifficulty
SyntaxBefore anything runsPython refuses to start. Points at a line.Easy
RuntimePart-way throughCrashes with a traceback naming the error typeModerate
SemanticMaybe neverRuns perfectly. Answer is wrong.Hard
three_errors.py
# 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.
Semantic errors are the expensive ones

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.

Traceback anatomyinteractive

Click any part of this traceback to find out what it is telling you.

terminal
Traceback (most recent call last):
  File "report.py", line 14, in <module>
    average = total / count
              ~~~~~~^~~~~~~
ZeroDivisionError: division by zero
Click a line above.

The eight errors you will actually meet

ErrorPlain EnglishUsual cause
SyntaxErrorI cannot read thisMissing colon, unbalanced bracket, stray quote. Check the line above the one reported — an unclosed bracket is reported on the next line.
IndentationErrorYour blocks do not line upInconsistent indentation, or a block body missing entirely
NameErrorI have never heard of this nameTypo, or using a variable before assigning it
TypeErrorYou cannot do that to this kind of thing"5" + 5. Almost always a missing int() or str()
ValueErrorRight type, impossible valueint("abc")
IndexErrorThere is nothing at that positionOff-by-one; range(len(s) + 1)
ZeroDivisionErrorYou divided by zeroAn empty collection, or a count that never got incremented
AttributeErrorThat kind of thing has no such method"text".push(), or calling a string method on a number
Name that errorquiz

Read the snippet, pick the error. Immediate feedback.

0 correct

3A method, not a mood

Staring harder does not work. These four techniques do.

1. Print debugging, done properly

print_debug.py
# 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.

KeyActionUse when
F5ContinueRun to the next breakpoint
F10Step overRun this line, do not go inside function calls
F11Step intoGo inside the function on this line
Shift+F11Step outFinish 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

  1. Observe precisely. Not "it does not work" but "it returns 10 when it should return 15."
  2. Hypothesise one specific cause. "The loop starts at 0 instead of 1."
  3. Predict what you would see if that were true. "Then the first value added is 0."
  4. Test the smallest thing that could confirm or kill it. One print inside the loop.
  5. Conclude, then fix. If the hypothesis was wrong, you have still eliminated something.
One change at a time

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.

before.py
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)
41.57142857142857 3 42.857142857142854

Same behaviour, rewritten:

after.py
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}%")
Average: 41.57 Above average: 3 of 7 Percent above: 42.9%
RuleBadGood
Names state intentd, t, cscores, total, above_average_count
No magic numbersx * 100x * PERCENT
Constants in CAPStax = 0.15TAX_RATE = 0.15
One idea per lineprint(a, c, c/len(d)*100)Three labelled prints
Blank lines separate stagesOne dense blockInput / process / output
Format your output41.5714285714285741.57

Comments that earn their place

comments.py
# 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.

terminal
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 mistakes
Let the tool do the arguing

Formatting 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

  1. Repeatedly ask the user for a number. The sentinel done ends input.
  2. Reject anything that is not a valid number, with a clear message, and keep asking.
  3. 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
  4. Handle the empty case: if the user enters no numbers at all, say so and exit cleanly — do not crash with a ZeroDivisionError.
  5. Handle a single number correctly (range is 0, one number, and it is not above its own mean).

Sample run

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 use len() 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 black before submitting.

Marking rubric (100 points)

CriterionPoints
Input loop with working sentinel15
Input validation that never crashes15
All nine statistics correct25
Edge cases: no numbers, one number, all negatives, mixed floats and ints15
Formatted, aligned, readable report10
Naming, constants, structure, PEP 815
Comments where they earn their place5

Suggested build order

  1. Skeleton with a hardcoded list. Get the statistics right first, with no input at all.
  2. Replace the hardcoded list with an input loop and the sentinel. No validation yet.
  3. Add validation. Test it with letters, empty input, and symbols.
  4. Format the report. Line the columns up.
  5. Break it on purpose: no numbers, one number, all negatives, all identical. Fix what breaks.
  6. Rename everything for clarity. Pull out constants. Run black. Read it once as a stranger.
The trap in this project

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.

scratch.pypython not loaded
Values for input(), comma separated →
Output appears here. The first run takes a few seconds while Python loads.

Exercise set

Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.

All Warm-up Core Challenge Reset day

Day 6 — Functions I: Definition, Parameters, Return

Turning the code you have written into reusable, testable parts.

Continue →