Unit 06 · lesson

Refactor the System Monitor

Core path: 35 minutes

The Week 5 monitor works. That does not mean its structure is good.

This lesson keeps the visible behavior but changes the architecture. That distinction matters: refactoring changes how the code is organized without intentionally changing what the program does.

Start from the working monitor and save a copy or Git checkpoint before touching it. Future you will appreciate the backup.

Find the responsibilities hiding in the script

The monolithic version is doing several jobs at once:

  • collecting device data;
  • deciding status;
  • counting warnings;
  • calculating totals;
  • displaying one device;
  • displaying the final report.

Those jobs are candidates for functions because each one can be named, reasoned about, and tested separately.

Extract the decision logic first

def determine_status(battery, temperature):
    if battery <= 10:
        return "CRITICAL BATTERY"
    elif temperature >= 80:
        return "OVERHEATING"
    return "NORMAL"

Test it before reconnecting it to the whole application:

print(determine_status(5, 40))
print(determine_status(80, 90))
print(determine_status(80, 40))

Expected:

CRITICAL BATTERY
OVERHEATING
NORMAL

That is one reason decomposition matters. You can inspect one piece without dragging the rest of the program into the test.

Separate input helpers

def get_device_name():
    return input("Name: ")


def get_battery():
    return int(input("Battery: "))


def get_temperature():
    return float(input("Temperature: "))

These functions hide repetitive input/conversion details behind names that explain the job.

Separate report output

def display_device_report(name, status):
    print(f"{name}: {status}")


def display_final_report(device_count, warning_count, average_battery):
    print("=== FINAL REPORT ===")
    print(f"Devices: {device_count}")
    print(f"Warnings: {warning_count}")
    print(f"Average battery: {average_battery:.1f}%")

Notice that these functions mostly display. determine_status() mostly decides.

That separation becomes useful later when we test code automatically.

Rebuild the main flow

The main program should now read more like a coordinator:

ask how many devices
repeat for each device
    get device data
    determine status
    update totals
    display device report
calculate average
display final report

Your exact function boundaries may differ. That is fine if you can explain why each function has one coherent responsibility.

Break return on purpose

Change:

return "NORMAL"

to:

print("NORMAL")

Then inspect the caller.

The text may still appear on screen, which makes this bug interesting. But the function no longer gives the caller the string value it expects. The default return value is None.

That is the difference between:

print → display something now
return → send a value back to the caller

Repair it and verify the report again.

Add one function of your own

Examples:

def battery_category(battery):
    ...

or:

def is_warning(status):
    ...

Before writing it, state the contract in plain language:

function name:
input(s):
returned value:
what it should NOT do:

Then implement it.

Prove the refactor preserved behavior

Run the same small test data through the old and new versions.

Compare:

  • statuses;
  • warning count;
  • average battery;
  • visible report output.

If the architecture improved but the outputs changed unexpectedly, you introduced a behavior change while refactoring. Investigate it.

The goal is not "more functions." The goal is code where the boundaries help another developer understand and test the system.