ooad1-10 Exercise 2: Reverse-Engineering the Bike Shop =========================================================== ex2.py (needs roundtrip.py, uml2py.py and pyreverse; run from the chapter folder): """Exercise 2: reverse-engineer the bike shop code and list what the recovered diagram lost. Run from the chapter folder: py ex2/ex2.py""" import pathlib import sys HERE = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(HERE)) import roundtrip # noqa: E402 print(roundtrip.compare(HERE / "ex1" / "shop.mmd", HERE / "ex1" / "shop_generated.py", HERE / "ex2" / "rt")) print() print("what pyreverse drew for the classes (excerpt):") text = (HERE / "ex2" / "rt" / "classes_rt.mmd").read_text(encoding="utf8") for line in text.splitlines(): if "-->" in line or "--*" in line or "--o" in line or "--|>" in line or "list[" in line or "*" in line: print(" " + line.strip()) Run with: py ex2/ex2.py Output: design: 7 classes, 4 relationships | recovered from the code: 3 in the design, NOT recovered : RepairJob has JobPart in the design, NOT recovered : StandardPricing realises Pricing recovered, NOT in the design : RepairJob has JobStatus recovered : JobPart has Part recovered : RepairJob has Bike what pyreverse drew for the classes (excerpt): job_parts : list[JobPart] total(pricing: Pricing)* int price(hours: float, part_pence: int)* int RepairJob --> Bike : bike RepairJob --> JobStatus : status JobPart --> Part : part WHAT WAS LOST, AND WHOSE LIMIT IT IS RepairJob has JobPart (composition): lost. The code holds a list[JobPart], and pyreverse shows it only as the text of a field, not as an arrow. This is the TOOL's limit: the information (a list of JobPart objects) is in the code. StandardPricing realises Pricing: lost. The code never says so (Pricing is a Protocol and StandardPricing just has a matching method). This is the CODE's limit: nothing in the source names the relationship, so no tool could recover it. It is also a trade-off of Python's structural typing. Multiplicities, and the fact that JobPart belongs to exactly one job: not in the code at all (CODE). GAINED RepairJob has JobStatus: pyreverse reads the attribute 'status: JobStatus' as an association. The design drew it as an attribute; both are valid ways to show the same fact. Also visible in the excerpt: pyreverse marks total() and price() with * (abstract), because their generated bodies only raise NotImplementedError. That is a misreading, so a reverse-engineered diagram of stub code needs reading with care. WHY THIS WORKS AS AN ANSWER --------------------------- Sorting each loss into 'tool' or 'code' matters: a tool limit might be fixed by another tool or by annotations, a code limit cannot be recovered by any tool and must live in a hand-written diagram or a note.