CSC 1001 — Lab 7: Functions vs. No Functions — Tracing, Debugging & Testing
Intro
This lab compares two versions of the same program — one without functions, one with — to build your tracing and debugging skills. You'll then redesign a problem using functions, test it yourself, and test a partner's code.
| Section | Mode | What You'll Do |
|---|---|---|
| Understand | Individual | Trace, run, and debug two versions of a trip-cost calculator (with and without functions). |
| Do | Individual, then Partner | Redesign a Week 6 problem using functions, write your own tests, then test a partner's code. |
| Communicate | Partner | Discuss your testing results and decide whether your code needs to change. |
Submit: your Lab 7 Worksheet and your Lab7.ipynb notebook7. |
Understand (Individual)
Below are two versions of the same program: a trip-cost calculator that charges a daily rate for a number of days and applies a 10% discount for trips of 5 days or more. Both versions contain a bug. Work through each version on your own before moving on.
Step 1 — Trace Version A (No Functions)
Read the code below line by line. Before running anything, predict the value of each variable after every relevant line executes.
# Trip Cost Calculator (Version A -- no functions)
days: int = 7
daily_rate: int = 45
total_cost: float = 0
for day in range(1, days):
total_cost = total_cost + daily_rate
if days >= 5:
discount: float = total_cost * 0.10
total_cost = total_cost - discount
print("Number of days:", days)
print("Total cost after discount: $" + str(total_cost))
Record your trace and predicted output on your Lab 7 Worksheet. (The line numbers are shown in the Lab 7 Worksheet.)
Step 2 — Trace Version B (With Functions)
Now trace Version B. Because the logic is split across functions, track which function you are "inside" at each step, and pay close attention to what each function returns (or doesn't return) to the code that called it.
# Trip Cost Calculator (Version B -- with functions)
def calculate_base_cost(days, daily_rate):
total: float = 0
day: int
for day in range(days):
total = total + daily_rate
return total
def apply_discount(total_cost, days):
if days >= 5:
discount: float = total_cost * 0.10
total_cost = total_cost - discount
def main():
days: int = 7
daily_rate: int = 45
total_cost: float = calculate_base_cost(days, daily_rate)
total_cost: float = apply_discount(total_cost, days)
print("Number of days:", days)
print("Total cost after discount: $" + str(total_cost))
main()
Record your trace and predicted output on your worksheet. (The line numbers are shown in the Lab 7 Worksheet.)
Step 3 — Run, Compare, and Debug
Open Jupyter and run both versions.
- Copy each version into its own cell (or its own notebook) and run it.
- Compare the actual output to what you predicted in your trace tables. Where do they differ?
- Find and fix the bug in Version A. Re-run until the output is correct.
- Find and fix the bug in Version B. Re-run until the output is correct.
- Both bugs should be fixable with a small change — you should not need to rewrite the program.
Hint: Version A's bug affects how many times a loop runs. Version B's bug affects whether a value calculated inside a function actually makes it back out.
Step 4 — Reflect (answer in a few sentences each)
- Which version was easier to trace — A or B? Why? What made following the flow of values easier or harder?
- Which version was easier to debug once you found the wrong output — A or B? Why?
- Was the version that was easier to trace also the version that was easier to debug? If not, why might tracing difficulty and debugging difficulty be different things?
- What would you tell a classmate about when functions make code easier to work with, and when they might make a bug harder to spot?
Record your answers on your Lab 7 Worksheet.
Do
Step 1 — Redesign With Functions (Individual)
Choose one of your Week 6 problems and redo it using functions and modular design:
- Ticket Counter Problem: given a list of customer ages, report how many child tickets (age 12 or under) and adult tickets (age 13 or older) are needed.
- Password Attempts Problem: ask the user for a password, allow up to 3 attempts, print "Access granted" on success or "Account locked" after 3 failures.
Neither of your original Week 6 solutions used functions — Ticket Counter was built from a shuffled block of loop/if lines, and Password Attempts was solved with a single for- or while-loop version. This time work individually and rebuild your chosen problem using functions. On your Lab 7 Worksheet, produce each of the following:
- Requirements — rewrite the requirements in your own words, as if explaining them to someone who has never seen the problem.
- Subtasks — break the problem into the smaller pieces of work it requires (these will likely become your functions).
- Pseudocode — outline your solution's logic, organized by subtask/function, before writing any Python.
- Code — implement your solution in
Lab7.ipynbusing at least two functions with parameters and return values.
Aim for functions that each do one clear job, rather than one giant function. For example:
- Ticket Counter Problem: one function that counts child/adult tickets from a list of ages (takes the list, returns two counts), and a separate function that prints/formats the result.
- Password Attempts Problem: one function that checks a single password attempt, and a separate function that manages the attempt loop and decides whether to print "Access granted" or "Account locked."
Step 2 — Design Your Own Tests (Individual)
Before handing your code to anyone else, design three categories of test input and record what you expect your program to do with each on your worksheet:
- Typical input — the most common, ordinary input you'd expect a real user to enter.
- Extreme input — the biggest, smallest, or otherwise most extreme valid input you can think of.
- Weird input — an unusual, unexpected, or edge-case input (zero, a negative number, an empty value, oddly formatted text, etc.).
Run your program with all three of your own test inputs and record the actual results on your worksheet next to your predictions.
Step 3 — Reflection
- Was refactoring the code (changing it to functions) easier or harder than you expected? Why do you think that was?
- Which of the steps: requirements, subtasks, or pseudocode was the most helpful for refactoring? Why?
- Which test category was easiest to brainstorm tests for? Why?
- Which test category was most useful for you? Why?
Step 4 — Partner Testing
Trade code with a partner.
- Without looking at your partner's test cases first, come up with three new test inputs of your own — different from the ones they already tried — using the same three categories (typical, extreme, weird).
- Run your partner's code with your test inputs. Record what happens for each one on your worksheet.
- Try, specifically, to make your partner's program break, crash, or produce an obviously wrong answer. If you succeed, note exactly what input caused it.
Communicate (Partner)
With your partner, discuss the results of your testing, then record brief answers on your Lab 7 Worksheet:
- How did your partner's test inputs compare to yours? Did they test the same three categories the same way, or did their idea of "weird" or "extreme" differ from yours?
- What did your partner discover when testing your code? Did anything break that you didn't expect?
- What did you discover when testing your partner's code?
- Based on what you both found, does anything about your code need to change? If so, what — and if not, why do you think your original tests were already thorough enough?
- What did you learn from testing your partner's code?
- What do you still have questions or confusion about?
Submit: your Lab 7 Worksheet (trace tables, reflections, requirements/subtasks/pseudocode, test-case tables, and Communicate answers) and your Lab7.ipynb notebook (corrected Version A/B code and your function-based solution).