*;
```
]
**Rust** โ private method, index return:
.font-sm[
```rust
fn _create_node(&mut self, node_type: NodeType, pt: Point) -> usize {
let id = match node_type {
NodeType::Steiner => { let s = format!("steiner_{}", self.next_steiner_id);
self.next_steiner_id += 1; s }
NodeType::Terminal => { /* ... */ }
NodeType::Source => "source".to_string(),
};
self.add_node(RoutingNode::new(&id, node_type, pt))
}
```
]
]
.pull-right[
**Python** โ method returning the node object:
.font-sm[
```python
def _create_node(self, node_type, pt):
if node_type == NodeType.Steiner:
node_id = f"steiner_{self.next_steiner_id}"
self.next_steiner_id += 1
elif node_type == NodeType.Terminal:
node_id = f"terminal_{self.next_terminal_id}"
self.next_terminal_id += 1
else: # Source
node_id = "source"
node = RoutingNode(node_id, node_type, pt)
self.nodes[node_id] = node
return node
```
]
**One factory, zero duplication, three languages.** ๐
]
---
### ๐ญ Subtle Behaviors We Had to Preserve
Python taught us the factory isn't a *mechanical* extraction:
- โ ๏ธ **`insert_terminal_node`** โ the node must NOT be registered before
`_find_nearest_node` runs, or it becomes its own nearest neighbor (distance 0)
โ reordered: *resolve parent first, then create*
- โ ๏ธ **`insert_node_on_branch`** โ a test asserted it raises `ValueError` for
`NodeType.Source` โ kept the guard in front of the factory
**Lesson:** patterns remove duplication, not contracts. Tests are the safety net. ๐งช
---
class: nord-light, middle, center
## ๐งฐ Part 2 โ Builder Pattern (physdes)
---
### ๐งฐ The Smell: Telescoping Constructor
```python
# Python โ 8 positional parameters with defaults
class ClockTreeVisualizer:
def __init__(self, margin=50, node_radius=8, wire_width=2,
sink_color="#4CAF50", internal_color="#2196F3",
root_color="#F44336", wire_color="#666666",
text_color="#333333"):
...
```
- ๐คฏ Call sites like `ClockTreeVisualizer(60, 10, 3, "#2E7D32", ...)` โ what is `60`?
- โ Adding a 9th option touches every call site
- ๐ Hard to read, hard to extend, hard to review
---
### ๐งฐ The Fix: Fluent Builder
.font-sm[
```python
# Python
viz = (ClockTreeVisualizer.builder()
.margin(60)
.node_radius(10)
.wire_width(3)
.sink_color("#2E7D32")
.build())
```
```cpp
// C++ โ same intent
auto viz = ClockTreeVisualizer::Builder()
.margin(60)
.node_radius(10)
.wire_width(3)
.build();
```
```rust
// Rust โ same intent
let viz = ClockTreeVisualizer::builder()
.margin(60)
.node_radius(10)
.wire_width(3)
.build();
```
]
Self-documenting: each option is *named* at the call site. โจ
---
### ๐งฐ Language Idioms for the Builder
.pull-left[
**C++** โ private ctor, nested Builder:
```cpp
class ClockTreeVisualizer {
public:
class Builder { /* setters + build() */ };
private:
friend class Builder;
ClockTreeVisualizer(int margin, ...); // private
};
```
Forces every customization through the Builder. ๐
**Python** โ builder is a *classmethod*:
```python
@classmethod
def builder(cls):
return ClockTreeVisualizerBuilder()
```
]
.pull-right[
**Rust** โ the *honest* note ๐ฆ:
Rust already had the idiomatic answer:
```rust
let viz = ClockTreeVisualizer {
margin: 40,
node_radius: 6,
..Default::default() // struct-update syntax
};
```
We added a real `ClockTreeVisualizerBuilder` anyway for **parity with the siblings** โ but Rust's native ergonomics make it optional.
**Lesson:** sometimes the pattern is *already the language*. ๐ฏ
]
---
### ๐งฐ Tests Guard the Builder
Every language got builder tests:
```
โ
Builder Configuration โ custom values reach the output
โ
Builder Defaults Match โ builder().build() == default ctor
โ
Builder Is Fluent โ setters return self/builder
```
Plus a **doctest** in the Python class docstring (runs under pytest):
```
>>> viz = ClockTreeVisualizerBuilder().margin(10).node_radius(5).build()
>>> viz.margin
10
```
**Patterns without tests are just opinions.** ๐งช
---
class: nord-light, middle, center
## ๐๏ธ Part 3 โ Template Method (physdes)
---
### ๐๏ธ The Smell: Three Near-Identical Decomposers
`rpolygon_cut` splits a rectilinear polygon into convex pieces โ
**three variants** (convex / explicit / implicit) shared ~80% of their code:
```
base cases โโโบ find concave point โโโบ min-dist scan โโโบ re-wire โโโบ recurse โโโบ merge
โ โ โ โ โ
โ โ differs โ differs โ differs โ differs
โ (3 variants) (2 variants) (2 variants) (2 variants)
โ
โโโ convex had a 3-node base case; explicit/implicit didn't
```
Only the *hooks* differed โ the **skeleton was identical**. ๐ธ๏ธ
---
### ๐๏ธ The Fix: Extract the Skeleton, Inject the Hooks
.pull-left[
**Before โ 3ร ~80-line recursive functions**:
.font-sm[
```
rpolygon_cut_convex_recur 295 duplicated lines
rpolygon_cut_explicit_recur
rpolygon_cut_implicit_recur
```
]
]
.pull-right[
**After โ one skeleton + policy hooks**:
.font-sm[
```
rpolygon_cut_recur_impl โ Template Method
โโโ ConvexCutPolicy โ find_concave_point + triangle base case
โโโ ExplicitCutPolicy โ find_concave_point only
โโโ ImplicitCutPolicy โ different scan + rewiring + target
```
]
]
**Net โ82 lines** in the C++ file. ๐
**Python version** โ same idea, function-parameter style:
```python
def rpolygon_cut_recur(v1, lst, is_anticlockwise, rdll,
find_concave_point, has_triangle_base_case):
# ... shared skeleton ...
vcurr = find_concave_point(v1, ...) # โ hook
def rpolygon_cut_convex_recur(...):
return rpolygon_cut_recur(..., _find_convex_concave_point, True)
def rpolygon_cut_explicit_recur(...):
return rpolygon_cut_recur(..., _find_explicit_concave_point, False)
```
---
### ๐๏ธ The Template Method at a Glance
.mermaid[
graph TD
SKEL["๐๏ธ rpolygon_cut_recur_impl\nShared Skeleton\nbase cases ยท fallback ยท insert ยท recurse"]
SKEL --> H1["๐ฃ Hook: find_concave_point"]
SKEL --> H2["๐ฃ Hook: find_min_dist_point"]
SKEL --> H3["๐ฃ Hook: insert_cut (rewiring)"]
SKEL --> H4["๐ฃ Hook: first_target (recursion)"]
H1 --> C1["๐ข ConvexCutPolicy\ndirection-reversal test"]
H1 --> C2["๐ต ExplicitCutPolicy\narea-sign test"]
H1 --> C3["๐ ImplicitCutPolicy\ncorner-point test"]
H2 --> C1
H2 --> C3
H3 --> C1
H3 --> C3
H4 --> C3
style SKEL fill:#e8eaf6,stroke:#283593,color:#2e3440,stroke-width:3px
style C1 fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style C2 fill:#e3f2fd,stroke:#1565c0,color:#2e3440,stroke-width:3px
style C3 fill:#ffe0b2,stroke:#e65100,color:#2e3440,stroke-width:3px
]
**Rust note:** its `rpolygon_cut` was a *placeholder stub* โ all three "variants"
were byte-identical and returned the whole polygon. We deduplicated only the
wrapper boilerplate, and left the stubs as future extension points. ๐ฆ
---
### ๐๏ธ Behavioral Equivalence โ the Hard Part
The C++ refactor looked trivial but had **four hidden contracts**:
| Difference | How preserved |
|------------|---------------|
| ๐บ Convex's 3-node triangle base case | `has_triangle_base_case = true` flag |
| ๐ฏ Implicit recurses on `v_min`, not `vcurr` | `first_target` hook |
| ๐ Implicit scans from a corner point | `find_min_dist_point` hook |
| ๐ Implicit's different list rewiring | `insert_cut` hook |
**Result:** all **701 assertions** still passed with zero behavioral drift. โ
---
class: nord-light, middle, center
## ๐ Part 4 โ Cross-Language Parity (physdes)
---
### ๐ Same Pattern, Three Codebases
.mermaid[
graph TD
subgraph Factory["๐ญ Factory Method"]
F1["C++ _create_node\n5 sites โ 1"]
F2["Rust _create_node\n5 sites โ 1"]
F3["Py _create_node\n5 sites โ 1"]
end
subgraph Builder["๐งฐ Builder"]
B1["C++ nested Builder\nprivate ctor"]
B2["Rust Builder struct\n+ Default"]
B3["Py Builder class\n+ classmethod"]
end
subgraph Template["๐๏ธ Template Method"]
T1["C++ policy hooks\nโ82 lines"]
T2["Rust shared wrapper\n(stub algorithms)"]
T3["Py finder-hook skeleton\n2 variants โ 1"]
end
style Factory fill:#e3f2fd,stroke:#1565c0,color:#2e3440,stroke-width:3px
style Builder fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style Template fill:#ffe0b2,stroke:#e65100,color:#2e3440,stroke-width:3px
]
All pushed to GitHub in **9 commits** (3 per repo) on the `dev` branches. ๐
---
### ๐ Verification Across All Three
| Project | Test count | Toolchain gates |
|---------|-----------|-----------------|
| ๐งฑ **physdes-cpp** | 132 cases ยท 701 assertions | `-W4 -WX`, clang-format |
| ๐ฆ **physdes-rs** | 299 lib + 11 integ + 66 doc | `cargo fmt/clippy/doc -- -D warnings` |
| ๐ **physdes-py** | 600 passed ยท 1 skipped | black ยท isort ยท flake8 |
Every gate **green** before any commit. โ
---
class: nord-light, middle, center
## ๐งฎ Part 5 โ Case Study 2: The LDS Family
---
### ๐งฎ The LDS Polyglot Family
Four siblings implementing the same low-discrepancy sequence generators
(Van der Corput ยท Halton ยท Circle ยท Sphere):
.mermaid[
graph TD
CPP["๐งฑ lds-cpp\nC++20 templates ยท constexpr"] --> CORE["๐ข Shared Core\nvdc_digit_sum"]
CC["โ๏ธ lds-gen-cpp\nC++20 runtime ยท std::atomic"] --> CORE
RS["๐ฆ lds-rs\nRust crate"] --> CORE
PY["๐ lds-gen\nPython package"] --> CORE
CORE --> PROTO["๐ฏ Uniform Protocol\npop ยท peek ยท reseed ยท get_index"]
style CPP fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style CC fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style RS fill:#e3f2fd,stroke:#1565c0,color:#2e3440,stroke-width:3px
style PY fill:#fff9c4,stroke:#f57f17,color:#2e3440,stroke-width:3px
style CORE fill:#ffe0b2,stroke:#e65100,color:#2e3440,stroke-width:3px
style PROTO fill:#e8eaf6,stroke:#283593,color:#2e3440,stroke-width:3px
]
**The mission:** refactor *all four* with the same patterns, idiomatically โ
and keep every public API byte-for-byte compatible.
---
### ๐ The Smells We Found (LDS)
| Smell | Where | Fix |
|-------|-------|-----|
| ๐๏ธ **~250 lines of identical protocol boilerplate** (pop/peek/skip/reseed/begin/end) | 6 template classes in `lds-cpp` | **Template Method** (CRTP base) |
| ๐ข **Digit-reversal loop inlined in `pop()` *and* `peek()`** | every `VdCorput` (all 4 languages) | **Shared digit core** |
| ๐ **Same `pop`/`reseed`/iterators/batch duplicated** | 7 classes in Python `lds.py` | **Generic base class** |
| ๐ญ **11-case factory switch** | `HaltonN::create_vdc` (lds-cpp) | **Factory registry table** |
---
### ๐งฎ Template Method โ State / Computation Separation
Every generator fused two concerns into `pop()`:
- ๐งฎ **the computation** โ "what is the value at index *n*?"
- ๐ข **the state** โ "how far have I advanced?"
```
pop() โ count += 1; compute(count)
peek() โ compute(count + 1) โ same loop, duplicated
after: pop() = value_at(++count)
peek() = value_at(count + 1) โ consistent BY CONSTRUCTION
```
Because `value_at` is pure, the components inside composites become
stateless calculators. โ
---
### ๐งฎ C++ โ Template Method via CRTP
.font-sm[
```cpp
// lds-gen-cpp โ the protocol lives once in the base
template
class GeneratorBase {
public:
auto pop() -> Value {
auto n = count_.fetch_add(1, std::memory_order_relaxed) + 1;
return derived().value_at(n); // โ hook
}
auto peek() -> Value {
return derived().value_at(count_.load(std::memory_order_relaxed) + 1);
}
// skip / reseed / get_index โฆ
protected:
std::atomic count_{0}; // state lives here
private:
auto derived() -> Derived& { return static_cast(*this); }
};
class Halton : public GeneratorIterable> {
VdCorput vdc0, vdc1;
public:
auto value_at(unsigned long n) const -> std::array {
return {vdc0.value_at(n), vdc1.value_at(n)}; // โ the ONLY thing left
}
};
```
]
`lds-cpp` uses a plain `unsigned long` (constexpr priority); `lds-gen-cpp` uses
`std::atomic` (thread-safe priority). Same skeleton. ๐งฑ
---
### ๐ฆ Rust โ a Trait with Default Methods
.font-sm[
```rust
// lds-rs โ the Generator trait (Template Method)
pub trait Generator {
type Value;
fn counter(&self) -> &AtomicU64;
fn value_at(&self, n: u64) -> Self::Value; // โ hook
fn pop(&mut self) -> Self::Value {
let n = self.counter().fetch_add(1, Ordering::Relaxed) + 1;
self.value_at(n)
}
fn peek(&self) -> Self::Value {
self.value_at(self.counter().load(Ordering::Relaxed) + 1)
}
// advance / get_index / reseed defaults โฆ
}
```
]
A `macro_rules!` keeps the protocol methods **inherent**, so the public API
is unchanged (no trait import needed to call `pop()`):
.font-sm[
```rust
impl_generator_protocol!(Halton, [f64; 2]); // pop/peek/advance/โฆ + Iterator
```
]
> โ ๏ธ Rust trait methods are *not* automatically inherent โ that is why the
> macro exists. Public API preservation drives the design. ๐ฆ
---
### ๐ Python โ a Generic Base Class
```python
# lds-gen โ GeneratorBase[T] (Template Method)
class GeneratorBase(Generic[T]):
def pop(self) -> T:
with self._count_lock:
self._count += 1
return self.value_at(self._count) # โ hook
def value_at(self, n: int) -> T:
raise NotImplementedError
# __iter__ / __next__ / pop_batch / iter_batch โฆ
class Halton(GeneratorBase[List[float]]):
def value_at(self, n: int) -> List[float]:
return [self.vdc0.value_at(n), self.vdc1.value_at(n)]
```
- `T` is the value type โ `float`, `List[float]`, `int`, โฆ
- All 7 classes shed ~25 lines of identical boilerplate each ๐
---
### ๐งฎ Template Method at a Glance
.mermaid[
graph TD
BASE["๐งฉ GeneratorBase ยท Generator trait\npop() โ value_at(++count)\npeek() โ value_at(count + 1)\nskip ยท reseed ยท get_index"]
HOOK["๐ฃ Hook: value_at(n)\npure ยท no state change"]
V["VdCorput\nvdc(n, base)"]
H["Halton\n[vdc0(n), vdc1(n)]"]
C["Circle\ncos/sin mapping"]
S["Sphere3Hopf\nHopf mapping"]
BASE --> HOOK
HOOK --> V
HOOK --> H
HOOK --> C
HOOK --> S
style BASE fill:#e8eaf6,stroke:#283593,color:#2e3440,stroke-width:3px
style HOOK fill:#ffe0b2,stroke:#e65100,color:#2e3440,stroke-width:3px
style V fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style H fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style C fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style S fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
]
**One skeleton, many generators.** ๐ฏ
---
### ๐ข Shared Digit Core โ One Loop, Four Languages
The radical inverse is the heart of every generator:
$$
\phi_b(n) = \sum_{k=0}^{m} d_k \, b^{-k-1}
$$
Every `VdCorput::pop()` *and* `peek()` inlined the same base-`b` digit loop.
Extract it once:
.font-sm[
```cpp
template
constexpr auto vdc_digit_sum(unsigned long n, unsigned long base,
const Table& weights) -> T {
T reslt{};
std::size_t idx = 0;
while (n != 0) {
const auto remainder = n % base;
n /= base;
reslt += static_cast(remainder) * weights[idx++];
}
return reslt;
}
```
]
- ๐ฆ **Rust** โ generic over `f64`/`u64` via a small `DigitValue` trait
(`f64` has no `From` โ lossy)
- ๐ **Python** โ the existing `vdc()` (float) plus a brand-new public `vdc_i()` (int)
Three implementations of the same loop became **one**. ๐ โ 1๏ธโฃ
---
### ๐ The Formal Contract โ Contract as Code
Each language now *enforces* the protocol at compile time:
```cpp
// C++20 โ the concept (lds-cpp / lds-gen-cpp)
template
concept SequenceGenerator = requires(G g, unsigned long n) {
{ g.pop() } -> std::convertible_to;
{ g.peek() } -> std::convertible_to;
g.skip(n);
g.reseed(n);
{ g.get_index() } -> std::convertible_to;
};
static_assert(SequenceGenerator); // compile-time check
```
- ๐ฆ **Rust** โ the `Generator` trait *is* the contract
- ๐ **Python** โ `GeneratorBase[Generic[T]]` + type hints
- โ๏ธ **lds-gen-cpp** โ the same concept also holds for the runtime
`VdCorputBase` / `HaltonN` โ both families interchangeable
---
### ๐ LDS Cross-Language Parity
.mermaid[
graph TD
subgraph CPP1["๐งฑ lds-cpp"]
A1["GeneratorBase CRTP\nconstexpr ยท concept ยท mixin"]
end
subgraph CPP2["โ๏ธ lds-gen-cpp"]
A2["GeneratorBase CRTP\nstd::atomic counter"]
end
subgraph RS["๐ฆ lds-rs"]
A3["Generator trait\ndefault methods + macro"]
end
subgraph PY["๐ lds-gen"]
A4["GeneratorBase Generic[T]\nlock-guarded _count"]
end
A1 --> CON["๐ฏ Same Template Method\none value_at(n) hook"]
A2 --> CON
A3 --> CON
A4 --> CON
style CPP1 fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style CPP2 fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style RS fill:#e3f2fd,stroke:#1565c0,color:#2e3440,stroke-width:3px
style PY fill:#fff9c4,stroke:#f57f17,color:#2e3440,stroke-width:3px
style CON fill:#ffe0b2,stroke:#e65100,color:#2e3440,stroke-width:3px
]
**Public API is unchanged** in every repo โ only *additive* surface
(`value_at`, the trait/base, `vdc_i`). ๐
---
### ๐ก๏ธ Thread Safety & Where the Counter Lives
The same pattern is *language-shaped*:
.mermaid[
graph TD
subgraph CPP["๐งฑ C++ โ counter lives in the base"]
B1["GeneratorBase\ncount_ member ยท inherited by all"]
end
subgraph RS["๐ฆ Rust โ traits can't hold data"]
R1["count: AtomicU64\nfield per struct"]
R2["counter() accessor hook"]
end
B1 --> SAME["1 atomic per generator\nsame semantics"]
R1 --> SAME
style CPP fill:#c8e6c9,stroke:#2e7d32,color:#2e3440,stroke-width:3px
style RS fill:#e3f2fd,stroke:#1565c0,color:#2e3440,stroke-width:3px
style SAME fill:#ffe0b2,stroke:#e65100,color:#2e3440,stroke-width:3px
]
Centralizing the counter made composite `pop()` **atomic as a point**:
`Halton.pop() = value_at(++count)` โ ONE atomic claim, always a valid point. โ
- ๐ **lds-gen** โ every generator now lock-guarded โ **all thread-safe**
- ๐งฑ **lds-cpp** โ plain `unsigned long` on purpose: `constexpr` beats
thread-safety there (a documented design trade-off)
---
### ๐ก๏ธ Do the LDS Dependents Break?
We audited every consumer before touching a header:
| Dependent | Consumes | Impact |
|-----------|----------|--------|
| ๐ต **ginger-cpp** | `lds-cpp` (CPM v1.2.5) | Uses only `VdCorput::pop()` (constexpr) + `TWO_PI` โ **safe, recompile only** |
| ๐งฑ **physdes-cpp** | `lds-gen-cpp` (not lds-cpp!) | **Unaffected** โ no transitive dependency |
| ๐ฎ **sphere-n-cpp** | `lds-gen-cpp` | **Unaffected** |
Only **one** of the three "dependents" actually depends on lds-cpp.
**Know your dependency graph before you refactor.** ๐บ๏ธ
---
### โ
LDS Verification Across Four Repos
| Repo | Tests | Toolchain gates |
|------|-------|-----------------|
| ๐งฑ **lds-cpp** | 96 cases ยท 4,622 assertions | MSVC `/W4 /WX` ยท clang-format |
| โ๏ธ **lds-gen-cpp** | 76 cases ยท 6,405 assertions | `/W4 /WX` ยท clang-format |
| ๐ฆ **lds-rs** | 97 unit + 12 doc tests | `cargo fmt` ยท `clippy` ยท `test` |
| ๐ **lds-gen** | 103 pytest + 19 doctests | black ยท isort ยท flake8 |
**Every gate green, zero behavioral drift** โ exact-value tests matched the
Python reference bit-for-bit. โ
---
class: nord-light, middle, center
## ๐ฏ Part 6 โ Lessons Learned
---
### ๐ฏ What Refactoring *Really* Means
.pull-left[
**Do refactor:** โ
- ๐ญ Real duplication (5ร node creation)
- ๐งฐ Telescoping constructors
- ๐๏ธ ~80% identical algorithm skeletons
- ๐ When siblings share the same patterns
**Don't force it:** ๐ซ
- ๐ฆ Rust's struct-update syntax already *is* a builder
- ๐งฉ A Visitor framework for a 30-line internal tree
- ๐ฟ Splitting a cohesive 500-line value type
- ๐ฎ Hypothetical future needs (YAGNI)
]
.pull-right[
**Process that made it safe:** ๐ก๏ธ
1. ๐ **Map the smells** โ explore agents + code review
2. ๐ฏ **Name the target pattern** before touching code
3. ๐งช **Baseline tests** before any edit
4. ๐ **Preserve contracts** โ hidden ordering, error guards
5. โ
**Re-run every gate** โ build, lint, format, doc, test
6. ๐งฑ **Atomic commits** โ one pattern per commit
]
---
### ๐ฏ Key Takeaways
- ๐งฉ **Design patterns are refactoring targets** โ recognize the smell, name the pattern, apply it
- ๐ **Polyglot refactoring** keeps sibling projects structurally consistent, making cross-language maintenance easier
- ๐ฆ **Match the idiom** โ the same pattern looks different in C++, Rust, and Python
- ๐งช **Tests are the safety net** โ 12,000+ assertions across the repos never blinked
- ๐ค **AI amplification** โ telling an agent "apply the Builder pattern" yields far better output than "clean this up"
- ๐ **Less code, same behavior** โ hundreds of duplicated lines removed, zero behavioral drift
---
### ๐๏ธ Session Log
.pull-left[
**Case Study 1 โ physdes** (9 commits)
.font-sm[
| Repo | Commits |
|------|---------|
| ๐งฑ physdes-cpp | `7942ac7` Factory ยท `9d8f9be` Builder ยท `52ac44f` Template Method |
| ๐ฆ physdes-rs | `1bbbeb0` Factory ยท `01179f1` Builder ยท `c3b9045` shared wrapper |
| ๐ physdes-py | `2e03732` Factory ยท `311848a` Builder ยท `3666c82` Template Method |
]
All on `dev`, all gates green, ready for PRs. ๐ค
]
.pull-right[
**Case Study 2 โ LDS family** (~600 lines removed)
.font-sm[
| Repo | ฮ lines | Highlights |
|------|---------|------------|
| ๐งฑ lds-cpp | `lds.hpp` 978 โ 802 | CRTP + concept + digit core + factory registry |
| โ๏ธ lds-gen-cpp | `lds.hpp` 949 โ 734 | atomic CRTP ยท composites atomic-as-point |
| ๐ฆ lds-rs | โ75 + 33ร `#[inline]` | trait + macro ยท `DigitValue` |
| ๐ lds-gen | `lds.py` 574 โ 430 | `GeneratorBase[T]` ยท new `vdc_i()` |
]
Uncommitted working trees, ready for review. ๐ค
]
---
### ๐ Further Reading
.pull-left[
**Patterns**
- ๐งฉ _Design Patterns_ โ Gamma, Helm, Johnson, Vlissides
- ๐๏ธ _Effective C++ / Modern C++_ โ Scott Meyers
- ๐ฆ _Rust Design Patterns_ โ rust-unofficial
**Case Study 1 โ physdes**
- ๐งฑ github.com/luk036/physdes-cpp
- ๐ฆ github.com/luk036/physdes-rs
- ๐ github.com/luk036/physdes-py
**Case Study 2 โ LDS family**
- ๐งฑ github.com/luk036/lds-cpp
- โ๏ธ github.com/luk036/lds-gen-cpp
- ๐ฆ github.com/luk036/lds-rs
- ๐ github.com/luk036/lds-gen
]
.pull-right[
**Refactoring practice**
- ๐ ๏ธ _Refactoring_ โ Martin Fowler
- ๐ _Working Effectively with Legacy Code_ โ Michael Feathers
- ๐งช Property-based testing (RapidCheck / proptest / Hypothesis)
- ๐ค Pattern-aware AI prompting (Sisyphus workflow)
**Try it:** pick your own 5-site duplication and give it a factory. ๐
]
---
count: false
class: nord-dark, middle, center
# ๐ Thank You!
### Refactoring with Design Patterns
@luk036 ๐จโ๐ป ยท Questions welcome ๐ค