--- theme: refactoring title: Split Loop with Lambda — when the variable you slide is a function name: split-loop-with-lambda kata: IceCreamForecasting-Refactoring-Kata (pre-factored "prefactored-longrange" variant) difficulty: 4 author: Willem van den Ende affiliation: QWAN tags: refactoring java split-loop lambda testability composed-method --- # Split Loop 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](https://github.com/emilybache/IceCreamForecasting-Refactoring-Kata), and end at a testable, pure seam. ## Learning Goals * Given a loop whose body needs an outside dependency, perform the six Split-Loop-with-Lambda 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 (handout: `zeroth-draft-of-refactoring-pattern.md`): 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`.) 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](https://www.sammancoaching.org/learning_hours/testable_design/slice.html). ## 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`](storyboard/daily-forecasts-demo.md). Do the six mechanics on a whiteboard-projection slice of `LongRangeForecast` — only the `DATES` loop: ```java List 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 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`. - 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.