Module 05 Refactoring Guide¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Functional Programming"]
section["Algebraic Data Modelling Validation"]
page["Module 05 Refactoring Guide"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
Read the first diagram as a placement map: this page closes the modelling work before Module 06 changes composition. Read the second as an execution order. Refactoring starts from a characterized observable, not from a preferred type name.
This guide closes Module 05 with one standard: the code should reveal what states exist, who validates them, and where transport representation ends.
The pressure: a dictionary has too many owners¶
Suppose ingestion currently accepts a dictionary and constructs a chunk directly:
def legacy_chunk(raw: AcceptedChunkPayload) -> Chunk:
return make_chunk(text=raw["text"], path=(), metadata=raw["metadata"])
The function is small, but the accepted contract is implicit. An empty string passes. Extra fields are silently irrelevant. Every caller could choose a different interpretation.
The Module 05 boundary makes the ownership split explicit:
ChunkModel owns untrusted input shape and runtime constraints.
to_core_chunk owns the translation. The resulting frozen Chunk remains an
ordinary core value.
Separate preservation from contract tightening¶
Two questions must not be hidden in one assertion:
| Question | Evidence |
|---|---|
| Do previously accepted valid inputs produce the same core value? | Compare the complete legacy and refactored Chunk values |
| Which previously tolerated inputs are now rejected intentionally? | Name each malformed case and assert its boundary error |
| Did cumulative RAG behavior change? | Run the earlier cleaning, chunking, ordering, and failure proofs |
| Did a transport type enter the core? | Inspect imports and the converted value's type |
An equality proof over accepted inputs is a preservation claim. A new rejection is a product decision. Record both; do not describe the second as “equivalent.”
Runnable comparison¶
cd capstone/module-reference-states/module-05
PYTHONPATH=src pytest -q tests/learning/test_module_05_data_modelling.py \
-k boundary_refactor_preserves_accepted_chunk_values
The test compares the complete chunk for one characterized payload. It then shows the deliberate difference: the boundary rejects empty text before core conversion. Add accepted cases before changing the old function; add rejected cases only when the new contract is agreed.
Refactoring sequence¶
- Name the observable. Decide whether callers observe equality, exact errors, serialized bytes, ordering, identity, or another contract.
- Characterize accepted input. Write tests against the existing path before introducing the replacement.
- Locate ownership. Put transport shape in
boundaries/adapters/, cross-field invariants inrag/domain/, and container laws infp/. - Introduce the new path beside the old path. Compare complete public values using the same input.
- Separate intended rejection. Test malformed inputs as contract changes, not equivalence cases.
- Run preservation proofs. Earlier Module 04 behavior must still hold.
- Remove the duplicate owner. Once evidence is sufficient, keep one construction route.
- Verify downstream states. The Module 05 learning proof must still pass through the live capstone.
Choose the narrowest abstraction¶
| Pressure | Prefer | Avoid |
|---|---|---|
| Several fields always exist together | Frozen product type | Parallel dictionaries and positional tuples |
| Exactly one outcome is active | Tagged sum type | Boolean flags plus nullable payloads |
| Checks are independent | Applicative Validation |
Fail-fast chaining that hides later feedback |
| A later check needs an earlier value | Result sequencing |
Pretending the checks are independent |
| External shape needs runtime enforcement | Boundary adapter | Pydantic models throughout the core |
| Values must cross a durable boundary | Explicit versioned codec | repr, pickle, or incidental object layout |
| Aggregation has identity and associativity | Monoid | A convenient but unlawful combine |
| Faster representation is proposed | Equivalence proof plus benchmark | Performance claims based only on intuition |
Stable comparison route¶
- run
make PROGRAM=python-programming/python-functional-programming history-refresh - open
capstone/_history/worktrees/module-05/src/funcpipe_rag/ - compare
fp/,rag/domain/, andboundaries/adapters/ - read
tests/learning/test_module_05_data_modelling.py - run the matching law or boundary test for the changed owner
What to refactor toward¶
- product and sum types that reveal domain meaning without extra commentary
- smart constructors that keep invariants close to the model
- validation that can accumulate multiple problems when that helps the caller
- serialization layers that adapt the model without rewriting it
- optimized representations whose public values match the clear reference path
Review checklist¶
- Can a reviewer enumerate every legal variant without searching for flags?
- Does the narrowest responsible constructor reject each illegal state?
- Are independent and dependent checks represented differently?
- Does every error retain code, message, stage, and path needed by its caller?
- Can an external library type be removed without rewriting domain operations?
- Is serialization versioning explicit and migration bounded?
- Does aggregation state its identity and associative operation?
- Does an optimization preserve identity, order, metadata, and numeric results?
- Do Module 04 behaviors and later Module 05 proofs still pass?
Exit standard¶
Before Module 06, you should be able to:
- justify the chosen value shape from a concrete invalid-state pressure;
- identify the owner of construction, validation, and transport conversion;
- distinguish preservation evidence from an intentional contract change;
- run one focused proof and explain what it does not establish; and
- show that the same Module 05 contract remains valid downstream.
Return to Module 05 Exercises, review your reasoning against Exercise Answers, and then continue to Module 06 only when the comparison route is reproducible.