EE1021 Lecture 6 Worksheet — Debugging and Error Handling

Instructions

  • Predict or state expected behavior before running code.
  • Use the supplied function names in the submission template.
  • Read tracebacks from the final line upward.
  • Do not use bare except, except Exception, or silent pass handlers.
  • Keep try blocks focused on the operation you intend to handle.
  • Complete the eight core tasks before the optional extension.

1. Classify the Bug

Classify each problem as syntax error, runtime exception, or logic error. For runtime cases, name the likely exception.

  1. print(values[4]) when values contains two elements.
  2. if voltage > 5.0 print("high").
  3. A program reports an average of 30 for [10, 20, 30].
  4. int("4.5").
  5. registry["S9"] when S9 is absent.
  6. A Celsius conversion uses c * 9 / 5 - 32.

2. Annotate a Traceback

Traceback (most recent call last):
  File "monitor.py", line 11, in <module>
    print(parse_reading(("T2", "error")))
  File "monitor.py", line 6, in parse_reading
    value = float(value_text)
ValueError: could not convert string to float: 'error'

Identify:

  1. exception type;
  2. exception message;
  3. innermost failing line;
  4. function containing the failure;
  5. input that reproduces it;
  6. one reasonable hypothesis.

3. Predict Exception Control Flow

Predict every printed line before running:

print("start")
try:
    value = int("4.5")
    print("converted")
except ValueError:
    print("invalid integer")
print("end")

Explain why one print statement is skipped.

4. Parse One Sensor Reading

Implement:

def parse_reading(record):
    """Return (sensor_id, value) for a (sensor_id, value_text) tuple."""

Contract:

  • ("T1", "23.5") returns ("T1", 23.5);
  • a record that does not contain exactly two items raises ValueError("expected (SENSOR, VALUE)");
  • a nonnumeric value raises ValueError("value must be numeric");
  • the conversion try block must contain only the conversion operation.

5. Required and Optional Sensor IDs

Implement:

def classify_reading(sensor, value, limits):
    """Return normal, alarm, or unknown."""

An unknown sensor must return "unknown". A known sensor is "alarm" only when its value is strictly greater than its limit.

Explain why using an extremely large default limit could incorrectly label unknown sensors as normal.

6. Enforce the Average Contract

Implement:

def average_reading(values):
    """Return the average of a non-empty sequence."""

Raise ValueError("values must not be empty") for empty input. Do not catch ZeroDivisionError. Verify:

  • [20.0] returns 20.0;
  • [20.0, 22.0, 24.0] returns 22.0;
  • [] raises the documented exception.

7. Narrow an Overly Broad Handler

record is a tuple such as ("T2", "error"). Rewrite this code so conversion and lookup failures receive different responses and unrelated defects remain visible:

try:
    sensor, value_text = record
    value = float(value_text)
    limit = limits[sensor]
    print(sensor, value, limit)
except Exception:
    print("bad input")

Use specific exception types and explain the boundary of each try block.

8. Engineering Mini-Problem: Process Records

Implement:

def process_records(records, limits):
    """Return (accepted, errors) after processing all records."""

For every valid record, append (sensor, value, status) to accepted. For malformed structure or a nonnumeric value, append the exception message to errors and continue. Unknown sensors are valid parsed records with status "unknown".

Test with:

records = [("T1", "23.5"), ("T2", "error"), ("T5",), ("T9", "21.0")]
limits = {"T1": 80.0, "T2": 75.0}

The result should contain two accepted records and two error messages.

Optional Extension (after L10, Files): Load a Text File

Load one record per line from sensor_readings.txt, strip newline characters, and pass the list to process_records. Catch file-system exceptions only at the user-interface boundary, where you can report a filename and decide whether recovery is possible.

Submission Checklist

  • All eight core tasks are complete.
  • Every bug is classified before repair.
  • Traceback evidence is annotated.
  • No bare or broad exception handler remains.
  • Conversion is handled in a narrow try block.
  • Unknown sensors are not silently treated as normal.
  • Empty input raises the documented ValueError.
  • Normal, boundary, and invalid cases are tested.