Organizing a Program Across Files · practice
Separate Domain, Interface, and Entry Modules
Chapter 6 argued that a domain
The three layers
main.py entry what this program does when you run it
report.py interface facts turned into text
models.py domain the rules, and the vocabulary they use
scoring.py domain rules with no state of their own
Dependencies point downward. main.py imports from the domain and report report.py uses the scoring rules and may also import from models. Nothing in the domain imports anything above it, ever.
This one-way arrangement is the module dependency direction. It is the whole design: it lets the same models.py serve this
The test that keeps it honest
There is a question you can ask about any module, and it is much sharper than “does this feel like the right file?”:
If the program moved from a terminal to a web page, would this file change?
models.py: no. A question is a question.scoring.py: no. Ninety percent is an A either way.report.py: yes. Lines of text become HTML.main.py: thiswiring would be replaced by the web application’s entry code.
Files that answer “yes” are interface. Files that answer “no” are domain. When a file answers “partly”, it is doing two jobs and wants splitting.
Why must models.py never import from report.py?
Where the line falls in practice
The distinction is not always obvious, and two cases come up constantly.
Formatting a number. percentage(4, 10) returning 0.4 is domain: it is a fact. Rendering it as "40%" is interface, because the choice of a percent sign, decimal places, and whether to use a comma or a period is presentation.
Deciding a pass mark. Domain. A quiz’s pass mark is a rule about quizzes, not about how the result is shown, which is why it lived on Quiz back in Chapter 6.
The useful phrasing: the domain decides what is true, the interface decides how to say it.
A layer is not always a file
Four files for a sixty-line program is close to too many, and this project is right at the edge of where splitting starts to pay.
Do not read this chapter as an instruction to create four modules for every script. Read it as: when a program grows enough that you cannot hold it in your head, these are the seams to cut along, and they are almost always the same three.
Real projects grow more files inside each layer, not more layers. A larger version of this program would have models.py, attempts.py, and question_types.py in the domain, with an entry point for each way the application is started.
The exercise
report_lines moves out of main.py and into a report.py of its own. Lesson 7 then groups the modules in a package, and Lesson 8 wires the finished project together.
Task
Create report.py, the project’s interface layer, which this lesson’s editor owns.
Move report_lines(attempt) into it, unchanged, and add headline(quiz) returning "<title> (<total> points)".
report.py may import from scoring, because turning a score into a grade is a rule it needs. It must not import from main: nothing in the project may depend on the entry point.
It must also print nothing, either at import time or when its
Keep the current copy in main.py for now so Run still works. Submit checks report.py; Lesson 8 removes that copy when it imports the package report.