Grade by Grade
B. Software Development · Software Development: Problem Solving, Design, Testing and the Life Cycle

Modular Design: Reusable Code and Program Skeletons

B2.3 B2.1About 70 minutes
Learning goals
  • I can explain the principle of modularity and why it makes programs easier to write, test, share and change.
  • I can turn repeated code into a reusable function and put functions in a module file to import into other programs.
  • I can design a program from a skeleton (template) by filling in stub functions one at a time and testing each one.
  • I can use docstrings, help() and documentation snippets to understand and reuse existing code.

Words to know

modularity
Designing a program as separate, self-contained parts (modules), each with one clear job and a clear way to communicate (parameters and return values).
reusable code
Code written so that it can be used again in other places or programs without being rewritten, such as a well-designed function or class.
module (Python file)
A .py file of functions (and variables) that another program can load with import.
skeleton (template)
A starting program that has the overall structure (function headers, main, comments) but leaves the details to be filled in.
stub
A placeholder function that has the right name and parameters but returns a simple fixed value, so the rest of the program can run and be tested.
docstring
A string in triple quotes right under a def line that explains what the function does. help() displays it.
DRY
"Don't Repeat Yourself": if you copy and paste code, it probably belongs in a function.

Building with blocks

LEGO sets are built from small bricks with standard connectors. A brick from a castle set fits a spaceship. Software can work the same way. The principle of modularity says: build a program from modules (functions, classes, files) that each do one job and connect through clear interfaces (the parameters going in and the value coming out).

In Arrays and Subprograms you learned how to write functions with parameters and return values. This lesson is about designing with them: deciding what the modules should be, making them reusable, and building programs from a skeleton.

Why modular design?

  • Easier to understand: each function is short and has a name that says what it does.
  • Easier to test: you can test one function by itself with known inputs (more on this in the testing lesson).
  • Reuse: write circle_area() once, use it in every program that needs it.
  • Teamwork: different people can build different modules at the same time.
  • Easier to change: if the tax rate changes, you fix it in one place.

Two signs of a good module:

  • High cohesion: it does one thing. calc_average() should not also print a report card.
  • Low coupling: it gets what it needs through parameters and gives back its result with return, instead of reading and changing global variables. Then it works anywhere you call it.
Worked example

Example 1: Refactoring repeated code

A student wrote this to find the area of three circular skating rinks:

print(3.14159 * 12 * 12) print(3.14159 * 15 * 15) print(3.14159 * 9 * 9)

Then she needed the area of a ring (a circular rink with a round garden in the middle). Redesign the code using modularity.

  1. Spot the repetition: "π × r × r" appears three times with different radii. That pattern becomes a function with a parameter r.
  2. Use math.pi instead of typing 3.14159: it is more accurate and the same everywhere.
  3. A ring's area is the big circle minus the small circle. Reuse the first function to build the second: ring_area(outer, inner) returns circle_area(outer) - circle_area(inner).
  4. Now a change (for example, rounding) happens in one place, and ring_area was correct as soon as circle_area was correct.

circle_area(r) and ring_area(outer, inner). A ring with outer radius 5 m and inner radius 3 m has area π(25 − 9) ≈ 50.27 m².

Modules you can import

To reuse functions across different programs, save them in their own file, such as geometry.py. Any program in the same folder can then write import geometry and call geometry.circle_area(15). This is exactly how import math and import random work: someone else wrote and tested those modules, and you reuse them.

geometry.pycircle_area(r)rectangle_area(l, w)triangle_area(b, h)pizza.pypizza sizerink.pyice surfacegarden.pygarden bedimport geometryWrite and test once. Reuse everywhere.
Reuse: one tested module, geometry.py, is imported by three different programs. A fix or improvement in geometry.py helps all three.
import math
def circle_area(r):
"""Return the area of a circle with radius r."""
return math.pi * r * r
def rectangle_area(length, width):
"""Return the area of a rectangle."""
return length * width

