The refactoring is now published as Slice on sammancoaching.org: https://sammancoaching.org/refactorings/slice.html - git mv ideas/split-loop-with-lambda -> ideas/slice-with-lambda - rename learning-hour file and update frontmatter name - update paths in tools/rehearse scripts and report title - update prose: 'Split Loop with Lambda' -> 'Slice with Lambda' in learning hour, exercise READMEs, storyboard, solution comments - link the published Slice refactoring from the pattern draft
8.1 KiB
theme, title, name, kata, difficulty, author, affiliation, tags
| theme | title | name | kata | difficulty | author | affiliation | tags |
|---|---|---|---|---|---|---|---|
| refactoring | Slice with Lambda — when the variable you slide is a function | slice-with-lambda | IceCreamForecasting-Refactoring-Kata (pre-factored "prefactored-longrange" variant) | 4 | Willem van den Ende | QWAN | refactoring java slice lambda testability composed-method |
Slice with Lambda
You know Split Loop: separate the loops that compute from the loops that collect, using introduce variable and slide statement. But sometimes the thing you want to slide is not a value — it is a function call that reaches out to a dependency you do not want (a weather API, machine learning, a database). Plain Split Loop stalls: the variable you introduce would have to hold "the answer to a question that is not asked yet". The fix is to make the laziness explicit: introduce a lambda, slide that, and extract a method that takes the function as a parameter.
This is a follow-up session: the team has already done Split Loop (and has used these mechanics on production code, unnamed). Here we name the pattern, drill the mechanics on a pre-factored slice of IceCreamForecasting-Refactoring-Kata, and end at a testable, pure seam.
Learning Goals
- Given a loop whose body needs an outside dependency, perform the six Slice mechanics (introduce lambda → slide → extract method with function parameter) on unfamiliar code, without the handout open.
- Recognize when plain Split Loop is not enough — the variable you need to slide must be a function, not a value — and name the next move.
(Testability is a consequence, not a goal this hour — it shows up as a debrief question: "what just became testable, and why?")
Session Outline
- 5 min connect: count the concerns (on the kata's original method)
- 5 min concept: the mechanics, six steps with names
- 10 min demo: the weather slice of
LongRangeForecast(Java) - 25 min concrete: pairs do the whole pre-factored method
- 5 min conclusion: debrief + "what became testable?" + next steps
Total 50 min + buffer.
Connect - Count the concerns
Show the original longRangeForecast from the kata (the one with the Q1/Q4
branch and the hardcoded dates) on a slide. Ask pairs, 90 seconds: "how many
different things is this method doing? Point at the lines."
Take answers: quarter default, reset loop, date list, weather collection, scoring loop — five or six, depending how you count. Land the point: your production code looks like this too, and today we learn one move that cuts one concern loose even when Split Loop alone cannot.
Concept - The six mechanics
Walk the pattern — the handout is zeroth-draft-of-refactoring-pattern.md;
there is also a published description, Slice:
- Extract the dependency-using code into a lambda — no tool does this; it is a small manual edit.
- Slide the lambda definition to the top of the method; leave the invocation where it is.
- Extract the loop into a method that takes the lambda as a parameter.
(IntelliJ: Extract Method, then Introduce Parameter / Change Signature.
Suggested type for this exercise:
LongFunction<Boolean>.) - Move the method to a class where the unwanted dependency is unknown.
- Test with a pure lambda instead of the real dependency.
- Check what new test combinations became easy.
Steps 1–2 are the new move — that is where plain Split Loop stalls, because the thing being slid is a function, not a value. Steps 3–6 are familiar Split Loop territory. Today we stop after step 3 (with a test, step 5-lite); step 4 is next time.
If there is time, show one slide: the slice graphic from the Slice learning hour.
Demo - The weather slice (10 min)
Full script for the complex variant of this demo — on DailyForecasts.printSalesForecasts(),
with SVGs and narration beats — is in storyboard/daily-forecasts-demo.md.
Do the six mechanics on a whiteboard-projection slice of
LongRangeForecast — only the DATES loop:
List<Boolean> expectedWeather = new ArrayList<>();
for (Instant interestingDate : DATES) {
scorer.updateSelection();
long daysForward = ChronoUnit.DAYS.between(ANCHOR_DATE, interestingDate);
Boolean weather = scorer.lookupWeather(daysForward);
expectedWeather.add(weather);
}
Narrate: everything this loop needs from outside is "given a day offset,
is it sunny?" Extract that question into a lambda, slide it up, extract the
loop into collectExpectedWeather(LongFunction<Boolean> dayOffsetToWeather).
Note out loud where you paused and what you checked (compile, tests still
compile). Point at the invocation site staying behind — that is what
"slide, don't move" means.
Concrete Practice - Pairs on prefactored-longrange (25 min)
Pairs, Rider or IntelliJ, one driver. Repo: exercise/java (a pre-factored
copy of the kata: Q1/Q4 dead branch stripped, dates as a constant — you gave
them the dates-as-parameter refactoring already, so the only "noise" left is
the two loops and the noise inside the scoring loop).
- (3 min) Run the approval test. It is red and flaky — why? (
ScorercallsRandom; it stands for the weather API.) - (17 min) Do the mechanics on the whole method. Careful: the weather loop
calls
scorertwice. Everything the lambda needs has to be in scope at the invocation site. - (5 min) When your extracted method takes the lambda as a parameter, make
the test pass with a pure lambda (e.g.
daysForward -> true) and check the approved file against{Strawberry:12, Chocolate:12, Vanilla:17, }. Reference end state:exercise/solution/.
Debrief questions on a slide (from exercise/java/README.md):
- At which step did plain Split Loop stop working, and why?
- What did you do with each of the two
scorercalls? - Could you delete either call without changing the forecast? Try it. Why did that work? (updateSelection only mutates Scorer state this method never reads — the lambda made that visible)
- What just became testable that was not before?
Conclusions - Name it, claim it, next move
- Pairs state the pattern in one sentence, using the words lambda, slide, function parameter. Add their sentences to the pattern draft.
- Slide: "the new tests are actually glanceable and pure" — testability was the side effect; ask first ("what was a benefit?"), then show the slide.
- Next moves this sets up: move the method to a class without
Scorer(step 4), turnDATESinto a parameter (the refactor Willem did before this exercise), and the ports-and-adapters view: a lambda is the smallest Port + Adapter that could work. - Feedback on the pattern draft: which steps felt named, which felt fuzzy?
Facilitator notes
- Materials:
exercise/java(participants),exercise/solution(facilitator + fast pairs), handout = the pattern draft, slides = original method + debrief questions. - Fast pairs finish early? Have them do step 4 for real (move
collectExpectedWeatherto a new class), or thedaysForward -> daysForward == 5variation fromsolution/README.md. - If most pairs stall at step 3 (adding the parameter), interrupt after
~8 min and demo just that: Extract Method, then Change Signature to
LongFunction<Boolean>. - The overload extension (test can't reach the pure seam because
longRangeForecast()bakes in the real lambda): Inline Variable on the lambda local (⌘⌥N), Introduce Parameter on the lambda (⌥⌘P), test passesdaysForward -> true, then restore a 3-line no-arg overload delegating with the real lambda. End state:exercise/solution/. - Debrief fork: inject through the public overload (tests composition +
slice) vs. test
collectExpectedWeatherdirectly, package-private (tests only the pure slice). No right answer — good conclusion discussion. - Willem's rehearsal:
exercise/rehearse-with-agent.md— run the exercise with qwen + java-refactoring-mcp CLI first; agent failure modes become concept-slide material.