dom lizarraga

    dominiclizarraga@hotmail.com

    POOD Session 1: Write Shameless Green

    14 minutes to read

    Session 1: Write Shameless Green

    Date: August 09, 2025

    Key Concepts

    • DRY is not always good.
    • Duplication is far cheaper than the wrong abstraction
    • Make smaller things and let them know about each other as little as possible.
    • To do refactoring, you must have tests.
    • Open/Closed. Open for extension, closed for modification.
    • Inheritance
    • Middleman
    • Factory item
    • Small touch on Single Responsibility Principle

    I’ve started a 12-week walkthrough of Practical Object-Oriented Design Course by Sandi Metz, and we’re reading the first two chapters of 99 botles.

    Here are the highlights from the first session:

    We were tasked to watch Sandi Metz’s talk All the little things (here my notes) and then to complete as much of the coding challenge as possible and make all tests pass within 15 mins. The test was about singing the 99 bottles of beer song which is a song counting down bottles of beer.

    SIDE NOTE: I asked chatGPT about this song after I solved the exercise which is wrong. My mentor has always told me to figure out as much as I can on the domain model (the rules and boundaries of the problem). And since it was the first time I knew about this song I missed very important details, for instance: knowing the highest number possible is 99 and the lowest is 0. It may have changed my approach.

    The tests asserted/expected a concatenated string and at a first glance I identified that numbers and a few words were changing as we were counting down.

    I tried to follow conventions like short classes, short methods and so on. First, I made sure I was outputting the correct string and then pinpointed the numbers that needed the counting-down, then came up with the method name bottle_count_phrase because it was meant for toggling from the words bottle, bottles and no more bottles (0, 1 or > 1 (greater than 1) respectively) and the integer change by subtracting 1.

    I tried the first version and it worked, so I advanced to the next test, until I faced the word variation (“one” or “it”). After identifying the possible variables, in this case only 2, I first wrote down an if statement, which later became a ternary operator.

    With these possible paths and having the 4 tests passing (green) this was my solution:

    class Bottles
      def verse(characters)
        song(characters)
      end
    
      def song(number_of_bottles)
        "#{bottle_count_phrase(number_of_bottles)} of beer on the wall, " +
        "#{bottle_count_phrase(number_of_bottles)} of beer.\n" +
        "Take #{removal_pronoun(number_of_bottles -= 1)} down and pass it around, " +
        "#{bottle_count_phrase(number_of_bottles)} of beer on the wall.\n"
      end
    
      def bottle_count_phrase(number_of_bottles_remaining)
        if number_of_bottles_remaining == 0
          "no more bottles"
        elsif number_of_bottles_remaining == 1
          "#{number_of_bottles_remaining} bottle"
        else
          "#{number_of_bottles_remaining} bottles"
        end
      end
    
      def removal_pronoun(number_of_bottles)
        number_of_bottles >= 1 ? "one" : "it"
      end
    end
    

    After this exercise we were encouraged to see Sandi’s solution which is the following:

    
    def song
      verses(99, 0)
    end
    
    def verses(upper, lower)
      upper.downto(lower).map { |i| verse(i) }.join("\n")
    end
    
    case number
    when 0
      "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n"
    when 1
      "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n"
    when 2
      "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n"
    else
      "#{number} bottles of beer on the wall, #{number} bottles of beer.\nTake one down and pass it around, #{number-1} bottles of beer on the wall.\n"
    end
    

    As soon as I saw it, felt like my mentor was talking to me again:

    • Make sure you understand the domain.
    • Stare at the input, see what changes in the output.
    • Devise how to achieve those modifications (do I need an array, hash or counter variable to store any type of result?).
    • If outcome is the expected, then improve.

    Where I’m getting with this is, that if I knew that we were only to receive integers as inputs and that the song based on that input, was always going to be the same except the words (‘one’ vs. ‘it’) and numbers, I could have hardcoded the solution (not saying it’s an easy approach to come up with) but my approach was thought with more flexible design with interpolation and conditionals but definitely, I overcomplicated the solution.

    Something that I noticed in this course as in the Effective testing with RSpec by Myron Marston and Ian Dees is that the authors encourage to tackle test one by one even though it sounds obvious to avoid cluttering your terminal with errors, sometimes we want to see the full picture but their approach is very effective.

    Lesson learned, read thoroughly the problem, understand the inputs, outputs, if possible make a hand-written drawing, understand what changes, what type of data structure? and lastly if the result is the expected, refine it. It also reminds me of the PEDAC method.

    And we must aim as quick as possible to ‘Shameless Green’ which is a solution that’s optimized for readability without consideration for future change. We can think of it as the simplest version possible.

    Notes on Sandi Metz’s talk: “All the Little Things

    • All the problems we have caused by writting ugly code has the same simple solution.

    • One piece of advice: make smaller things. Make more classes, more methods and let them know about each other as little as possible.

    • Metric Flog is for measuring complexity of code (Assignments, Branches, Calls). The higher the worse.

    • Sandi used to think that she had to understand everything in order to help with anything.

    • Squint test, is identifying code that can benefit of changing.

    • Changes in shape (nested conditionals == harder to reason about)

    • Changes in color, meaning code has different levels of abstractions therefore difficult to follow.

    • Refactoring is: change its arrangement without altering its behaviour.

    • To do refactoring, you must have tests.

    • OOP build seams between objects, they send messages each other. In prodecual code, it’s just one flow.

    • The first refactoring is not going to be neither smart nor clever. But that’s the whole point. The point is to get to green as quickly as possible.

    • Write code for one test at the time.

    • Finish the refactoring before going off on a tangent so keep the duplication

    • Duplication is far cheaper than the wrong abstraction.

    • If you have code that you don’t understand you have two choices: you can keep it forever or you can write test around it and refactor.

    • All intermediate refactoring amde the code more complex.

    • Open/Closed. Open for extension, closed for modification.

    • Object oriented design is a style guide for arranging code.

    • With Open/Closed in place we’d be able to add new behavior without editing existing code.

    • Whe you have different prefixes and common suffixes, they’re candidate to have the prefix as a Class and the suffix as a method.

    • If a purpose of an object is just forwarding messages to someone else, you must ask if it justifies its existance (it’s called middleman).

    • Item factory, it finds the rights object and sends a message to it. (it produces the right kind of item object and then forwards messages)

    • Inheritance is not evil. You want subclasses to be at the leafnode of the object graph (shallow, narrow) and subclasses use all the behaviour of the super class.

    • Business logic != code configuration

    Notes on Middleman and ItemFacory principle with chatGPT

    Ah, I see exactly the part you’re struggling with. Let me break it down for you clearly, because Sandi Metz is talking about two related ideas here: message forwarding and the “middleman” problem.


    1️⃣ The Gilded Rose problem

    Originally, the GildedRose class has one big method (tick) with 43 lines of conditional logic. It checks the type of item (Normal, Brie, Backstage, Sulfuras) and applies the rules for that item.

    From GildedRose’s perspective:

    • It’s treating items as roles—“I have something that behaves like a Normal item, or like a Brie, etc.”
    • But the code treats them as concrete classes—it looks inside and checks strings, runs conditionals, and manipulates quality and days_remaining directly.

    Sandi’s point: we should flip this around—instead of Gilded Rose knowing how each item works, each item should know how to “tick” itself. GildedRose just asks “item, do your tick.”


    2️⃣ The “middleman” concept

    A middleman is a class whose only purpose is to forward a message to someone else. For example:

    class GildedRose
      def tick_item(item_name)
        item = get_item_class(item_name).new
        item.tick
      end
    end
    

    Here:

    • GildedRose isn’t doing any real work itself; it just finds the right item class and tells it to do its tick.
    • That’s message forwarding.

    Sandi says: “When an object’s only purpose is to forward messages somewhere else, you have to wonder if it justifies its existence.”

    • If GildedRose only forwards messages and does nothing else, maybe it’s not necessary.
    • But in this case, it still has one real responsibility: it figures out which item class to instantiate based on a string. That’s enough justification for its existence.

    So GildedRose becomes effectively an item factory: it produces the right kind of item object and then forwards messages.


    3️⃣ Why this matters for design

    By doing this:

    1. Each item class is responsible for its own behavior → follows the Single Responsibility Principle.
    2. GildedRose no longer has huge conditionals → easier to maintain.
    3. Forwarding messages creates a seam → you can easily swap in new item types without touching GildedRose.

    Before:

    GildedRose -> lots of if statements -> manipulates every item type directly
    

    After:

    GildedRose -> finds item object -> sends :tick
    item object -> handles its own rules
    

    From GildedRose’s point of view, it doesn’t care what kind of item it is, only that it responds to tick. That’s duck typing in action.


    4️⃣ Why “middleman” isn’t always bad here

    Yes, a middleman is sometimes unnecessary. But here, GildedRose:

    • Maps strings to classes (Normal, Brie, Backstage…)
    • Instantiates the correct class
    • Sends tick

    That one responsibility makes it justified. Without it, you’d have to do the mapping everywhere else in your code, which would violate DRY and clarity.


    TL;DR

    • Original GildedRose: knows too much about every item type → big, messy conditionals.
    • Refactored: GildedRose just figures out which object to talk to → forwards tick → small object design.
    • Middleman = class that only forwards messages. Sometimes it’s unnecessary, sometimes it’s a justified abstraction (here, a factory).

    If you want, I can draw a small diagram showing the message flow before and after refactoring—it really makes this click visually.

    Do you want me to do that?