The file geometry.py. It only defines functions, so running it by itself prints nothing. The triple-quoted lines are docstrings.

import geometry
print("30 cm pizza:", round(geometry.circle_area(15)), "cm2")
print("Garden bed:", geometry.rectangle_area(6, 2.5), "m2")

A second file in the same folder. Output: 30 cm pizza: 707 cm2 and Garden bed: 15.0 m2. (A 30 cm pizza has radius 15 cm; π × 15 × 15 ≈ 706.9.)

Designing from a skeleton

Teachers, textbooks and companies often hand you a skeleton (also called a template). It contains main(), the function headers with their parameters, docstrings that say what each function must do, and # TODO comments. The functions are stubs: they return a simple placeholder so the whole program already runs.

Working from a skeleton:

  1. Read the whole skeleton first. Understand main(): it is the program's plan, like a structure chart in code.
  2. Run it. A good skeleton runs even before you change anything.
  3. Fill in one stub at a time. Test it before moving on.
  4. Don't change the headers (names and parameters) unless the design is changed. Other parts depend on them.
# Skeleton: Winter Carnival ticket calculator
def get_count(prompt):
"""Ask with prompt; return a whole number 0 or more."""
# TODO: ask again until the input is valid
return 1 # stub: a placeholder value
def ticket_cost(adults, children):
"""Return the cost: adults $12, children $6."""
# TODO
return 0 # stub
def show_receipt(adults, children, cost):
"""Print a one-line receipt."""
print("TODO receipt")
def main():
adults = get_count("Adults: ")
children = get_count("Children: ")
cost = ticket_cost(adults, children)
show_receipt(adults, children, cost)
main()

A teacher-supplied skeleton. It runs right away and prints TODO receipt, because every stub returns a placeholder.

1. Skeletonstubstubstubmain() calls3 stubsruns already2. Fill onedonestubstubget_count()written andtested3. Fill nextdonedonestubticket_cost()written andtested4. Completedonedonedoneall moduleswork together
Working from a skeleton. The program runs at every stage; stubs (dashed) are replaced one at a time by finished, tested functions.
Worked example

Example 2: Filling in the skeleton

Complete the Winter Carnival skeleton: get_count must keep asking until the user types a whole number; tickets cost $12 per adult and $6 per child.

  1. get_count: read the text; text.isdigit() is True only for strings made entirely of digits (like "12"), so while not text.isdigit(): repeats the question for bad input such as "two", "-3" or an empty answer. Then return int(text).
  2. Test it alone: typing "two" then "3" should return 3.
  3. ticket_cost: return adults * 12 + children * 6. Test: (2, 3) → 24 + 18 = 42; (0, 0) → 0.
  4. show_receipt: print one formatted line. Now main() works without any changes, because the headers stayed the same.

The completed program is below. For 2 adults and 3 children (after one bad entry) it prints 2 adults, 3 children: $42.00.

def get_count(prompt):
"""Ask with prompt; return a whole number 0 or more."""
text = input(prompt)
while not text.isdigit():
print("Please enter a whole number like 0, 1 or 2.")
text = input(prompt)
return int(text)
def ticket_cost(adults, children):
"""Return the cost: adults $12, children $6."""
return adults * 12 + children * 6
def show_receipt(adults, children, cost):
"""Print a one-line receipt."""
print(f"{adults} adults, {children} children: ${cost:.2f}")
def main():
adults = get_count("Adults: ")
children = get_count("Children: ")
cost = ticket_cost(adults, children)
show_receipt(adults, children, cost)
main()

Sample run: the user types 2, then two (rejected), then 3. Output: the message Please enter a whole number like 0, 1 or 2., a second Children: prompt, and finally 2 adults, 3 children: $42.00.

Reusing code from documentation

