dom lizarraga

    dominiclizarraga@hotmail.com

    POOD Session 11: Escalation, Loosen Coupling in Tests & Nothing is Something

    12 minutes to read

    Session 11: Escalation, Loosen Coupling in Tests & Nothing is Something

    Date: October 18, 2025

    This blog post consists in four parts:

    Key Concepts

    Escalation

    This block introduces a new requirement: Escalation #2-Mix up Actors and Actions. This triggers a whole bunch of refactorings and writing of new tests, after which the code ends up both simpler and more abstract.

    Watch 1: Seeking Abstractions

    New requirement is split the data into two arrays, one named ‘actor’ and the other ‘action’.

    Example:

    # The trailing 'that' in each line below separates two things: 'actor' and 'action'
    # actors:
    # 'the horse and the hound and the horn' or 'the rooster that crowed in the morn'
    #
    # actions
    # 'that belong to' or'that woke'
    #
    # Create another variant that randomly mixes actors and actions.
    # ------------actor-----------------------action---------
    # Thus, 'the priest all shaven and shorn', 'that killed', etc.
    
    

    As you work, consider these questions:

    1. Should you split the DATA into two arrays, one named ‘actor’ and the other ‘action’? Or is there a more general way to satisfy the actor/action requirement?

    2. This is the first requirement that involves that DATA array. Is it time to isolate the DATA in a separate class?

    3. If the DATA moves to a separate class which is then injected back into House, should the injected data come in the correct order, or should House still be responsible for ordering it?

    As we look here at the current House class, if you were to ask yourself, “Does it have more than one responsibility?” or perhaps “If it did two things, what would those two things be?” the answer is, probably, the DATA feels like one kind of thing, and the code, the algorithm, feels like another.

    Let’s start by writing some tests for the new classes before we extract DATA from House:

    class OriginalOrdererTest < Minitest::Test
      def test_order
        input = %w(a b c d e)
        expected = input
        assert_equal expected, OriginalOrderer.new.order(input)
      end
    end
    
    class RandomOrdererTest < Minitest::Test
      def test_order
        Random.srand(1)
        input = %w(a b c d e)
        puts input
        expected = %w(c b e a d)
        assert_equal expected, RandomOrderer.new.order(input)
        Random.srand
      end
    end
    
    class RandomLastOrdererTest < Minitest::Test
      def test_order
        Random.srand(1)
        input = %w(a b c d e always_last_item)
        expected = %w(c b e a d always_last_item)
        assert_equal expected, RandomLastOrderer.new.order(input)
        Random.srand
      end
    end
    

    This is the code so far once we have extracted the DATA into its own class, making sure it provides the right order since it’s not a House responsability:

    class Phrases
        DATA =
        [ ["the horse and the hound and the horn", "that belonged to"],
          ["the farmer sowing his corn", "that kept"],
          ["the rooster that crowed in the morn", "that woke"],
          ["the priest all shaven and shorn", "that married"],
          ["the man all tattered and torn", "that kissed"],
          ["the maiden all forlorn", "that milked"],
          ["the cow with the crumpled horn", "that tossed"],
          ["the dog", "that worried"],
          ["the cat", "that killed"],
          ["the rat", "that ate"],
          ["the malt", "that lay in"],
          ["the house", "that Jack built"]
        ]
    
      attr_reader :data
    
      def initialize(orderer: OriginalOrderer.new)
        @data = orderer.order(DATA)
      end
    
      def phrase(num)
        data.last(num).join(" ")
      end
    
      def size
        data.size
      end
    end
    
    class House
      attr_reader :phrases, :prefix
    
      def initialize(phrases: Phrases.new, prefixer: MundanePrefixer.new)
        @phrases = phrases
        @prefix = prefixer.prefix
      end
    
      def recite
        1.upto(phrases.size).collect {|i| line(i)}.join("\n")
      end
    
      def phrase(num)
        phrases.phrase(num)
      end
    
      def line(num)
        "#{prefix} #{phrase(num)}.\n"
      end
    end
    
    
    class RandomOrderer
      def order(data)
        data.shuffle
      end
    end
    
    class OriginalOrderer
      def order(data)
        data
      end
    end
    
    class RandomLastOrderer
      def order(data)
        data[0..-2].shuffle << data.last
      end
    end
    
    class PiratePrefixer
      def prefix
        "Thar be"
      end
    end
    
    class MundanePrefixer
      def prefix
        "This is"
      end
    end
    
    puts
    phrases = Phrases.new(orderer: OriginalOrderer.new)
    puts House.new(phrases: phrases).line(12)
    

    Now that we have separated these bits of code, we can play with actors and actions:

    # test for the new method
    class MixedColumnOrdererTest < Minitest::Test
      def test_order
        Random.srand(1)
        input = [["a1", "a2"], ["b1", "b2"], ["c1", "c2"], ["d1", "d2"], ["e1", "e2"]]
        expected = [["c1", "a2"], ["b1", "c2"], ["e1", "e2"], ["a1", "d2"], ["d1", "b2"]]
        assert_equal expected, MixedColumnOrderer.new.order(input)
        Random.srand
      end
    end
    
    # code
    
    class Phrases
        DATA =
        [ ["the horse and the hound and the horn", "that belonged to"],
          ["the farmer sowing his corn", "that kept"],
          ["the rooster that crowed in the morn", "that woke"],
          ["the priest all shaven and shorn", "that married"],
          ["the man all tattered and torn", "that kissed"],
          ["the maiden all forlorn", "that milked"],
          ["the cow with the crumpled horn", "that tossed"],
          ["the dog", "that worried"],
          ["the cat", "that killed"],
          ["the rat", "that ate"],
          ["the malt", "that lay in"],
          ["the house", "that Jack built"]
        ]
    
      attr_reader :data
    
      def initialize(orderer: OriginalOrderer.new)
        @data = orderer.order(DATA)
      end
    
      def phrase(num)
        data.last(num).join(" ")
      end
    
      def size
        data.size
      end
    end
    
    class House
      attr_reader :phrases, :prefix
    
      def initialize(phrases: Phrases.new, prefixer: MundanePrefixer.new)
        @phrases = phrases
        @prefix = prefixer.prefix
      end
    
      def recite
        1.upto(phrases.size).collect {|i| line(i)}.join("\n")
      end
    
      def phrase(num)
        phrases.phrase(num)
      end
    
      def line(num)
        "#{prefix} #{phrase(num)}.\n"
      end
    end
    
    class MixedColumnOrderer
      def order(data)
        data.transpose.map { |column| column.shuffle }.transpose
      end
    end
    
    class RandomOrderer
      def order(data)
        data.shuffle
      end
    end
    
    class OriginalOrderer
      def order(data)
        data
      end
    end
    
    class RandomLastOrderer
      def order(data)
        data[0..-2].shuffle << data.last
      end
    end
    
    class PiratePrefixer
      def prefix
        "Thar be"
      end
    end
    
    class MundanePrefixer
      def prefix
        "This is"
      end
    end
    
    puts
    phrases = Phrases.new(orderer: MixedColumnOrderer.new)
    puts House.new(phrases: phrases).line(12)
    
    

    This requirement, that we mix up actors and actions, gave us the impetus to extract a Phrases class, and that class is now responsible not only for the data but for putting the data in order. House gets injected with an instance of Phrases, and that Phrases object is responsible for returning each phrase to the House class.

    All of this code is super simple, and part of that simplicity comes from the fact that we’ve insisted on finding straightforward abstractions.

    Also, we’re using composition, and we were able to conceive of writing a new orderer to do this, and we were able to think about extracting the data off into some other place where the array of data could have some different kind of shape.

    Loosen Coupling in Tests

    Glory in One Final Easy Change

    Nothing is Something