Layer 6 Session 37 Architecture

Reusable Functions and Modules

With a clean package structure in place, we identify genuinely reusable logic scattered across the project and extract it into shared, well-tested functions.

before after formatting.py search.py validators.py

Duplicated logic, pulled into one shared, tested place.

Estimated time: 35โ€“40 minutes  ยท  Pre-quiz โ†’ Concept โ†’ Lab โ†’ Commit โ†’ Post-quiz

1. Learning Objective

By the end of this session you will be able to:

  • Identify duplicated or near-duplicated logic across different parts of the project
  • Extract that logic into a single, well-named, reusable function
  • Write a docstring that documents a function's parameters, return value, and behavior clearly
  • Add tests for the newly extracted function to close a coverage gap
  • Distinguish "reusable" from "premature abstraction" โ€” extracting too early or unnecessarily

2. Pre-Coding Quiz

Answer these before reading the concept explanation or writing any code. It is fine to get these wrong โ€” that is the point. You need 4/5 to proceed to the lab.

Question 1 of 5

Both summary() (formatting a country) and a hypothetical future feature might need to format a large number with comma separators, like 54,000,000. If this formatting appears in two places with near-identical code, what should you do?

This is Session 10's "one place to change" principle applied specifically to duplicated logic โ€” extracting shared behavior into one function prevents the two copies from silently drifting apart when one gets updated and the other is forgotten.
Question 2 of 5

What is the purpose of a docstring like """Format a population count with comma separators.\n\nArgs:\n value: the population as an int\nReturns:\n A string like '54,000,000'\n""" right under a function definition?

A docstring is documentation that lives directly with the code it describes, readable via help(function_name) and by anyone reading the source โ€” especially valuable for a function meant to be reused by other parts of the project.
Question 3 of 5

Why should a newly extracted reusable function get its own tests, even if the code it was extracted FROM was already tested indirectly?

