Slice with lambda: add C# exercise projects and export
- Add SliceWithLambda.slnx + csproj scaffolding for ideas/slice-with-lambda/ exercise/csharp (examples project + xUnit solution/test project) - Add exercises/slice-with-lambda (C# version of the prefactored-longrange exercise with Buyers and SliceWithLambda projects) - Add export/slice-with-lambda (nested git repo with exercise export) - Ignore bin/ and obj/ build output at repo root
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user