81 lines
3.9 KiB
Markdown
81 lines
3.9 KiB
Markdown
# Slice with Lambda — C# exercises
|
|
|
|
Exercises for the **Slice with Lambda** learning hour (C# variant).
|
|
|
|
- Condensed mechanics: <https://www.sammancoaching.org/refactorings/slice.html>
|
|
(where that page says mock, we use a lambda instead).
|
|
- Full Java walkthrough: `../../ideas/slice-with-lambda/storyboard/storyboard.md`.
|
|
|
|
Open `SliceWithLambda.sln` in Rider (or run `dotnet test` — see below).
|
|
Requires the .NET 10 SDK (pinned by `global.json`).
|
|
|
|
## What's in the box
|
|
|
|
| folder | what | start state |
|
|
|---|---|---|
|
|
| `src/SliceWithLambda/` | three demo examples (pristine) | the refactoring **not** done yet |
|
|
| `src/Buyers/` | the pair exercise (pristine) | one loop, one lambda-able expression |
|
|
| `solution/` | reference end states (facilitator + fast pairs) | mechanics done, tests stub through the seam |
|
|
| `tests/` | test projects | see the red/green table |
|
|
|
|
## The three demo examples (`src/SliceWithLambda/`)
|
|
|
|
| file | guard (client state) | unwanted dependency | lambda | tail |
|
|
|---|---|---|---|---|
|
|
| `MetaphorFree.cs` | `if (a < 0) return 0;` | `_someDependency.mightThrowWhenPreconditionNotSatisfied(a)` | `Func<int, int> readB = a => ...` | `int c = a + b; return c * 2;` |
|
|
| `WeatherStation.cs` | `if (_wateredToday)` | `_lightSensor.Read()` (byte) | `Func<byte> readLight = () => ...` | sunny -> `WateringDuration.Medium`, shady -> `Short` (reading > 128 => `SunExposure.Sunny`) |
|
|
| `IceCreamShop.cs` | `if (_thawedToday)` | `_thermometer.Read()` (byte) | `Func<byte> readTemperature = () => ...` | warm -> `ThawAmount.Medium`, cold -> `Low` (temperature > 25 => `Warmth.Warm`) |
|
|
|
|
In all three, the guard reads client state (a parameter or a flag the client
|
|
owns), so it stays in the extracted method. Only the dependency call moves
|
|
into the lambda.
|
|
|
|
`MetaphorFree.cs` is the smallest possible shape: no loop, one dependency
|
|
call, a tail of one arithmetic step. It carries the capture-to-parameter
|
|
lesson: the lambda takes `a` as its parameter because a lambda moved up into
|
|
the function head cannot capture method parameters. `WeatherStation.cs` and
|
|
`IceCreamShop.cs` are same-shape domain repetitions, so participants can
|
|
practice extracting the lambda twice — the C# delegate syntax (`Func<byte>`)
|
|
is worth the repetition.
|
|
|
|
## The pair exercise (`src/Buyers/`)
|
|
|
|
`Buyers.ShoppingListTotal(buyerName)` computes a buyer's shopping-list
|
|
total: one loop, and exactly **one** lambda-able expression —
|
|
`priceList.PriceFor(flavour)`. `PriceList` adds random surge pricing, so the
|
|
test in `tests/BuyersTests/` is red or flaky by design: it stands for the
|
|
external pricing service.
|
|
|
|
Do the mechanics on the whole method:
|
|
|
|
1. Extract the dependency-using code into a lambda and slide it to the top
|
|
(`Func<IceCream, int> priceFor = flavour => priceList.PriceFor(flavour);`
|
|
— the invocation in the loop stays put).
|
|
2. Extract the loop into a method taking the lambda as a parameter.
|
|
3. Keep a no-arg overload for callers (pass the method group
|
|
`priceList.PriceFor` as the "real" pricing function) and make the test
|
|
pass with a pure lambda (`_ => 2`): 2 flavours x 2 scoops x 2 = 8.
|
|
|
|
Reference end state: `solution/Buyers/` (green test in
|
|
`tests/BuyersSolutionTests/`).
|
|
|
|
## Tests: red/green map
|
|
|
|
| test project | against | expected at start | after the refactoring |
|
|
|---|---|---|---|
|
|
| `tests/BuyersTests/` | `src/Buyers/` | red or **flaky** (surge pricing) — that is the point | green once you stub with a pure lambda |
|
|
| `tests/BuyersSolutionTests/` | `solution/Buyers/` | green | — |
|
|
| `tests/SliceWithLambdaTests/` | `solution/SliceWithLambda/` | green | — (the demo end states, with pure-lambda stubs, incl. a throwing stub) |
|
|
|
|
## Run
|
|
|
|
```sh
|
|
dotnet test # everything
|
|
dotnet test tests/BuyersTests # the pair exercise only
|
|
```
|
|
|
|
## Next move (debrief)
|
|
|
|
Add `DiscountFor(IceCream)` to `PriceList` and feel why one lambda is no
|
|
longer enough — extract a role interface (`IPricing`) instead of two lambdas.
|