Date: August 23, 2025
This blog post consists in two parts:
Selecting the most similar pieces of code
Finding the smallest difference between them
Making the simplest change to remove that difference
Abstraction Discovery – Don’t invent abstractions upfront; uncover them by observing recurring patterns and responsibilities that emerge through refactoring.
Naming by Responsibility – When extracting methods, name them after what they do, not how they do it. (e.g. quantity, container, pronoun, action, successor)
Liskov Substitution Principle – Objects should behave as they claim to; avoid conditionals that depend on an object’s type.
Duck Typing – In Ruby, objects are defined by their behavior, not their class. If two objects respond to the same messages, they share a “role.”
e.g. PrivateContract and CommercialContract both respond to #name, so they can be used interchangeably.
Depending on Abstractions
Emergent Design – By consistently applying the Flocking Rules, abstractions emerge naturally; you don’t need to foresee the design from the start.
Watch 1: Following the Flocking Rules
✨ Quick reminder: We need to change the code to display “six-pack” instead of “6 bottles” which means make the code open to extension, closed to modification
Here are the steps to find abstractions in code, these are mini-decisions, we still don’t know the outcome:
a) parse the new code b) parse and execute it c) parse, execute and use its results d) deleted unused code
Remember to
Set of rules of flocking birds 🦅-🦅-🦅-🦅
This will turn difference into sameness and will reveal Concealed Concepts.
Watch 2: Converging on Abstractions
Increase isolation of the thing we want to vary. With the hope that eventually the code will open for the change we want to make.
Discover hidden abstractions instead of look at the problem and make them up.
We went from this code:
def verse(number)
case number
when 0
"No more bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go 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.\n" +
"Take 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.\n " +
"Take 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.\n " +
"Take one down and pass it around, " +
"#{number - 1} bottles of beer on the wall.\n"
end
end
To this one, always following flocking rules for finding hidden abstracions, select the things that are most alike, find the smallest difference between them and make the simplest change to remove the difference:
def verse(number)
case number
when 0
"No more bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go 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.\n" +
"Take it down and pass it around," +
"no more bottles of beer on the wall.\n"
else
"#{number} #{container(number)} of beer on the wall, " +
"#{number} bottles of beer.\n" +
"Take one down and pass it around, " +
"#{number - 1} bottles of beer on the wall.\n"
end
end
def container(number)
if number == 1
"bottle"
else
"bottles"
end
end
In this block we finished DRYing out bottles Shameless Green via the Flocking Rules. We coded for 30 minutes with the aim to DRYing out the #verse.
After that time we watched the solution videos, replicate the code and discussed it with the group.
This is the code we started with, after chopping off the case statement for number 2 all tests run on green:
def song
verses(99, 0)
end
def verses(upper, lower)
upper.downto(lower).map { |i| verse(i) }.join("\n")
end
def verse(number)
case number
when 0
"No more bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go 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.\n" +
"Take it down and pass it around, " +
"no more bottles of beer on the wall.\n"
else
"#{number} bottles of beer on the wall, " +
"#{number} bottles of beer.\n" +
"Take one down and pass it around, " +
"#{number-1} #{container(number-1)} of beer on the wall.\n"
end
end
def container(number)
if number == 1
"bottle"
else
"bottles"
end
end
end
Watch 1: Replacing Difference With Sameness
It’s time to pick a pair to work on, keeping in mind go for the code that is pretty similar to each other, in other words, less differences and work towards reducing the differences.
In this case is the branch 1 and the else branch.
Their difference rely on "1 bottle" vs "#{number}", "it" vs "one" and "no more" vs "#{number-1}"
This change will make our code slower and more abstract, but he have to do this in order to make our code look the same.
The cost of this change is execution time, but keeping both cases will cost more due to maintenance (2 very similar branches).
def verse(number)
case number
when 0
"No more bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go to the store and buy some more, " +
"99 bottles of beer on the wall.\n"
when 1
"#{number} #{container(number)} of beer on the wall, " +
"#{number} #{container(number)} of beer.\n" +
"Take it down and pass it around, " +
"no more bottles of beer on the wall.\n"
else
"#{number} #{container(number)} of beer on the wall, " +
"#{number} #{container(number)} of beer.\n" +
"Take one down and pass it around, " +
"#{number - 1} #{container(number - 1)} of beer on the wall.\n"
end
end
When following the flocking rules trying to turn difference into sameness, resist the urge to volunteer changes. Limit your changes to only what the recipe calls for and see what happens
Watch 2: Equivocating About Names
The official definition of equivocate is to use ambiguous language so as to conceal the truth or avoid committing oneself.
Sometimes we just don’t know and we have to do the best we can with the information we have at the time.
We are now tackling the "it" vs "one" case, therefore we need to name the concept, create a method or function to be responsible for it, and then use it, send the message in the place of this difference.
We are looking for that method name where def thing is many layers of abstraciton away.
When naming, we have 3 rules:
| Naming Rule | Pros | Cons |
|---|---|---|
| Time-boxed naming - Set a time limit and use a thesaurus to find the best name within that time | Time limited approach prevents overthinking | You'll never know less than you know right now, so a better name may emerge later; good enough names may not motivate future improvements |
| Intentionally bad placeholder - Pick the worst possible name (like "foo") knowing you'll rename it later | Very fast; saves time and money | The name is terrible |
| Ask someone good at naming - Find a person skilled at naming and describe your problem to them | Access to expertise; the act of describing the problem may help you discover a good name yourself | Self explanatory |
In this case "it" and "one" are pronouns. It feels too far from the domain of the 99 Bottles song, but it also feels more correct than anything else I can think of.
Let’s use pronoun by first defining the method, then making it return the else branch. After that add the pronoun method in the actual code, run tests, then add a default argument, then we add another branch to cover the 1 branch
Now that else branch and 1 branch are identical and as we are getting used to this recipe the coding part becomes easier, however the naming part is the challenging.
def verse(number)
case number
when 0
"No more bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go to the store and buy some more, " +
"99 bottles of beer on the wall.\n"
when 1
"#{number} #{container(number)} of beer on the wall, " +
"#{number} #{container(number)} of beer.\n" +
"Take #{pronoun(number)} down and pass it around, " +
"no more bottles of beer on the wall.\n"
else
"#{number} #{container(number)} of beer on the wall, " +
"#{number} #{container(number)} of beer.\n" +
"Take #{pronoun(number)} down and pass it around, " +
"#{number - 1} #{container(number - 1)} of beer on the wall.\n"
end
end
def container(number)
...
end
def pronoun(number)
if number == 1
"it"
else
"one"
end
end
Watch 3: Deriving Names From Responsibilities
We finished last lesson by picking def pronoun(number) for the "it" and "one" and it was difficult to come up with that name, it’s even more complex when we don’t know the domain.
We’ll continue with the same flocking rules as previously, pick the code that are similar, remove differences, create a new method to inject in that space and remove unused code.
A good trick for naming is “What is the responsibility of the method or function that I’m trying to create to replace this difference?”
At least for now, the good enough name for the change on "no more" words is going to be "quantity".
Watch 4: Choosing Meaningful Defaults
In this section we add the "def quantity" method and play with a default argument "def quantity(number=0)"
When you can, use the else branch first.
def verse(number)
case number
when 0
"No more bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go to the store and buy some more, " +
"99 bottles of beer on the wall.\n"
else
"#{number} #{container(number)} of beer on the wall, " +
"#{number} #{container(number)} of beer.\n" +
"Take #{pronoun(number)} down and pass it around, " +
"#{quantity(number - 1)} #{container(number - 1)} of beer on the wall.\n"
end
end
def quantity(number=0)
if number == 0
"no more"
else
number
end
end
def container(number)
def pronoun(number)
Watch 5: Seeking Stable Landing Points
The section emphasizes the importance of consistency in code style to reduce mental load and business costs. Sandi Metz explains that consistent, similarly structured methods make code easier to read and maintain. She advises teams to adopt and follow a style guide, even if imperfect, because any consistent style is better than none.
If teams can’t agree, rotate disputed styles for a month; if seniors resist, let them keep their own area but require adherence to team style elsewhere. Over time, consistency benefits everyone by highlighting real differences in code and lowering long-term costs.
Watch 6: Obeying the Liskov Substitution Principle
The point of Liskov is that objects have to be what they say they are. They have to behave like you expect. They can’t do anything that forces folks that interact with them to check what kind of a thing they are in order to know how to talk to them.
Please notice the consistency we have in the new methods, all of them have if statements, and receive an argument number however we are doing a kind of a duck type. 🦆
Example:
# Define the "duck type" role: every Contract must respond to `name`
class PrivateContract
def initialize(person_name)
@person_name = person_name
end
def name
@person_name
end
end
class CommercialContract
def initialize(business_name)
@business_name = business_name
end
def name
@business_name
end
end
# Code that works with any kind of contract
def print_contract_name(contract)
puts "Contract with: #{contract.name}"
end
# Example usage
private_contract = PrivateContract.new("John Doe")
commercial_contract = CommercialContract.new("Acme Corporation")
print_contract_name(private_contract)
# => Contract with: John Doe
print_contract_name(commercial_contract)
# => Contract with: Acme Corporation
In the example above, we unified the API so that both private and commercial contracts respond to the same message: .name
Here is the code by fixing it from the quantity method to respond to .capitalize
def verse(number)
case number
when 0
"#{quantity(number).capitalize} bottles of beer on the wall, " +
"no more bottles of beer.\n" +
"Go to the store and buy some more, " +
"99 bottles of beer on the wall.\n"
else
"#{quantity(number).capitalize} #{container(number)} of beer on the wall, " +
"#{number} #{container(number)} of beer.\n" +
"Take #{pronoun(number)} down and pass it around, " +
"#{quantity(number - 1)} #{container(number - 1)} of beer on the wall.\n"
end
end
def quantity(number=0)
if number == 0
"no more"
else
number.to_s
end
end
def container(number)
def pronoun(number)
Even in a dynamically-typed language like Ruby, you should never check an object’s type just to decide what message to send. Instead, define a clear role (a duck type) and make sure every object that plays that role conforms to its API.
Watch 6: Taking Bigger Steps
So we’ve been through this pattern a couple of times where we turned small differences into methods, so we could send messages to make things the same.
We grabbed that shape (verse method and 4 case statements) and moved it down into these new methods that we’re creating.
def verse(number)
case number
when 0
"#{quantity(number).capitalize} bottles of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"99 bottles of beer on the wall.\n"
else
"#{quantity(number).capitalize} #{container(number)} of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"#{quantity(number - 1)} #{container(number - 1)} of beer on the wall.\n"
end
end
def action(number)
if number == 0
"Go to the store and buy some more, "
else
"Take #{pronoun(number)} down and pass it around, "
end
end
def quantity(number)
def container(number)
def pronoun(number)
Watchn 7: Discovering Deeper Abstractions
We’ve almost arrived to have same code on both branches, except the last line of verse method.
In our first apporach we proposed adding a new branch to if statemnt
def verse(number)
case number
when 0
"#{quantity(number).capitalize} bottles of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"#{quantity(number - 1)} bottles of beer on the wall.\n"
else
"#{quantity(number).capitalize} #{container(number)} of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"#{quantity(number - 1)} #{container(number - 1)} of beer on the wall.\n"
end
end
def quantity(number)
if number == -1 # 👈
99
elsif number == 0
"no more"
else
number.to_s
end
end
With this change the tests are green however "-1" isn’t a valid value in the context of the song, which has to make you wonder whether things are working by accident and not really working by design.
We’d better ask ourselves, what is the responsibility of the quantity method?
quantity is responsible for knowing what to sing in place of a number. If there are 50, you’re going to sing “50”, if there’s 0, you’re going to sing “no more”. This is the method that represents the mapping between the value of a number and the string that you sing in its place.
As the song progresses, the verse number gets decremented, except when we reach 0, it wraps back around to the top and starts over again with 99.
When you’re confused, very often a good strategy is: don’t try to solve the whole problem straight away. If you can nibble away at it, solving the simple parts of the problem might make the hard ones easier.
And here we already have a rule for what to do when we’re confused; it’s to try to make things more alike, even if not yet identical, using code that we’ve already written.
You can think of this next verse as the successive verse. If I ask you for the successor of the letter B, you would tell me C, you wouldn’t tell me A. We think of A as the predecessor and B as a successor. I kind of like the word successor here, but we have to agree that successor means next and not necessarily next higher.
def verse(number)
case number
when 0
"#{quantity(number).capitalize} bottles of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"#{quantity(succesor(number))} #{container(number)} of beer on the wall.\n"
else
"#{quantity(number).capitalize} #{container(number)} of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"#{quantity(number - 1)} #{container(number - 1)} of beer on the wall.\n"
end
end
def succesor(number)
if number == 0
"99"
else
number - 1
end
end
def action(number)
def quantity(number)
def container(number)
def pronoun(number)
Watch 8: Depending on Abstractions
Abstractions are beautiful things. They allow you to consolidate the implementation details for an idea in your code in a single place so that everybody can use it if they know its name. They give a name to those things so that you can have a conversation with people using this shortcut language, instead of having to describe the whole thing.
class Bottles
def song
verses(99, 0)
end
def verses(upper, lower)
upper.downto(lower).map { |i| verse(i) }.join("\n")
end
def verse(number)
"#{quantity(number).capitalize} #{container(number)} of beer on the wall, " +
"#{quantity(number)} #{container(number)} of beer.\n" +
action(number) +
"#{quantity(succesor(number))} #{container(succesor(number))} of beer on the wall.\n"
end
def succesor(number)
if number == 0
"99"
else
number - 1
end
end
def action(number)
if number == 0
"Go to the store and buy some more, "
else
"Take #{pronoun(number)} down and pass it around, "
end
end
def quantity(number=0)
if number == 0
"no more"
else
number.to_s
end
end
def container(number)
if number == 1
"bottle"
else
"bottles"
end
end
def pronoun(number)
if number == 1
"it"
else
"one"
end
end
end
We use the Flocking Rules to convert four concrete verse_templates into a single more abstract verse method. And along the way, we found a bunch of smaller, internal abstractions and we’ve created methods for them and named them.
It’s important to ask whether this new code actually improves upon the Shameless Green variant from which we started. Most programmers do think it’s better, but you may be sad to find out that static analysis tools will score it worse.
We turned one conditional into many and add 55% more code.
The counterbalance of that is that there’s a lot of value in this code.
What we have now, that we didn’t use to have, is a bunch of identified, named concepts. We know that 99 bottles contains a container and a pronoun and a quantity and an action, and even a successor.
This block finished the refactoring that began in previous blocks. It iteratively followed the Flocking Rules to remove differences in the verse method, and as a result unearthed abstractions that were deeply hidden within the 99 Bottles song.
It illustrated the power of the Flocking Rules to uncover sophisticated concepts, even those which cast only dim shadows in the existing code. You don’t have to understand the entire problem in order to find and express the correct abstractions—you merely apply these rules, repeatedly, and abstractions will naturally appear.
One final thought before moving on. Consider this question: If several different programmers started from Shameless Green and refactored the verse method according to the Flocking Rules, what would the resulting code look like? If you’ve guessed that everyone’s code would be identical, excepting the names used for the concepts, you’d be right. This has enormous value.
Now we’ll return to the “six-pack” problem.