Files
learning-hour-maker/ideas/slice-with-lambda/slice-with-lambda-learning-hour.md
Your Name 77ae05c28c Rename split-loop-with-lambda to slice-with-lambda
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
2026-09-29 15:47:30 +00:00

8.1 KiB
Raw Permalink Blame History

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:

  1. Extract the dependency-using code into a lambda — no tool does this; it is a small manual edit.
  2. Slide the lambda definition to the top of the method; leave the invocation where it is.
  3. 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>.)
  4. Move the method to a class where the unwanted dependency is unknown.
  5. Test with a pure lambda instead of the real dependency.
  6. 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).

  1. (3 min) Run the approval test. It is red and flaky — why? (Scorer calls Random; it stands for the weather API.)
  2. (17 min) Do the mechanics on the whole method. Careful: the weather loop calls scorer twice. Everything the lambda needs has to be in scope at the invocation site.
  3. (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 scorer calls?
  • 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), turn DATES into 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 collectExpectedWeather to a new class), or the daysForward -> daysForward == 5 variation from solution/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 passes daysForward -> 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 collectExpectedWeather directly, 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.