A focused, direct test of the extracted function (following Session 33's return-value testing techniques) is simpler and more precise than relying on it being indirectly exercised through some other, larger test โ€” and it documents the function's contract clearly on its own.
Question 4 of 5

What is "premature abstraction," and why is it a real risk when looking for things to extract?

Not all similar-looking code should be merged โ€” if two pieces of logic happen to look alike now but serve genuinely different purposes and are likely to diverge, forcing them into one shared function adds complexity and coupling without a real benefit. Extraction should follow genuine, ongoing duplication of ONE actual concept.
Question 5 of 5

Where is the most sensible place, given Session 36's new package structure, to put a genuinely reusable formatting function used across multiple parts of the project?

This follows directly from Session 36's package organization principle: a genuinely reusable, standalone piece of logic gets its own focused module, consistent with how models.py, repository.py, and validators.py were each split out by responsibility.

3. The Concept โ€” Extracting Reusable Logic

SUMMARY()f"{pop:,}"REPORT LINEf"{pop:,}"FORMAT_POPULATION()one shared function

Two near-identical formatting expressions collapse into one shared, tested, documented function.

Spotting duplicated logic

As a project grows, similar formatting or calculation logic tends to appear in more than one place. The first step is noticing it โ€” comparing what several pieces of code are actually doing, not just how they look on the surface.

# Duplication scattered across the project:

# models.py
def summary(self):
    return f"{self.name} ({self.region}): pop. {self.population:,}"

# some future report-generation code, formatting the same kind of number again
def population_report_line(name, population):
    return f"{name}: {population:,} people"

Extracting one shared function

Once genuine, ongoing duplication is identified, extract it into one well-named function, and update every call site to use it โ€” exactly the "one place to change" principle from Session 10.

# country_explorer/formatting.py
def format_population(value):
    """Format a population count with comma separators.

    Args:
        value: the population as an int.
    Returns:
        A string like "54,000,000".
    """
    return f"{value:,}"

# models.py
from .formatting import format_population

def summary(self):
    return f"{self.name} ({self.region}): pop. {format_population(self.population)}"

Documenting the extracted function with a docstring

A function meant to be reused across the project deserves clear documentation of what it takes and what it returns, directly alongside the code โ€” readable via help() and by anyone reading the source.

Testing the extracted function directly

A focused test of the standalone function is simpler than relying on it being indirectly exercised through summary() or other larger tests.

# tests/test_formatting.py
from country_explorer.formatting import format_population

def test_format_population_adds_commas():
    assert format_population(54000000) == "54,000,000"

def test_format_population_small_number():
    assert format_population(5) == "5"

When NOT to extract โ€” premature abstraction

If two pieces of code happen to look similar right now, but represent genuinely different concepts likely to change independently, forcing them into a shared function adds coupling without a real benefit. Extraction should follow real, ongoing duplication of one actual idea โ€” not surface-level resemblance.


4. Lab

Lab objective: Extract a shared, documented, tested format_population function, and identify (without necessarily extracting) a case of premature abstraction.

What you will build

A new module country_explorer/formatting.py plus tests/test_formatting.py.

Step-by-step instructions

1

Create formatting.py with format_population and a clear docstring

# country_explorer/formatting.py
def format_population(value):
    """Format a population count with comma separators.

    Args:
        value: the population as an int.
    Returns:
        A string like "54,000,000".
    """
    return f"{value:,}"
2

Update models.py's summary() to use the extracted function

# country_explorer/models.py
from .formatting import format_population

class Country:
    # ... existing code ...
    def summary(self):
        return f"{self.name} ({self.region}): pop. {format_population(self.population)}"
3

Write direct tests for format_population

# tests/test_formatting.py
from country_explorer.formatting import format_population

def test_format_population_adds_commas():
    assert format_population(54000000) == "54,000,000"

def test_format_population_small_number():
    assert format_population(0) == "0"
4

Run the full test suite, confirming summary()'s existing tests still pass unchanged

This proves the extraction was behavior-preserving.

# pytest -v
5

Write a short comment describing a case of near-identical-looking code you deliberately did NOT merge, and why

This exercises the "premature abstraction" judgment call โ€” invent a plausible example if needed, e.g. two functions that both happen to check "value > 0" but for conceptually different reasons.


5. Expected Files Changed

FileActionWhy
country_explorer/formatting.py Created A shared, documented, reusable population formatting function.
country_explorer/models.py Modified summary() now uses the extracted, shared function.
tests/test_formatting.py Created Direct, focused tests for the extracted function.
docs/sessions/session-37/index.html Created This session document.
If you find yourself editing any other file, stop. This session touches exactly 4 files.

6. Commit Checkpoint

Once the lab is complete and you can explain every line, make this exact commit:

git add country_explorer/formatting.py country_explorer/models.py tests/test_formatting.py docs/sessions/session-37/index.html
git commit -m "session-37: extract and test a reusable format_population function"
Do not commit until you can answer out loud: "How did I confirm this extraction was purely a refactor and did not change summary()'s existing observable behavior?"

7. Code Review Checklist

Go through your code line by line and check each item:


8. Post-Coding Quiz

Same 5 questions. Take it again now that you have written and run the code. You need 4/5 to mark this session complete.

Question 1 of 5

Both summary() (formatting a country) and a hypothetical future feature might need to format a large number with comma separators, like 54,000,000. If this formatting appears in two places with near-identical code, what should you do?

This is Session 10's "one place to change" principle applied specifically to duplicated logic โ€” extracting shared behavior into one function prevents the two copies from silently drifting apart when one gets updated and the other is forgotten.
Question 2 of 5

What is the purpose of a docstring like """Format a population count with comma separators.\n\nArgs:\n value: the population as an int\nReturns:\n A string like '54,000,000'\n""" right under a function definition?

A docstring is documentation that lives directly with the code it describes, readable via help(function_name) and by anyone reading the source โ€” especially valuable for a function meant to be reused by other parts of the project.
Question 3 of 5

Why should a newly extracted reusable function get its own tests, even if the code it was extracted FROM was already tested indirectly?

A focused, direct test of the extracted function (following Session 33's return-value testing techniques) is simpler and more precise than relying on it being indirectly exercised through some other, larger test โ€” and it documents the function's contract clearly on its own.
Question 4 of 5

What is "premature abstraction," and why is it a real risk when looking for things to extract?

Not all similar-looking code should be merged โ€” if two pieces of logic happen to look alike now but serve genuinely different purposes and are likely to diverge, forcing them into one shared function adds complexity and coupling without a real benefit. Extraction should follow genuine, ongoing duplication of ONE actual concept.
Question 5 of 5

Where is the most sensible place, given Session 36's new package structure, to put a genuinely reusable formatting function used across multiple parts of the project?

This follows directly from Session 36's package organization principle: a genuinely reusable, standalone piece of logic gets its own focused module, consistent with how models.py, repository.py, and validators.py were each split out by responsibility.

9. Reflection Questions

Think through these after the post-quiz. No right answer โ€” they are for discussion.

  1. Before this session, summary()'s formatting logic was only tested indirectly through summary()'s own tests. Why is a direct test of format_population still valuable now that it exists?
  2. Can you find another piece of logic anywhere in the project (Sessions 1-32) that is duplicated and could benefit from this same extraction treatment?
  3. What is the cost of extracting something too early, before a second real use case actually exists? Contrast that with the cost of NOT extracting genuinely duplicated logic.
  4. How does docstring documentation differ in purpose from the reflection comments you've written throughout this curriculum?

10. What Breaks If This Knowledge Is Missing?

  • Silent formatting drift: Without extracting shared formatting logic, a future change to how numbers should display (e.g. adding a currency symbol) risks being applied in one place and forgotten in another, producing visibly inconsistent output across the application.
  • Utility modules (Session 38): The next session builds on this exact pattern, extracting more substantial, cross-cutting utility logic into its own dedicated module โ€” formatting.py is the first, smallest example of that broader pattern.
  • Recognizing coupling problems (Session 39): Learning to correctly judge WHEN to extract (and when NOT to, per premature abstraction) is a prerequisite for Session 39's harder judgment call: recognizing when a class has taken on too much responsibility.

11. What We Learned

Python concept mastered: Identifying genuine duplication, extracting it into a documented, tested, reusable function, and recognizing premature abstraction as a real risk to avoid.

Unlocks: You can now confidently spot and safely extract reusable logic from a growing codebase, backed by tests that prove the extraction changed nothing about behavior.

Next session: Session 38 โ€” Building Utility Modules. We build a more substantial, standalone utility module โ€” logic that supports the whole application without being tied to any one class.