The Help facility is another source of templates. In Python's interactive shell, help(len) or help(str.isdigit) shows the docstring of a built-in function, and help(ticket_cost) shows your docstring. The official Python documentation (docs.python.org) includes short code snippets for most features. When you reuse a snippet:

  • read it until you understand every line;
  • change the names to fit your program;
  • test it in your program;
  • if you copy more than a line or two from a book or website, note the source in a comment.
Just for fun

Programmers have a saying: "Weeks of coding can save you hours of planning." It is a joke, and it is exactly backwards: an hour spent on a skeleton saves days of rewriting.

Hands-on activity

Build your own geometry toolkit

You need: a computer with Python (IDLE or a free browser-based Python that lets you create more than one file, or just one file if not).

  1. Create geometry.py with three functions, each with a docstring: circle_area(r), rectangle_area(length, width) and triangle_area(base, height).
  2. Create rooms.py that imports it and finds the floor area of two rooms in your home (measure them with a tape measure, in metres) and of a round or triangular object (a plate, a pizza, a slice of pie).
  3. Create skeleton.py for your own program idea (for example, a party-supplies calculator) with main() and three stubs. Run it to prove it works, then fill in one stub.
  4. In the shell, type import geometry and then help(geometry.circle_area). Your docstring appears!

What to look for: functions that use parameters and return (not input() and print() inside them), docstrings on every function, and a skeleton that runs before it is finished. If your browser Python only allows one file, put the functions at the top of one file instead.

Your turn

1

State the principle of modularity in one sentence.

2

Give three benefits of modular design.

3

What is a stub, and why is it useful?

4

Using the completed ticket program, what is the cost for 4 adults and 1 child?

5

What does '2.5'.isdigit() return? What would get_count do if the user typed 2.5?

6

A program in the same folder as geometry.py wants the area of a 4 m by 3.5 m room. Write the two lines it needs.

7

Why should you avoid changing a function header in a skeleton?

More practice: warm-up

1

What does DRY stand for?

2

Where does a docstring go?

3

What Python statement loads functions from a file named tools.py?

4

True or false: a good skeleton cannot run until all the TODOs are finished.

5

What does help(len) do in the Python shell?

More practice: on your own

1

This function has low cohesion: it reads two numbers with input(), adds them, prints the sum and returns nothing. Redesign it.

2

Explain the difference between high cohesion and low coupling.

3

Write a stub for a function shipping_cost(weight_kg) that will eventually look up a price table. Why might a stub return 10 rather than 0?

4

A student copied a 25-line sorting snippet from a website into her assignment. What should she do before submitting?

5

Write a reusable function average(values) that returns the mean of a list. What does average([3, 4, 8]) return?

Challenge

1

A program has TAX = 0.13 at the top and five different functions each multiply by TAX. Is this good or bad for modularity? Suggest a better design.

2

Design a skeleton (headers, docstrings and main only) for a program that reads a list of daily snowfall amounts, finds the total and the snowiest day, and prints a report.

3

Explain why testing is easier with modular code. Use ticket_cost as an example.

Check your understanding

Quiz

1.

Which best describes the principle of modularity?

2.

What is a stub?

3.

A function that receives data through parameters and returns a result, without using global variables, shows:

4.

With the ticket program, what is ticket_cost(3, 2)?

5.

How does a program use functions saved in geometry.py?

6.

What does a docstring do?

For parents and teachers

Big idea: B2.3 asks students to apply the principle of modularity to design reusable code (subprograms and classes); B2.1 asks them to design programs from a supplied template or skeleton, including code snippets from a help facility.

How to help: Ask "If you had to use this in another program, could you?" and "Does each function do just one thing?" When your teen gets a starter file from a teacher, encourage them to run it first and fill in one function at a time.

Common difficulties:

  • Functions that use input() and print() inside instead of parameters and return, which makes them hard to reuse and test.
  • Changing a skeleton's function names or parameters, which breaks main().
  • Writing the whole program before running anything. Run after every stub is filled.
  • Copying code from the internet without understanding or crediting it.