Module 16 — Summary¶
The essentials¶
- An application is a structure, not a long file. A documented layout, an entry point a stranger finds in ten seconds, and a read order written down. The boundary to split on is a responsibility, not a line count.
- The public/internal header distinction is a rule, not a comment. A public header must compile with no internal include — checked mechanically by compiling it alone, because a comment cannot fail.
- The One Definition Rule decides what may live in a header. The linker's failure on this toolchain
is a single line that names nothing —
collect2.exe: error: ld returned 1 exit status— so the diagnosis isnm -Con the objects:Ttwice is a duplicate,Uwith noTis a missing definition, andUagainst a lowercasetis a name that cannot be linked because its definition is local. inlineis an ODR permission, not an optimisation hint, and an unnamed namespace is what makes a helper private — withnmshowing the lowercasetthat proves it.- A file is input. It can be missing, empty, truncated, hand-edited, or written by an older version, and every one of those has to produce a message rather than a plausible-looking empty catalogue.
- Check stream state, and check it after
close(). The unchecked open that loads nothing is the module's worst failure: not a crash, but a program that looks like a working program with no data. - Validate at the boundary, then trust your own types. After a successful load, the rest of the program needs no re-checking — which is the entire benefit of validating once, at the edge.
- Never allocate from a number read from a file without checking it. A declared count can exceed a
limit, fail to convert (
std::stoulthrowsstd::out_of_range), or disagree with the data — three checks, all of which the example program demonstrates. - Every message carries a line number. "Invalid file" sends the user to you;
line 3: malformed record 'Compilers|Aho'sends them to the line. - There is such a thing as too strict. A blank line left by an editor is not corruption: strictness belongs where the ambiguity is.
- A round trip is the only convincing evidence of persistence, and it has to include the empty catalogue — the case every naive implementation breaks, because a loop assumes a first record and a format writes a marker and then nothing.
- A test asserts; it does not print. The same printing test produced identical output for a correct implementation and one whose cap was wrong by a day. Nothing compares a printed value with anything, so it cannot run unattended and cannot catch a regression.
- A test suite is an assertion count and an exit code. Forty lines of
check(bool, const std::string&)is enough; a framework adds convenience, not capability. - Know that your tests would fail: introduce the bug on purpose and watch. In the example, two of eight assertions failed, and both were boundaries — which is what boundaries are for.
- Refactoring is defined by behaviour not changing, so a test edited to make it pass is evidence the behaviour changed, or that the test was asserting an implementation detail.
- Evaluation is the exit level, and it is written down: context, choice, cost, rejected option, the condition that would reverse the decision — and the cost of the reversal, which is the line that distinguishes a considered decision from a wish.
- Extend the finished system once, and count the files. One or two means the abstractions earned their place; ten means the design promised more than it delivered, and saying so is worth more than the extension.
The diagnostics worth remembering verbatim¶
collect2.exe: error: ld returned 1 exit status
helper.cpp:8:5: error: 'int {anonymous}::scale(int)' defined but not used [-Werror=unused-function]
In file included from double_include.cpp:2:
no_guard.h:2:8: error: redefinition of 'struct Box'
no_guard.h:2:8: note: previous definition of 'struct Box'
U library::internal::scale(int) # wanted, and undefined
0000000000000000 t (anonymous namespace)::scale(int) # provided, and local
0000000000000000 T shelf_capacity() # ... twice, in two objects
The course in one page¶
Sixteen modules, one system, and a short list of habits that outlive all of them.
The mechanisms, in dependency order. Values and invariants (4) → lifetime and ownership (3, 10, 11) → substitution and dispatch (6, 7, 8) → relationships and reuse decisions (9) → generic containers (12) → failure as a design (13) → the library, chosen with measurements (14) → change localisation (15) → structure, persistence and evidence (16).
The habits.
- Predict before you run. Lifecycle counts, dispatch, complexity, the number an expression will print. Being wrong is the point; it is where the model gets corrected.
- Measure rather than assert. Two data sizes, a comparison count, a change-cost trace, a file count. Numbers are the difference between an argument and an opinion.
- Name the cost of the choice you made, and the option you rejected. A decision with no cost has not been made.
- Say what you would reverse, and what reversing it would cost. The strongest evidence of understanding a system is being able to name its weakness precisely.
- Make failures loud. An unhandled error that shows up as an empty catalogue is worse than a crash.
- Prove it with a round trip, a comparison, a failing test — anything that would have noticed if you were wrong.
Further reading¶
- A Tour of C++ (3rd ed.), Bjarne Stroustrup — the language and standard library at the level this course has reached, including the chapters on streams and on program organisation.
- The C++ Standard Library (2nd ed.), Nicolai Josuttis — I/O in the depth Module 16 only sketches, plus the containers and algorithms Module 14 used.
- Effective C++ (3rd ed.), Scott Meyers — Items 1–4 and 31 on interface design, header discipline and the ODR, which is this module's territory.
- Effective Modern C++ (2014), Scott Meyers — the ownership vocabulary that makes Module 11's rules
second nature, and the material on
std::filesystemandstd::optional. - Refactoring (2nd ed.), Martin Fowler — the mechanics of changing a working design safely, which is what the capstone's milestone 5 is.
- Clean Code, Robert C. Martin — naming and structure, read critically: its advice on small functions needs the counterweight of this module's lesson that too-fine splits cost the reader.
- Design Patterns, Gamma et al. — the pattern catalogue behind Module 15, read for the forces rather than the recipes.
- cppreference: filesystem library — the standard library facilities this module's examples use for temporary paths.
- cppreference: input/output library — stream state, open modes
and the formatting manipulators, when you need exactly what
fail,eofandbadmean. - GCC: options that control diagnostics — the options this course uses to turn warnings into evidence, and the ones worth adding next.
Before you move on¶
There is nothing after this module. So the last checklist is the one that decides whether the course is finished, and every box needs evidence rather than memory.
The capstone
- [ ] It builds from the documented command in a clean copy, with no warnings — evidence: the command and its output
- [ ] Persistence works and is documented, with a version marker and an escape rule — evidence: the format section of my notes
- [ ] A round trip preserves every field, including an empty catalogue — evidence: the pasted output
- [ ] Three hand-edited corruptions were rejected with line numbers, leaving the data untouched — evidence: the corruption log
- [ ] The suite covers invariants, boundaries and failure paths, and has never been edited to pass — evidence: the suite, and the test that caught my deliberate bug
- [ ] The UML diagram matches the code, with multiplicities — evidence: the diagram, checked line by line
- [ ] The design report names the decisions, the rejected alternatives, the costs, and one decision I would reverse with the cost of reversing it — evidence: the report
The course
- [ ] I can state an invariant and show where the code enforces it — Module 4
- [ ] I can say who owns every resource, and show it in the types — Modules 3, 10, 11
- [ ] I can add a derived type without editing any existing operation — Modules 7, 8
- [ ] I can choose composition or inheritance and defend it in one sentence — Module 9
- [ ] I can state the complexity of my main operations and justify each container — Module 14
- [ ] I can design a failure path, and test it — Module 13
- [ ] I can measure a design change as a number of files touched — Module 15
- [ ] I can name the decision I would reverse in my own work, and its cost — Modules 15 and 16
Where to go next. There is no Module 17, and the honest route onward is work rather than more material: attempt a capstone project you did not use for this module, take the practice assessments cold, and re-do one lab from an earlier module from scratch to see what you now do differently. Two things from this course are worth carrying into whatever you build next: predict before you run, and name the decision you would reverse. They are the two habits that keep working when there is no course left to follow.
Related: Modules index · Projects and capstone rubric · Labs · Cheat sheets · Practice assessments
Continue¶
Previous: Self-assessment · Back to: Module 16 · Course home: Object-Oriented Programming using C++ · All modules: Modules index