99 Bottles of OOP

Beta-2 Sample

Front Matter, Chapter 1, Appendices


Sandi Metz
Katrina Owen
Version 0.3
2016-10-06
 

Read the description.

 
Table of Contents

Colophon

Version: 0.3

Version Date: 2016-10-06

Version Notes: beta-2

ISBN-10:1-944823-00-X

ISBN-13:978-1-944823-00-9

Published By: Potato Canyon Software, LLC

1st Edition

Copyright: 2016

Cover Design and Art by Lindsey Morris.

Created using Asciidoctor.

Dedication

Sandi

To Amy, for everything she is and does, and to Jasper, who taught me that nothing trumps a good walk.

Katrina

To Sander, whose persistence is out of this world.

Preface

It turns out that everything you need to know about Object-Oriented Design (OOD) can be learned from the 99 Bottles of Beer song.

Well, perhaps not everything, but quite certainly, a great many things.

The song is simultaneously easy to understand and full of hidden complexity, which makes it the perfect skeleton upon which to hang lessons in OOD. The lessons embedded within the song are so useful, and so broad, that over the last three years it has become a core part of the curriculum of Sandi Metz’s Practical Object-Oriented Design course.

The thoughts in this book reflect countless hours of discussion and collaboration between Sandi and Katrina Owen. These ideas have been battle-tested by hundreds of students, and refined by a series of deeply thoughtful co-instructors, beginning with Katrina. While neither Katrina nor Sandi have the hubris to claim perfect understanding, both have learned a great deal about Object-Oriented Design from teaching this song, and have come to feel that it’s time to buck it up and write it down.

Therefore, this book. We hope that you find it both useful and enjoyable.

What This Book Is About

This book is about writing cost-effective, maintainable, and pleasing code.

Chapter 1 explores how to decide if code is "good enough." This chapter uses metrics to compare several possible solutions to the 99 Bottles problem. It introduces a type of solution known as Shameless Green, and argues that although Shameless Green is neither clever nor changeable, it is the best initial solution to many problems.

Chapter 2 is a primer for Test-Driven Development (TDD), which is used to find Shameless Green. This chapter is concerned with deciding what to test, and with creating tests that happily tolerate changes to the underlying code.

Chapter 3 introduces a new requirement (six-pack), which leads to a discussion of how to decide where to start when changing code. This chapter examines the Open/Closed Principle, and then explores code smells. The chapter then defines a simple set of Flocking Rules which guide a step-by-step refactoring of code.

Chapter 4 continues the step-by-step refactoring begun in Chapter 3. It iteratively applies the Flocking Rules, eventually stumbles across the need for the Liskov Substitution Principle, and ultimately unearths a deeply hidden abstraction.

Chapter 5 inventories the existing code for smells, chooses the most prominent one, and uses it to trigger the creation of a new class. Along the way it takes a hard look at immutability, performance, and caching.

Chapter 6 is not yet available. This chapter performs a miracle which not only removes all conditionals, but also allows you to finally implement the new six-pack requirement without altering any existing code.

Who Should Read This Book

The lessons in the book have been found useful by programmers with a broad range of experience, from rank novice through grizzled veteran. Despite what one might predict, novices often have an easier time with this material. As they are unencumbered by prior knowledge, their minds are open, and easily absorb these ideas.

It’s the veterans who struggle. Their habits are deeply ingrained. They know themselves to be good at programming. They feel quick, and efficient, and so resist new techniques, especially when those techniques temporarily slow them down.

This book will be useful if you are a veteran, but it cannot be denied that it teaches programming techniques that likely contradict your current practice. Changing entrenched ideas can be painful. However, you cannot make informed decisions about the value of new ideas unless you thoroughly understand them, and to understand them you must commit, wholeheartedly, to learning them.

Therefore, if you are a veteran, it’s best to adopt the novice mindset before reading on. Set aside prior beliefs, and dedicate yourself to what follows. While reading, resist the urge to resist. Read the entire book, work the problems, and only then decide whether to integrate these ideas into your daily practice.

Before You Read This Book

You’ll learn more from this book if you spend 30 minutes working on the 99 Bottles of Beer problem before starting to read. See the appendix for instructions.

If you just want to read on but you don’t know Ruby, have no fear. The syntax of the language is so straightforward that you’ll have no trouble understanding what follows. The ideas in this book are not about Ruby, they’re about object-oriented programming and design.

How to read this book

The chapters build upon one another, and so should be read in order. While isolated sections may be useful, the whole is more than the sum of its parts. The ideas gain power in relation to one another.

To get the most from the book, work the code samples as you read along. With active participation you’ll learn more, understand better, and retain longer. While reading has value, doing has more.

Code Examples

The examples are written in Ruby, and the exercises rely on Minitest. The code is available in the 99bottles repository on GitHub, which contains a branch for each chapter.

Errata

A current list of errata is located at www.sandimetz.com/99bottles/errata. If you find additional errors, please email them to errata@99bottlesbook.com.

About the Authors

Sandi Metz

Sandi is the author of Practical Object-Oriented Design in Ruby. She has thirty years of experience working on large object-oriented applications. She’s spoken about programming, object-oriented design and refactoring at numerous conferences including Agile Alliance Technical Conference, Craft Conf, Øredev, RailsConf, and RubyConf. She believes in simple code and straightforward explanations, and is the proud recipient of a Ruby Hero award for her contribution to the Ruby community. She prefers working software, practical solutions and lengthy bicycle trips (not necessarily in that order). Find out more about Sandi at sandimetz.com.

Katrina Owen

Katrina works for GitHub as an Advocate on the Open Source team. Katrina has ten years of experience and works primarily in Go and Ruby. She is the creator of exercism.io, a platform for programming skill development in more than 30 languages. She’s spoken about refactoring and open source at international conferences such as NordicRuby, Mix-IT, Software Craftsmanship North America, OSCON, Bath Ruby and RailsConf. She received a Ruby Hero award for her contribution to the Ruby community. When programming, her focus is on automation, workflow optimization, and refactoring. Find out more about Katrina at kytrinyx.com.

Introduction

This book creates a simple solution to the 99 Bottles of Beer song problem, and then applies a series of refactorings to improve the design of the code.

Put that way, the topic sounds so painfully obvious that one might reasonably wonder if this entire tome could be replaced by a few samples of code. These refactoring "end points" would be a fraction of the size of this book, and a vastly quicker read. Unfortunately, they would teach you almost nothing about programming. Writing code is the process of working your way to the next stable end point, not the end point itself. You don’t know the answer in advance, instead you are seeking it.

This book documents every step down every path of code, and so provides a guided-tour of the decisions made along the way. It not only shows how good code looks when it’s done, it reveals the thoughts that produced it. It aims to leave nothing out. It flings back the veil and exposes the sausage being made.

One final note before diving into the book proper. The chapters that follow apply a general, broad, solution to a specific, narrow, problem. The authors cheerfully stipulate to the fact that you are unlikely to encounter the 99 Bottles of Beer song in your daily work, and that problems of similar size are best solved very simply. For the purposes of this book, 99 Bottles is convenient because it’s simultaneously easily understandable and surprisingly complex, and so provides a refreshing stand-in for larger problems. Once you understand the solutions here, you’ll be able to apply them to the much larger real world.

With that, on to the book.

1. Rediscovering Simplicity

When you were new to programming you wrote simple code. Although you may not have appreciated it at the time, this was a great strength. Since then, you’ve learned new skills, tackled harder problems, and produced increasingly complex solutions. Experience has taught you that most code will someday change, and you’ve begun to craft it in anticipation of that day. Complexity seems both natural and inevitable.

Where you once optimized code for understandability, you now focus on its changeability. Your code is less concrete but more abstract—you’ve made it initially harder to understand in hopes that it will ultimately be easier to maintain.

This is the basic promise of Object-Oriented Design (OOD): that if you’re willing to accept increases in the complexity of your code along some dimensions, you’ll be rewarded with decreases in complexity along others. OOD doesn’t claim to be free; it merely asserts that its benefits outweigh its costs.

Design decisions inevitably involve trade-offs. There’s always a cost. For example, if you’ve duplicated a bit of code in many places, the Don’t Repeat Yourself (DRY) principle tells you to extract the duplication into a single common method and then invoke this new method in place of the old code. DRY is a great idea, but that doesn’t mean it’s free. The price you pay for DRYing out code is that the invoker of the new method no longer knows the result, only the message it should send. If you’re willing to pay this price (i.e. being willingly ignorant of the actual behavior), the reward you reap is that when the behavior changes, you need alter your code in only one place. The argument that OOD makes is that this bargain will save you money.

Did you divide one large class into many small ones? You can now reuse the new classes independently of one another, but it’s no longer obvious how they fit together for the original case. Have you injected a dependency instead of hard-coding the class name of a collaborator? The receiver can now freely depend on new and previously unforeseen objects, but it must remain ignorant of their actual class.

The examples above change code by increasing its level of abstraction. DRYing out code inserts a level of indirection between the place that uses behavior and the place that defines it. Breaking one large class into many forces the creation of something new to embody the relationship between the pieces. Injecting a dependency transforms the receiver into something that depends on an abstract role rather than a concrete class.

Each of these design choices has costs, and it only makes sense to pay these costs if you also accrue some offsetting benefits. Design is thus about picking the right abstractions. If you choose well, your code will be expressive, understandable and flexible, and everyone will love both it and you. However, if you get the abstractions wrong, your code will be convoluted, confusing, and costly, and your programming peers will hate you.

Unfortunately, abstractions are hard, and even with the best of intentions, it’s easy to get them wrong. Well-meaning programmers tend to over anticipate abstractions, inferring them prematurely from incomplete information. Early abstractions are often not quite right and therefore they create a catch-22.[1] You can’t create the right abstraction until you fully understand the code, but the existence of the wrong abstraction may prevent you from ever doing so. This suggests that you should not reach for abstractions, but instead, you should resist them until they absolutely insist upon being created.

This book is about finding the right abstraction. This first chapter starts by peeling away the fog of complexity and defining what it means to write simple code.

1.1. Simplifying Code

The code you write should meet two often contradictory goals. It must remain concrete enough to be understood while simultaneously being abstract enough to allow for change.

short_bottle

Imagine a continuum with "most concrete" at one end and "most abstract" at the other. Code at the concrete end might be expressed as a single long procedure full of if statements. Code at the abstract end might consist of many classes, each with one method containing a single line of code.

The best solution for most problems lies not at the extremes of this continuum, but somewhere in the middle. There’s a sweet spot that represents the perfect compromise between comprehension and changeability, and it’s your job as a programmer to find it.

This section discusses four different solutions to the 99 Bottles of Beer problem. These solutions vary in complexity and thus illustrate different points along this continuum.

You must now make a decision. As you were forewarned in the preface, the best way to learn from this book is to work the exercises yourself. If you continue reading before solving the problem in your own way, your ideas will be contaminated by the code that follows. Therefore, if you plan to work along, go do the 99 Bottles exercise now. When you’re finished, you’ll be ready to examine the following four solutions.

1.1.1. Incomprehensibly Concise

Here’s the first of four different solutions to the 99 Bottles song.

Listing 1.1: Incomprehensibly Concise
 1 class Bottles
 2   def song
 3     verses(99, 0)
 4   end
 5
 6   def verses(hi, lo)
 7     hi.downto(lo).map {|n| verse(n) }.join("\n")
 8   end
 9
10   def verse(n)
11     "#{n == 0 ? 'No more' : n} bottle#{'s' if n != 1}" +
12     " of beer on the wall, " +
13     "#{n == 0 ? 'no more' : n} bottle#{'s' if n != 1} of beer.\n" +
14     "#{n > 0  ? "Take #{n > 1 ? 'one' : 'it'} down and pass it around"
15               : "Go to the store and buy some more"}, " +
16     "#{n-1 < 0 ? 99 : n-1 == 0 ? 'no more' : n-1} bottle#{'s' if n-1 != 1}"+
17     " of beer on the wall.\n"
18   end
19 end

This first solution embeds a great deal of logic into the verse string. The code above performs a neat trick. It manages to be concise to the point of incomprehensibility while simultaneously retaining loads of duplication. This code is hard to understand because it is inconsistent and duplicative, and because it contains hidden concepts that it does not name.

Consistency

The style of the conditionals is inconsistent. Most use the ternary form, as on line 11:

n == 0 ? 'No more' : n

Other statements are made conditional by adding a trailing if. Line 11 again provides an example:

's' if n != 1

Finally, there’s the ternary within a ternary on line 16, which is best left without comment:

n-1 < 0 ? 99 : n-1 == 0 ? 'no more' : n-1

Every time the style of the conditionals changes, the reader has to press a mental reset button and start thinking anew. Inconsistent styling makes code harder for humans to parse; it raises costs without providing benefits.

Duplication

The code duplicates both data and logic. Having multiple copies of the strings "of beer" and "on the wall" isn’t great, but at least string duplication is easy to see and understand. Logic, however, is harder to comprehend than data, and duplicated logic is doubly so. Of course, if you want to achieve maximum confusion, you can interpolate duplicated logic inside strings, as does the verse method above.

For example, "bottle" pluralization is done in three places. The code to do this is identical in two of the places, on Lines 11 and 13:

's' if n != 1

But later, on line 16, the pluralization logic is subtly different. Suddenly it’s not n that matters, but n-1:

's' if n-1 != 1

Duplication of logic suggests that there are concepts hidden in the code that are not yet visible because they haven’t been isolated and named. The need to sometimes say "bottle" and other times say "bottles" means something, and the need to sometimes use n and other times use n-1 means something else. The code gives no clue about what these meanings might be; you’re left to figure this out for yourself.

Names

The most obvious point to be made about the names in the verse method of Listing 1.1: Incomprehensibly Concise is that there aren’t any. The verse string contains embedded logic. Each bit of logic serves some purpose, and it is up to you to construct a mental map of what these purposes might be.

This code would be easier to understand if it did not place that burden upon you, the intrepid reader. The logic that’s hidden inside the verse string should be dispersed into methods, and verse should fill itself with values by sending messages.

Terminology: Method versus Message

A "method" is defined on an object, and contains behavior. In the previous example, the Bottles class defines a method named song.

A "message" is sent by an object to invoke behavior. In the aforementioned example, the song method sends the verses message to the implicit receiver self.

Therefore, methods are defined, and messages are sent.

The confusion between these terms comes about because it is common for the receiver of a message to define a method whose name exactly corresponds to that message. Consider the example above. The song method sends the verses message to self, which results in an invocation of the verses method. The fact that the message name and the method name are identical may make it seem as if the terms are synonymous.

They are not. Think of objects as black boxes. Methods are defined within a black box. Messages are passed between them. There are many ways for an object to cheerfully respond to a message for which it does not define a matching method. While it is common for message names to map directly to method names, there is no requirement that this be so.

Drawing a distinction between messages and methods improves your OO mindset. It allows you to isolate the intention of the sender from the implementation in the receiver. OO promises that if you send the right message, the correct behavior will occur, regardless of the names of the methods that eventually get invoked.

Creating a method requires identifying the code you’d like to extract and deciding on a method name. This, in turn, requires naming the concept, and naming things is just plain hard. In the case above, it’s especially hard. This code not only contains many hidden concepts, but those concepts are mixed together, conflated, such that their individual natures are obscured. Combining many ideas into a small section of code makes it difficult to isolate and name any single concept.

When you first write a piece of code, you obviously know what it does. Therefore, during initial development, the price you pay for poor names is relatively low. However, code is read many more times than it is written, and its ultimate cost is often very high and paid by someone else. Writing code is like writing a book; your efforts are for other readers. Although the struggle for good names is painful, it is worth the effort if you wish your efforts to survive to be read. Code clarity is built upon names.

Problems with consistency, duplication and naming conspire to make the code in Listing 1.1: Incomprehensibly Concise likely to be costly.

Note that the above assertion is, at this point, an unsupported opinion. The best way to judge code would be to compare its value to its cost, but unfortunately it’s hard to get good data. Judgments about code are therefore commonly reduced to individual opinion, and humans are not always in accord. There’s no perfect solution to this problem, but the Judging Code section, later in this chapter, suggests ways to acquire empirical data about the goodness of code.

Independent of all judgment about how well a bit of code is arranged, code is also charged with doing what it’s supposed to do now as well as being easy to alter so that it can do more later. While it’s difficult to get exact figures for value and cost, asking the following questions will give you insight into the potential expense of a bit of code:

  1. How difficult was it to write?

  2. How hard is it to understand?

  3. How expensive will it be to change?

The past ("was it") is a memory, the future ("will it be") is imaginary, but the present ("is it") is true right now. The very act of looking at a piece of code declares that you wish to understand it at this moment. Questions 1 and 3 above may or may not concern you, but question 2 always applies.

Code is easy to understand when it clearly reflects the problem it’s solving and thus openly exposes that problem’s domain. If Listing 1.1: Incomprehensibly Concise openly exposed the 99 Bottles domain, a brief glance at the code would answer these questions:

  1. How many verse variants are there?

  2. Which verses are most alike? In what way?

  3. Which verses are most different, and in what way?

  4. What is the rule to determine which verse comes next?

These questions reflect core concepts of the problem, yet none of their answers are apparent in this solution. The number of variants, the difference between the variants, and the algorithm for looping are distressingly obscure. This code does not reflect its domain, and therefore you can infer that it was difficult to write and will be a challenge to change. If you had to characterize the goal of the writer of Listing 1.1: Incomprehensibly Concise, you might suggest that their highest priority was brevity. Brevity may be the soul of wit, but it quickly becomes tedious in code.

Incomprehensible conciseness is clearly not the best solution for the 99 Bottles problem. It’s time to examine one that’s more verbose.

1.1.2. Speculatively General

This next solution errs in a different direction. It does many things well but can’t resist indulging in unnecessary complexity. Have a look at the code below:

Listing 1.2: Speculatively General
 1 class Bottles
 2   NoMore = lambda do |verse|
 3     "No more bottles of beer on the wall, " +
 4     "no more bottles of beer.\n" +
 5     "Go to the store and buy some more, " +
 6     "99 bottles of beer on the wall.\n"
 7   end
 8
 9   LastOne = lambda do |verse|
10     "1 bottle of beer on the wall, " +
11     "1 bottle of beer.\n" +
12     "Take it down and pass it around, " +
13     "no more bottles of beer on the wall.\n"
14   end
15
16   Penultimate = lambda do |verse|
17     "2 bottles of beer on the wall, " +
18     "2 bottles of beer.\n" +
19     "Take one down and pass it around, " +
20     "1 bottle of beer on the wall.\n"
21   end
22
23   Default = lambda do |verse|
24     "#{verse.number} bottles of beer on the wall, " +
25     "#{verse.number} bottles of beer.\n" +
26     "Take one down and pass it around, " +
27     "#{verse.number - 1} bottles of beer on the wall.\n"
28   end
29
30   def song
31     verses(99, 0)
32   end
33
34   def verses(finish, start)
35     (finish).downto(start).map {|verse_number|
36       verse(verse_number) }.join("\n")
37   end
38
39   def verse(number)
40     verse_for(number).text
41   end
42
43   def verse_for(number)
44     case number
45     when 0 then Verse.new(number, &NoMore)
46     when 1 then Verse.new(number, &LastOne)
47     when 2 then Verse.new(number, &Penultimate)
48     else        Verse.new(number, &Default)
49     end
50   end
51 end
52
53 class Verse
54   attr_reader :number
55   def initialize(number, &lyrics)
56     @number = number
57     @lyrics = lyrics
58   end
59
60   def text
61     @lyrics.call self
62   end
63 end

If you find this code less than clear, you’re not alone. It’s confusing enough to warrant an explanation, but because the explanation naturally reflects the code, it’s confusing in its own right. Don’t worry if the following paragraphs muddle things further. Their purpose is to help you appreciate the complexity rather than understand the details.

The code above first defines four lambdas (lines 2, 9, 16, and 23) and saves them as constants (NoMore, LastOne, Penultimate, and Default). Notice that each lambda takes argument verse but only Default actually refers to it. The code then defines the song and verses methods. Next comes the verse method, which passes the current verse number to verse_for and sends text to the result (line 40). This is the line of code that returns the correct string for a verse of the song.

Things get more interesting in verse_for, but before pondering that method, look ahead to the Verse class on line 53. Verse instances are initialized with two arguments, number and &lyrics, and they respond to two messages, number and text. The number method simply returns the verse number that was passed during initialize. The text method is more complicated; it sends call to lyrics, passing self as an argument.

If you now return to verse_for and examine lines 45-48, you can see that when instances of Verse are created, the number argument is a verse number and the &lyrics argument is one of the lambdas. The verse_for method gets invoked for every verse of the song, and therefore, one hundred instances of Verse will be created, each containing a verse number and the lambda that corresponds to that number.

To summarize, sending verse(number) to an instance of Bottles invokes verse_for(number), which uses the value of number to select the correct lambda on which to create and return an instance of Verse. The verse method then sends text to the returned Verse, which in turn sends call to the lambda, passing self as an argument. This invokes the lambda, which may or may not actually use the argument that was passed. Regardless, executing the lambda returns a string that contains the lyrics for one verse of the song.

You can be forgiven if you suspect that this is unduly complicated. It is. However, it’s curious that despite this complexity, Listing 1.2: Speculatively General does a much better job than Listing 1.1: Incomprehensibly Concise of answering the domain questions:

  1. How many verse variants are there?
    There are four verse variants (they start on lines 2, 9, 16 and 23 above).

  2. Which verses are most alike? In what way?
    Verses 3-99 are most alike (as evidenced by the fact that all are produced by the Default variant).

  3. Which verses are most different? In what way?
    Verses 0, 1 and 2 are clearly different from 3-99, although it’s not obvious in what way.

  4. What is the rule to determine which verse should be sung next?
    Buried deep within the NoMore lambda is a hard-coded "99," which might cause one to infer that verse 99 follows verse 0.

This solution’s answers to the first three questions above are quite an improvement over those of Listing 1.1: Incomprehensibly Concise. However, all is not perfect; it still does poorly on the value/cost questions:

  1. How difficult was it to write?
    There’s far more code here than is needed to pass the tests. This unnecessary code took time to write.

  2. How hard is it to understand?
    The many levels of indirection are confusing. Their existence implies necessity, but you could study this code for a long time without discerning why they are needed.

  3. How expensive will it be to change?
    The mere fact that indirection exists suggests that it’s important. You may feel compelled to understand its purpose before making changes.

As you can see from these answers, this solution does a good job of exposing core concepts, but does a bad job of being worth its cost. This good job/bad job divide reflects a fundamental fissure in the code.

Aside from the song and verses methods, the code does two basic things. First, it defines templates for each kind of verse (lines 2-28), and second, it chooses the appropriate template for a specific verse number and renders that verse’s lyrics (lines 39-63).

Notice that the verse templates contain all of the information needed to answer the domain questions. There are four templates, and therefore, there must be four verse variants. The Default template handles verses 3 through 99, and these verses are clearly most alike. Verses 0, 1, and 2 have their own special templates, so each must be unique. The four templates (if you ignore the fact that they’re stored in lambdas) are very straightforward, which makes answering the domain questions easy.

But it’s not the templates that are costly; it’s the code that chooses a template and renders the lyrics for a verse. This choosing/rendering code is overly complicated, and while complexity is not forbidden, it is required to pay its own way. In this case, complexity does not.

Instead of 1) defining a lambda to hold a template, 2) creating a new object to hold the lambda, and 3) invoking the lambda with self as an argument, the code could merely have put each of the four templates into a method and then used the case statement on lines 45-48 to invoke the correct one. The lambdas aren’t needed, nor is the Verse class, and the route between them is a series of pointless jumps through needless hoops.

Given the obvious superiority of this alternative implementation, how on earth did the "calling a lambda" variant come about? At this remove, it’s difficult to be certain of the motivation, but the code gives the impression that its author feared that the logic for selecting or invoking a template would someday need to change, and so added levels of indirection in a misguided attempt to protect against that day.

They did not succeed. Relative to the alternative, Listing 1.2: Speculatively General is harder to understand without being easier to change. The additional complexity does not pay off. The author may have acted with the best of intentions, but somewhere along the way, their commitment to the plan overcame good sense.

Programmers love clever code. It’s like a neat card trick that uses sleight of hand and misdirection to make magic. Writing it, or suddenly understanding it, supplies a little burst of appreciative pleasure. However, this very pleasure distracts the eye and seduces the mind, and allows cleverness to worm its way into inappropriate places.

You must resist being clever for its own sake. If you are capable of conceiving and implementing a solution as complex as Listing 1.2: Speculatively General, it is incumbent upon you to accept the harder task and write simpler code.

Neither Listing 1.2: Speculatively General nor Listing 1.1: Incomprehensibly Concise is the best solution for 99 Bottles. Perhaps, as was true for porridge, the third solution will be just right.[2]

1.1.3. Concretely Abstract

This solution valiantly attempts to name the concepts in the domain. Here’s the code:

Listing 1.3: Concretely Abstract
 1 class Bottles
 2
 3   def song
 4     verses(99, 0)
 5   end
 6
 7   def verses(bottles_at_start, bottles_at_end)
 8     bottles_at_start.downto(bottles_at_end).map do |bottles|
 9       verse(bottles)
10     end.join("\n")
11   end
12
13   def verse(bottles)
14     Round.new(bottles).to_s
15   end
16 end
17
18 class Round
19   attr_reader :bottles
20   def initialize(bottles)
21     @bottles = bottles
22   end
23
24   def to_s
25     challenge + response
26   end
27
28   def challenge
29     bottles_of_beer.capitalize + " " + on_wall + ", " +
30     bottles_of_beer + ".\n"
31   end
32
33   def response
34     go_to_the_store_or_take_one_down + ", " +
35     bottles_of_beer + " " + on_wall + ".\n"
36   end
37
38   def bottles_of_beer
39     "#{anglicized_bottle_count} #{pluralized_bottle_form} of #{beer}"
40   end
41
42   def beer
43     "beer"
44   end
45
46   def on_wall
47     "on the wall"
48   end
49
50   def pluralized_bottle_form
51     last_beer? ? "bottle" : "bottles"
52   end
53
54   def anglicized_bottle_count
55     all_out? ? "no more" : bottles.to_s
56   end
57
58   def go_to_the_store_or_take_one_down
59     if all_out?
60       @bottles = 99
61       buy_new_beer
62     else
63       lyrics = drink_beer
64       @bottles -= 1
65       lyrics
66     end
67   end
68
69   def buy_new_beer
70     "Go to the store and buy some more"
71   end
72
73   def drink_beer
74     "Take #{it_or_one} down and pass it around"
75   end
76
77   def it_or_one
78     last_beer? ? "it" : "one"
79   end
80
81   def all_out?
82     bottles.zero?
83   end
84
85   def last_beer?
86     bottles == 1
87   end
88 end

This solution is characterized by having many small methods. This is normally a good thing, but somehow in this case it’s gone badly wrong. Have a look at how this solution does on the domain questions:

  1. How many verse variants are there?
    It’s almost impossible to tell.

  2. Which verses are most alike? In what way?
    Ditto.

  3. Which verses are most different? In what way?
    Ditto.

  4. What is the rule to determine which verse should be sung next?
    Ditto.

It fares no better on the value/cost questions.

  1. How difficult was it to write?
    Difficult. This clearly took a fair amount of thought and time.

  2. How hard is it to understand?
    The individual methods are easy to understand, but despite this, it’s tough to get a sense of the entire song. The parts don’t seem to add up to the whole.

  3. How expensive will it be to change?
    While changing the code inside any individual method is cheap, in many cases, one simple change will cascade and force many other changes.

It’s obvious that the author of this code was committed to doing the right thing, and that they carefully followed the Red, Green, Refactor style of writing code. The various strings that make up the song are never repeated—it looks as though these strings were refactored into separate methods at the first sign of duplication.

The code is DRY, and DRYing out code should save you money. DRY promises that if you put a chunk of code into a method and then invoke that method instead of duplicating the code, you will save money later if the behavior of that chunk changes. When you invoke a method instead of implementing behavior, you add a level of indirection. This indirection makes the details of what’s happening harder to understand, but DRY promises that in return, your code will be easier to change.

The Don’t Repeat Yourself principle, like all principles of object-oriented design, is completely true. However, despite that fact that the code above is DRY, there are many ways in which it’s expensive to change.

One of many possible examples is the beer method on line 42. This method returns the string "beer," which occurs nowhere else in the code. To change the drink to "Kool-Aid," you need only change line 43 to return "Kool-Aid" instead of "beer." As this one small change is all that’s needed to meet the "Kool-Aid" requirement, on the surface, DRY has fulfilled its promise. However, step back a minute and consider the resulting method:

def beer
  "Kool-Aid"
end

Or ponder some of the other method names:

def bottles_of_beer
def buy_new_beer
def drink_beer
def last_beer?

In light of the "Kool-Aid" change, these names are terribly confusing. These method names no longer make sense where they are defined, and they are totally misleading in places where they are used. To mitigate this confusion, you not only have to change "beer" to "Kool-Aid" inside this method, but you also have to make the same change to every method name that includes the word "beer" and then again to every sender of one of those messages.

This small change in requirements forces a change in many places, which is exactly the problem DRY promises to avoid. The fault here, however, lies not with the DRY principle, but with the names of the methods.

When you choose beer as the name of a method that returns the string "beer," you’ve named the method after what it does right now. Unfortunately, when you name a method after its current implementation, you can never change that internal implementation without ruining the method name.

You should name methods not after what they do, but after what they mean, what they represent in the context of your domain. If you were to ask your customer what "beer" is in the context of the 99 Bottles song, they would not answer "Beer is the beer," they would say something like "Beer is the thing you drink" or "Beer is the beverage."

"Beer" and "Kool-Aid" are kinds of beverages; the word "beverage" is one level of abstraction higher than "beer." Naming the method at this slightly higher level of abstraction isolates the code from changes in the implementation details. If you choose beverage for the method name, going from:

def beverage
  "beer"
end

to:

def beverage
 "Kool-Aid"
end

makes perfect sense and requires no other change.

Listing 1.3: Concretely Abstract contains many small methods, and the strings that make up the song are completely DRY. These two things exert a force for good that should result in code that’s easy to change. However, in Concretely Abstract, this force is overcome by the high cost of dealing with methods that are named at the wrong level of abstraction. These method names raise the cost of change.

Therefore, one lesson to be gleaned from this solution is that you should name methods after the concept they represent rather than how they currently behave. However, notice that even if you edited the code to improve every method name, this code still isn’t quite right.

Changing the name of the beer method to beverage makes it easy to replace the string "beer" with the string "Kool-Aid" but does nothing to improve this code’s score on the domain questions. The problem goes far deeper than having methods that are named at the wrong level of abstraction. It’s not just the names that are wrong, but the methods themselves. Many methods in this code represent the wrong abstractions.

The problem of identifying the right abstractions is explored in future chapters, but meanwhile it’s time to consider one more solution.

1.1.4. Shameless Green

None of the solutions shown thus far do very well on the value/cost questions. Incomprehensibly Concise cares only for terseness. Speculatively General tries for extensibility but achieves unwarranted complexity. Concretely Abstract's heart is in the right place but can’t get its feet out of the mud.

Solving the 99 Bottles problem in any of these ways requires more effort than is necessary and results in more complexity than is needed. These solutions cost too much; they do too many of the wrong things and too few of the right.

Speculatively General and Concretely Abstract were both written with an eye toward reducing future costs, and it is distressing to see good intentions fail so spectacularly. It’s a particular shame that the abstractions are wrong because, given the opportunity to do so, the code is completely willing to reveal abstractions that are right. The failure here is not bad intention—it’s insufficient patience.

This next example is patient and so provides an antidote for all that has come before. The following solution is known as Shameless Green:

Listing 1.4: Shameless Green
 1 class Bottles
 2
 3   def song
 4     verses(99, 0)
 5   end
 6
 7   def verses(starting, ending)
 8     starting.downto(ending).map {|i| verse(i)}.join("\n")
 9   end
10
11   def verse(number)
12     case number
13     when 0
14       "No more bottles of beer on the wall, " +
15       "no more bottles of beer.\n" +
16       "Go to the store and buy some more, " +
17       "99 bottles of beer on the wall.\n"
18     when 1
19       "1 bottle of beer on the wall, " +
20       "1 bottle of beer.\n" +
21       "Take it down and pass it around, " +
22       "no more bottles of beer on the wall.\n"
23     when 2
24       "2 bottles of beer on the wall, " +
25       "2 bottles of beer.\n" +
26       "Take one down and pass it around, " +
27       "1 bottle of beer on the wall.\n"
28     else
29       "#{number} bottles of beer on the wall, " +
30       "#{number} bottles of beer.\n" +
31       "Take one down and pass it around, " +
32       "#{number-1} bottles of beer on the wall.\n"
33     end
34   end
35
36 end

The most immediately apparent quality of this code is how very simple it is. There’s nothing tricky here. The code is gratifyingly easy to comprehend. Not only that, despite its lack of complexity this solution does extremely well on the domain questions.

  1. How many verse variants are there?
    Clearly, four.

  2. Which verses are most alike? In what way?
    3-99, where only the verse number varies.

  3. Which verses are most different? In what way?
    0, 1 and 2 are different from 3-99, though figuring out how requires parsing strings with your eyes.

  4. What is the rule to determine which verse should be sung next?
    This is still not explicit. The 0 verse contains a deeply buried, hard-coded 99.

These answers are identical to those achieved by Listing 1.2: Speculatively General. Shameless Green and Speculatively General differ, though, in how they compare on the value/cost questions. Shameless Green is a substantial improvement.

  1. How difficult was this to write?
    It was easy to write.

  2. How hard is it to understand?
    It is easy to understand.

  3. How expensive will it be to change?
    It will be cheap to change. Even though the verse strings are duplicated, if one changes it’s easy to keep the others in sync.

By the criteria that have been established, Shameless Green is clearly the best solution, yet almost no one writes it. It feels embarrassingly easy, and is missing many qualities that you expect in good code. It duplicates strings and contains few named abstractions.

Most programmers have a powerful urge to do more, but sometimes it’s best to stop right here. If you were charged with writing the code to produce the lyrics to the 99 Bottles song, it is difficult to imagine fulfilling that requirement in a more cost-effective way.

The Shameless Green solution is disturbing because, although the code is easy to understand, it makes no provision for change. In this particular case, the song is so unlikely to change that betting that the code is "good enough" should pay off. However, if you pretend that this problem is a proxy for a real, production application, the proper course of action is not so clear.

When you DRY out duplication or create a method to name a bit of code, you add levels of indirection that make it more abstract. In theory these abstractions make code easier to understand and change, but in practice they often achieve the opposite. One of the biggest challenges of design is knowing when to stop, and deciding well requires making judgments about code.

1.2. Judging Code

You now have access to five different solutions to the 99 Bottles of Beer problem; the four listed in the preceding section and the one you wrote yourself.

Which is best?

You likely have an opinion on this question—one which, granted, may have been swayed by the commentary above. However, independent of that gentle influence, the sum of your experiences and expectations predispose you to assess the goodness of code in your own unique way.

You judge code constantly. Writing code requires making choices; the choices you make reflect personal, internalized criteria. You intend to write "good" code and if, in your estimation, you’ve written "bad" code, you are clearly aware that you’ve done so. Regardless of how implicit, unachievable, or unhelpful they may be, you already have rules about code.

While having standards of any sort is a virtue, the chance of achieving your standards is improved if they are explicit and quantifiable. Answering the question "What makes code good?" thus requires defining goodness in concrete and actionable ways.

This is harder than one might think.

1.2.1. Evaluating Code Based on Opinion

You’d think that by now, there would exist a universally agreed upon definition of good code that could unambiguously guide our programming behavior. The unfortunate truth is that, not only are there a multitude of definitions, but that these definitions generally describe how code looks when it’s done without providing any concrete guidance about how to get there.

Just as "Everybody complains about the weather but nobody does anything about it",[3] everyone has an opinion about what good code looks like, but those opinions usually don’t tell us what action to take to create it. Robert "Uncle Bob" Martin opens his book Clean Code by asking a number of luminaries for a definition of clean code. Their thoughtful answers could describe art or wine as easily as software.

I like my code to be elegant and efficient.

— Bjarne Stroustrup
inventor of C++

Clean code is …​ full of crisp abstractions …​

Clean code was written by someone who cares.

— Michael Feathers
author of Working Effectively with Legacy Code

Your own definition probably follows along these same lines. Any pile of code can be made to work; good code not only works, but is also simple, understandable, expressive and changeable.

The problem with these definitions is that although they accurately describe how good code looks once it’s written, they give no help with achieving this state, and provide little guidance for choosing between competing solutions. The attributes they use to describe good code are qualitative, not quantitative.

What does it mean to be "elegant?" What makes an abstraction "crisp?" Despite the fact that these definitions are undeniably correct, none are precise in a measurable way. This lack of precision means that well-meaning programmers can hold identically high standards and still have significant disagreements about relative goodness. Thus, we argue fruitlessly about code.

Since form follows function, good code can also be defined simply, and somewhat circularly, as that which provides the highest value for the lowest cost. Our sense of elegance, expressiveness and simplicity is an outgrowth of our experiences when reading and modifying code. Code that is easy to understand and a pleasure to extend naturally feels simple and elegant.

If you could identify and measure these qualities, you could seek after them diligently and deliberately. Therefore, although your opinions about code matter, you would be well served by facts.

1.2.2. Evaluating Code Based on Facts

A "metric" is a measure of some quality of code. Metrics are, obviously, created by people, so one could argue that they merely express one individual’s opinion. That assertion, however, vastly understates their worth. Measures that rise to become metrics are backed by research that has stood the test of time. They’ve been scrutinized by many people over many years. You can think of metrics as crowd-sourced opinions about the quality of code.

If you apply the same metric to two different pieces of source code, you can then compare that code (at least in terms of what the metric measures) by comparing the resulting numbers. While it’s possible to disagree with the premise of a specific metric, and to insist that the thing it measures isn’t useful, the rules of mathematics require all to concede that the numbers produced by metrics are facts.

It would be extremely handy to have agreed-upon facts with which to compare code. In search of these facts, this section examines three different metrics: Source Lines of Code, Cyclomatic Complexity, and ABC.

Source Lines of Code

In the days of yore, the desire for reproducible, reliable information about the cost of developing applications led to the creation of a metric known simply as Source Lines of Code (SLOC, sometimes shortened to just LOC). This one number has been used to predict the total effort needed to develop software, to measure the productivity of those who write it, and to predict the cost of maintaining it.

The metric has the advantage of being easily garnered and reproduced, but suffers from many flaws.

Using SLOC to predict the development effort needed for a new project is done by counting the SLOC of existing projects for which total effort is known, deciding which of those existing projects the new project most resembles, and then running a cost estimation model to make the prediction. If the person doing the estimating is correct about which existing project(s) the new project most closely resembles, this prediction may be accurate.

Measuring programmer productivity by counting lines of code assumes that all programmers write equally efficient code. However, novice programmers are often far more verbose than those with more experience. Despite the fact that novices write more code to produce less function, by this metric, they can seem more productive.

While the cost of maintenance is related to the size of an application, the way in which code is organized also matters. It is cheaper to maintain a well-designed application than it is to maintain a pile of spaghetti-code.

SLOC numbers reflect code volume, and while it’s useful for some purposes, knowing SLOC alone is not enough to predict code quality.

Cyclomatic Complexity

In 1976, Thomas J. McCabe, Sr. published A Complexity Measure, in which he asserted:

What is needed is a mathematical technique that will provide a quantitative basis for modularization and allow us to identify software modules that will be difficult to test or maintain.

A "mathematical technique" to identify code that is "difficult to test or maintain"--this could be the perfect tool for assessing code. In his paper, McCabe describes his Cyclomatic Complexity metric, an algorithm that counts the number of unique execution paths through a body of source code. Think of this algorithm as a little machine that ponders your code and then maps out all the possible routes through every combination of every branch of every conditional. A method with many deeply nested conditionals would score very high, while a method with no conditionals at all would score 0.

Cyclomatic complexity does not predict application development time nor does it measure programmer productivity. Its desire to identify code that is difficult to test or maintain aims it directly at code quality.

Cyclomatic complexity can be used in several ways. First, you can use it to compare code. If you have two variants of the same method, you can choose between them based on their cyclomatic complexity. Lower scores are better and so by extension the code with the lowest score is the best.

Next, you can use it to limit overall complexity. You can set standards for how high a score you’re willing to accept, and require explicit dispensation before allowing code to exceed this maximum.

Finally, you can use it to determine if you’ve written enough tests. Cyclomatic complexity tells you the minimum number of tests needed to cover all of the logic in the code. If you have fewer tests than cyclomatic complexity recommends, you don’t have complete test coverage.

Cyclomatic complexity sounds great, and it’s easy to see that it could be useful, but it views the world of code through a narrow lens.

Assignments, Branches and Conditions (ABC) Metric

The problem with cyclomatic complexity is that it doesn’t take everything into account. Code does more than just evaluate conditions; it also assigns values to variables and sends messages. These things add up, and as you do more and more of each, your code becomes increasingly difficult to understand.

In 1997, twenty-one years after the unveiling of cyclomatic complexity, Jerry Fitzpatrick published Applying the ABC Metric to C, C++, and Java, in which he describes a metric that does consider more than conditionals. His ABC stands for assignments, branches and conditions, where:

  • Assignments is a count of variable assignments.

  • Branches counts not branches of an if statement (as one could forgivably infer) but branches of control, i.e., it counts function calls or message sends.

  • Conditions counts conditional logic.

Fitzpatrick describes the ABC metric as a measure of size, as if ABC is a more sophisticated version of SLOC. This is his metric so he certainly gets to say what it represents, but you will not go wrong if you think of ABC scores as reflecting cognitive as opposed to physical size. High ABC numbers indicate code that takes up a lot of mental space. In this sense, ABC is a measure of complexity. Highly complex code is difficult to understand and change, therefore ABC scores are a proxy for code quality.

The most popular tool for generating ABC scores for Ruby code is Ryan Davis’s Flog. Flog is more ABC-ish than strictly ABC. Davis has specifically tuned it to reflect his considered opinion about what makes for good Ruby code. If you’re interested in the ways in which Flog differs from classic ABC, you can find out by simply browsing the source code, but you don’t have to delve into the gory details to benefit from running this metric against your own code.

Flog scores provide an independent perspective that may challenge your ideas about complexity and design. High scores suggest that code will be hard to test and expensive to maintain. If you believe your code to be simple but Flog says otherwise, you should think again.

Every example in this book will eventually be run through Flog and the relative scores will be compared and discussed. Although Flog scores aren’t everything, they are very definitely a useful something.

Metrics are fallible but human opinion is no more precise. Checking metrics regularly will keep you humble and improve your code.

1.2.3. Comparing Solutions

Now that you have some insight into code metrics, it’s time to examine some scores for the code examples shown in this chapter.

The following table shows each solution’s total lines of code (SLOC), total Flog score, and worst scoring "bit."

Table 1.1: Flog Scores
Solution SLOC Flog Total Flog Worst Bit

Listing 1.1: Incomprehensibly Concise

19

42.5

#verse

36.2

Listing 1.2: Speculatively General

63

50.6

lambdas

26.5

Listing 1.3: Concretely Abstract

92

61.9

#challenge

14.4

Listing 1.4: Shameless Green

34

25.6

#verse

19.3

In most cases, the worst scoring bit is a method, but in the case of Listing 1.2: Speculatively General, the worst score is earned by the group of lambdas that are defined as constants.

The following chart makes the numbers easier to compare. Although SLOC is not related to Flog score, the values are in similar ranges so it’s convenient to display everything on the same chart.

c1 examples flog
Figure 1.1: Flog Score Chart

This graph exposes a number of interesting patterns.

First, it is unsurprising that solutions with more lines of code tend to Flog to higher scores, i.e., that total Flog score generally rises in tandem with SLOC. Shameless Green is the notable exception—it is second lowest in SLOC but lowest in total Flog score by a considerable margin.

Next, Concretely Abstract scores at the extreme of every dimension. It contains the most code, Flogs to the highest total, and has the best, if you will, "Worst Bit" Flog score. The total Flog score is reasonable in light of the total lines of code, and the low Worst Bit score indicates that the methods are small and focused.

These metrics suggest that Concretely Abstract contains good code, but as you may recall, it does not. Metrics clearly don’t tell the whole story. The problem here is that although the code is nicely arranged, it contains names that are at the wrong level of abstraction. These names make Concretely Abstract expensive to change despite its orderly arrangement. The metrics overstate the quality of Concretely Abstract because they approve of the code structure, but they are unable to recognize the poor names.

Speculatively General is the second longest solution, and has the second highest Flog and Worst Bit scores. Its length and complexity reflect an attempt to arrange the code such that certain imagined changes will be easy, i.e. to guess the future. These guesses are unlikely to pay off and relative to the other solutions, Speculatively General is both longer and more complex than necessary.

The final two examples, Incomprehensibly Concise and Shameless Green, are similar in that most of their complexity is contained in a single method. In each case, the score of their worst bit is very near to their total score. This reflects the fact that both are basically procedures and that neither has attempted to identify abstractions.

Despite this similarity, if you compare their SLOC scores to their total Flog scores, you’ll see that they are also very different. Incomprehensibly Concise has a high Flog score relative to SLOC, Shameless Green has the opposite. Incomprehensibly Concise packs a lot of complexity into a few lines of code. Shameless Green is biased in the other direction; it has more code but is much simpler.

Overall, Shameless Green has the lowest total Flog score, the second lowest SLOC, and the second lowest Worst Bit score. If your goal is to write straightforward code, these metrics point you toward Shameless Green.

1.3. Summary

As programmers grow, they get better at solving challenging problems, and become comfortable with complexity. This higher level of comfort sometimes leads to the belief that complexity is inevitable, as if it’s the natural, inescapable state of all finished code. However, there’s something beyond complexity—a higher level of simplicity. Infinitely experienced programmers do not write infinitely complex code; they write code that’s blindingly simple.

This chapter examined four possible solutions to the 99 Bottles problem as a prelude to defining what it means to write simple code. It used metrics as a starting point, injected a bit of common sense, and landed on Shameless Green.

Shameless Green is defined as the solution which quickly reaches green while prioritizing understandability over changeability. It uses tests to drive comprehension, and patiently accumulates concrete examples while awaiting insight into underlying abstractions. It doesn’t dispute that DRY is good, rather it believes that it is cheaper to manage temporary duplication than to recover from incorrect abstractions.

Writing Shameless Green is fast, and the resulting code might be "good enough." Most programmers find it embarrassingly duplicative, and the code is certainly not very object-oriented. However, if nothing ever changes, the most cost-effective strategy is to deploy this code and walk away.

The challenge comes when a change request arrives. Code that’s good enough when nothing ever changes may well be code that’s not good enough when things do. Chapter 3 introduces just such a change, and in that chapter you’ll begin improving the code. Before moving on, however, it’s time to take a step back, and learn how to test-drive Shameless Green.

Appendix A: Prerequisites

A.1. Ruby

The code is compatible with any Ruby version starting at 1.9. Check which version of Ruby you have with the following command:

ruby --version

If you don’t have Ruby 1.9 or higher installed follow the instructions on ruby-lang.org to install it.

A.2. Minitest

The code examples include a Minitest test suite. To check which versions of Minitest you have, use the gem list command.

gem list minitest

If Minitest is not installed, or if none of the versions listed are in the 5.x series, install it with gem install.

gem install minitest --version "~> 5.4"

Appendix B: Initial Exercise

B.1. Getting the exercise

The code in this book is on Github. The simplest way to get the exercise is to clone the repository and check out the correct branch, as follows:

git clone --depth=1 --branch=exercise https://github.com/sandimetz/99bottles.git

The directory structure for the exercise should look like this:

├── lib
│   └── bottles.rb
└── test
    └── bottles_test.rb

If you don’t have git installed, create the expected directory structure, and then copy and paste the contents of the raw file on GitHub into bottles_test.rb.

Finally, if you don’t have an internet connection, you can find the full code listing for the test suite below, in the Test Suite section.

B.2. Doing the exercise

The test suite and exercise are written in Ruby. If you’re unfamiliar with the language, ruby-lang.org has installation instructions, a gentle tutorial (Ruby in Twenty Minutes), and further references.

To run the test suite, invoke Ruby with the path to the test file.

ruby test/bottles_test.rb

The test suite contains one failing test, and many skipped tests. Your goal is to write code that passes all of the tests. Follow this protocol:

  • run the tests and examine the failure

  • write only enough code to pass the failing test

  • unskip the next test (this simulates writing it yourself)

Repeat the above until no tests is skipped, and you’ve written code to pass each one.

Work on this task for 30 minutes. The vast majority of folks do not finish in 30 minutes, but it’s useful, for later comparison purposes, to record how far you got. Even if you can’t force yourself to stop at that point, take a break at 30 minutes and save your code.

B.3. Test Suite

  1 class BottlesTest < Minitest::Test
  2   def test_the_first_verse
  3     expected = <<-VERSE
  4 99 bottles of beer on the wall, 99 bottles of beer.
  5 Take one down and pass it around, 98 bottles of beer on the wall.
  6 VERSE
  7     assert_equal expected, ::Bottles.new.verse(99)
  8   end
  9
 10   def test_another_verse
 11     skip
 12     expected = <<-VERSE
 13 89 bottles of beer on the wall, 89 bottles of beer.
 14 Take one down and pass it around, 88 bottles of beer on the wall.
 15 VERSE
 16     assert_equal expected, ::Bottles.new.verse(89)
 17   end
 18
 19   def test_verse_2
 20     skip
 21     expected = <<-VERSE
 22 2 bottles of beer on the wall, 2 bottles of beer.
 23 Take one down and pass it around, 1 bottle of beer on the wall.
 24     VERSE
 25     assert_equal expected, ::Bottles.new.verse(2)
 26   end
 27
 28   def test_verse_1
 29     skip
 30     expected = <<-VERSE
 31 1 bottle of beer on the wall, 1 bottle of beer.
 32 Take it down and pass it around, no more bottles of beer on the wall.
 33     VERSE
 34     assert_equal expected, ::Bottles.new.verse(1)
 35   end
 36
 37   def test_verse_0
 38     skip
 39     expected = <<-VERSE
 40 No more bottles of beer on the wall, no more bottles of beer.
 41 Go to the store and buy some more, 99 bottles of beer on the wall.
 42     VERSE
 43     assert_equal expected, ::Bottles.new.verse(0)
 44   end
 45
 46   def test_a_couple_verses
 47     skip
 48     expected = <<-VERSES
 49 99 bottles of beer on the wall, 99 bottles of beer.
 50 Take one down and pass it around, 98 bottles of beer on the wall.
 51
 52 98 bottles of beer on the wall, 98 bottles of beer.
 53 Take one down and pass it around, 97 bottles of beer on the wall.
 54 VERSES
 55     assert_equal expected, ::Bottles.new.verses(99, 98)
 56   end
 57
 58   def test_a_few_verses
 59     skip
 60     expected = <<-VERSES
 61 2 bottles of beer on the wall, 2 bottles of beer.
 62 Take one down and pass it around, 1 bottle of beer on the wall.
 63
 64 1 bottle of beer on the wall, 1 bottle of beer.
 65 Take it down and pass it around, no more bottles of beer on the wall.
 66
 67 No more bottles of beer on the wall, no more bottles of beer.
 68 Go to the store and buy some more, 99 bottles of beer on the wall.
 69 VERSES
 70     assert_equal expected, ::Bottles.new.verses(2, 0)
 71   end
 72
 73   def test_the_whole_song
 74     skip
 75     expected = <<-SONG
 76 99 bottles of beer on the wall, 99 bottles of beer.
 77 Take one down and pass it around, 98 bottles of beer on the wall.
 78
 79 98 bottles of beer on the wall, 98 bottles of beer.
 80 Take one down and pass it around, 97 bottles of beer on the wall.
 81
 82 97 bottles of beer on the wall, 97 bottles of beer.
 83 Take one down and pass it around, 96 bottles of beer on the wall.
 84
 85 96 bottles of beer on the wall, 96 bottles of beer.
 86 Take one down and pass it around, 95 bottles of beer on the wall.
 87
 88 95 bottles of beer on the wall, 95 bottles of beer.
 89 Take one down and pass it around, 94 bottles of beer on the wall.
 90
 91 94 bottles of beer on the wall, 94 bottles of beer.
 92 Take one down and pass it around, 93 bottles of beer on the wall.
 93
 94 93 bottles of beer on the wall, 93 bottles of beer.
 95 Take one down and pass it around, 92 bottles of beer on the wall.
 96
 97 92 bottles of beer on the wall, 92 bottles of beer.
 98 Take one down and pass it around, 91 bottles of beer on the wall.
 99
100 91 bottles of beer on the wall, 91 bottles of beer.
101 Take one down and pass it around, 90 bottles of beer on the wall.
102
103 90 bottles of beer on the wall, 90 bottles of beer.
104 Take one down and pass it around, 89 bottles of beer on the wall.
105
106 89 bottles of beer on the wall, 89 bottles of beer.
107 Take one down and pass it around, 88 bottles of beer on the wall.
108
109 88 bottles of beer on the wall, 88 bottles of beer.
110 Take one down and pass it around, 87 bottles of beer on the wall.
111
112 87 bottles of beer on the wall, 87 bottles of beer.
113 Take one down and pass it around, 86 bottles of beer on the wall.
114
115 86 bottles of beer on the wall, 86 bottles of beer.
116 Take one down and pass it around, 85 bottles of beer on the wall.
117
118 85 bottles of beer on the wall, 85 bottles of beer.
119 Take one down and pass it around, 84 bottles of beer on the wall.
120
121 84 bottles of beer on the wall, 84 bottles of beer.
122 Take one down and pass it around, 83 bottles of beer on the wall.
123
124 83 bottles of beer on the wall, 83 bottles of beer.
125 Take one down and pass it around, 82 bottles of beer on the wall.
126
127 82 bottles of beer on the wall, 82 bottles of beer.
128 Take one down and pass it around, 81 bottles of beer on the wall.
129
130 81 bottles of beer on the wall, 81 bottles of beer.
131 Take one down and pass it around, 80 bottles of beer on the wall.
132
133 80 bottles of beer on the wall, 80 bottles of beer.
134 Take one down and pass it around, 79 bottles of beer on the wall.
135
136 79 bottles of beer on the wall, 79 bottles of beer.
137 Take one down and pass it around, 78 bottles of beer on the wall.
138
139 78 bottles of beer on the wall, 78 bottles of beer.
140 Take one down and pass it around, 77 bottles of beer on the wall.
141
142 77 bottles of beer on the wall, 77 bottles of beer.
143 Take one down and pass it around, 76 bottles of beer on the wall.
144
145 76 bottles of beer on the wall, 76 bottles of beer.
146 Take one down and pass it around, 75 bottles of beer on the wall.
147
148 75 bottles of beer on the wall, 75 bottles of beer.
149 Take one down and pass it around, 74 bottles of beer on the wall.
150
151 74 bottles of beer on the wall, 74 bottles of beer.
152 Take one down and pass it around, 73 bottles of beer on the wall.
153
154 73 bottles of beer on the wall, 73 bottles of beer.
155 Take one down and pass it around, 72 bottles of beer on the wall.
156
157 72 bottles of beer on the wall, 72 bottles of beer.
158 Take one down and pass it around, 71 bottles of beer on the wall.
159
160 71 bottles of beer on the wall, 71 bottles of beer.
161 Take one down and pass it around, 70 bottles of beer on the wall.
162
163 70 bottles of beer on the wall, 70 bottles of beer.
164 Take one down and pass it around, 69 bottles of beer on the wall.
165
166 69 bottles of beer on the wall, 69 bottles of beer.
167 Take one down and pass it around, 68 bottles of beer on the wall.
168
169 68 bottles of beer on the wall, 68 bottles of beer.
170 Take one down and pass it around, 67 bottles of beer on the wall.
171
172 67 bottles of beer on the wall, 67 bottles of beer.
173 Take one down and pass it around, 66 bottles of beer on the wall.
174
175 66 bottles of beer on the wall, 66 bottles of beer.
176 Take one down and pass it around, 65 bottles of beer on the wall.
177
178 65 bottles of beer on the wall, 65 bottles of beer.
179 Take one down and pass it around, 64 bottles of beer on the wall.
180
181 64 bottles of beer on the wall, 64 bottles of beer.
182 Take one down and pass it around, 63 bottles of beer on the wall.
183
184 63 bottles of beer on the wall, 63 bottles of beer.
185 Take one down and pass it around, 62 bottles of beer on the wall.
186
187 62 bottles of beer on the wall, 62 bottles of beer.
188 Take one down and pass it around, 61 bottles of beer on the wall.
189
190 61 bottles of beer on the wall, 61 bottles of beer.
191 Take one down and pass it around, 60 bottles of beer on the wall.
192
193 60 bottles of beer on the wall, 60 bottles of beer.
194 Take one down and pass it around, 59 bottles of beer on the wall.
195
196 59 bottles of beer on the wall, 59 bottles of beer.
197 Take one down and pass it around, 58 bottles of beer on the wall.
198
199 58 bottles of beer on the wall, 58 bottles of beer.
200 Take one down and pass it around, 57 bottles of beer on the wall.
201
202 57 bottles of beer on the wall, 57 bottles of beer.
203 Take one down and pass it around, 56 bottles of beer on the wall.
204
205 56 bottles of beer on the wall, 56 bottles of beer.
206 Take one down and pass it around, 55 bottles of beer on the wall.
207
208 55 bottles of beer on the wall, 55 bottles of beer.
209 Take one down and pass it around, 54 bottles of beer on the wall.
210
211 54 bottles of beer on the wall, 54 bottles of beer.
212 Take one down and pass it around, 53 bottles of beer on the wall.
213
214 53 bottles of beer on the wall, 53 bottles of beer.
215 Take one down and pass it around, 52 bottles of beer on the wall.
216
217 52 bottles of beer on the wall, 52 bottles of beer.
218 Take one down and pass it around, 51 bottles of beer on the wall.
219
220 51 bottles of beer on the wall, 51 bottles of beer.
221 Take one down and pass it around, 50 bottles of beer on the wall.
222
223 50 bottles of beer on the wall, 50 bottles of beer.
224 Take one down and pass it around, 49 bottles of beer on the wall.
225
226 49 bottles of beer on the wall, 49 bottles of beer.
227 Take one down and pass it around, 48 bottles of beer on the wall.
228
229 48 bottles of beer on the wall, 48 bottles of beer.
230 Take one down and pass it around, 47 bottles of beer on the wall.
231
232 47 bottles of beer on the wall, 47 bottles of beer.
233 Take one down and pass it around, 46 bottles of beer on the wall.
234
235 46 bottles of beer on the wall, 46 bottles of beer.
236 Take one down and pass it around, 45 bottles of beer on the wall.
237
238 45 bottles of beer on the wall, 45 bottles of beer.
239 Take one down and pass it around, 44 bottles of beer on the wall.
240
241 44 bottles of beer on the wall, 44 bottles of beer.
242 Take one down and pass it around, 43 bottles of beer on the wall.
243
244 43 bottles of beer on the wall, 43 bottles of beer.
245 Take one down and pass it around, 42 bottles of beer on the wall.
246
247 42 bottles of beer on the wall, 42 bottles of beer.
248 Take one down and pass it around, 41 bottles of beer on the wall.
249
250 41 bottles of beer on the wall, 41 bottles of beer.
251 Take one down and pass it around, 40 bottles of beer on the wall.
252
253 40 bottles of beer on the wall, 40 bottles of beer.
254 Take one down and pass it around, 39 bottles of beer on the wall.
255
256 39 bottles of beer on the wall, 39 bottles of beer.
257 Take one down and pass it around, 38 bottles of beer on the wall.
258
259 38 bottles of beer on the wall, 38 bottles of beer.
260 Take one down and pass it around, 37 bottles of beer on the wall.
261
262 37 bottles of beer on the wall, 37 bottles of beer.
263 Take one down and pass it around, 36 bottles of beer on the wall.
264
265 36 bottles of beer on the wall, 36 bottles of beer.
266 Take one down and pass it around, 35 bottles of beer on the wall.
267
268 35 bottles of beer on the wall, 35 bottles of beer.
269 Take one down and pass it around, 34 bottles of beer on the wall.
270
271 34 bottles of beer on the wall, 34 bottles of beer.
272 Take one down and pass it around, 33 bottles of beer on the wall.
273
274 33 bottles of beer on the wall, 33 bottles of beer.
275 Take one down and pass it around, 32 bottles of beer on the wall.
276
277 32 bottles of beer on the wall, 32 bottles of beer.
278 Take one down and pass it around, 31 bottles of beer on the wall.
279
280 31 bottles of beer on the wall, 31 bottles of beer.
281 Take one down and pass it around, 30 bottles of beer on the wall.
282
283 30 bottles of beer on the wall, 30 bottles of beer.
284 Take one down and pass it around, 29 bottles of beer on the wall.
285
286 29 bottles of beer on the wall, 29 bottles of beer.
287 Take one down and pass it around, 28 bottles of beer on the wall.
288
289 28 bottles of beer on the wall, 28 bottles of beer.
290 Take one down and pass it around, 27 bottles of beer on the wall.
291
292 27 bottles of beer on the wall, 27 bottles of beer.
293 Take one down and pass it around, 26 bottles of beer on the wall.
294
295 26 bottles of beer on the wall, 26 bottles of beer.
296 Take one down and pass it around, 25 bottles of beer on the wall.
297
298 25 bottles of beer on the wall, 25 bottles of beer.
299 Take one down and pass it around, 24 bottles of beer on the wall.
300
301 24 bottles of beer on the wall, 24 bottles of beer.
302 Take one down and pass it around, 23 bottles of beer on the wall.
303
304 23 bottles of beer on the wall, 23 bottles of beer.
305 Take one down and pass it around, 22 bottles of beer on the wall.
306
307 22 bottles of beer on the wall, 22 bottles of beer.
308 Take one down and pass it around, 21 bottles of beer on the wall.
309
310 21 bottles of beer on the wall, 21 bottles of beer.
311 Take one down and pass it around, 20 bottles of beer on the wall.
312
313 20 bottles of beer on the wall, 20 bottles of beer.
314 Take one down and pass it around, 19 bottles of beer on the wall.
315
316 19 bottles of beer on the wall, 19 bottles of beer.
317 Take one down and pass it around, 18 bottles of beer on the wall.
318
319 18 bottles of beer on the wall, 18 bottles of beer.
320 Take one down and pass it around, 17 bottles of beer on the wall.
321
322 17 bottles of beer on the wall, 17 bottles of beer.
323 Take one down and pass it around, 16 bottles of beer on the wall.
324
325 16 bottles of beer on the wall, 16 bottles of beer.
326 Take one down and pass it around, 15 bottles of beer on the wall.
327
328 15 bottles of beer on the wall, 15 bottles of beer.
329 Take one down and pass it around, 14 bottles of beer on the wall.
330
331 14 bottles of beer on the wall, 14 bottles of beer.
332 Take one down and pass it around, 13 bottles of beer on the wall.
333
334 13 bottles of beer on the wall, 13 bottles of beer.
335 Take one down and pass it around, 12 bottles of beer on the wall.
336
337 12 bottles of beer on the wall, 12 bottles of beer.
338 Take one down and pass it around, 11 bottles of beer on the wall.
339
340 11 bottles of beer on the wall, 11 bottles of beer.
341 Take one down and pass it around, 10 bottles of beer on the wall.
342
343 10 bottles of beer on the wall, 10 bottles of beer.
344 Take one down and pass it around, 9 bottles of beer on the wall.
345
346 9 bottles of beer on the wall, 9 bottles of beer.
347 Take one down and pass it around, 8 bottles of beer on the wall.
348
349 8 bottles of beer on the wall, 8 bottles of beer.
350 Take one down and pass it around, 7 bottles of beer on the wall.
351
352 7 bottles of beer on the wall, 7 bottles of beer.
353 Take one down and pass it around, 6 bottles of beer on the wall.
354
355 6 bottles of beer on the wall, 6 bottles of beer.
356 Take one down and pass it around, 5 bottles of beer on the wall.
357
358 5 bottles of beer on the wall, 5 bottles of beer.
359 Take one down and pass it around, 4 bottles of beer on the wall.
360
361 4 bottles of beer on the wall, 4 bottles of beer.
362 Take one down and pass it around, 3 bottles of beer on the wall.
363
364 3 bottles of beer on the wall, 3 bottles of beer.
365 Take one down and pass it around, 2 bottles of beer on the wall.
366
367 2 bottles of beer on the wall, 2 bottles of beer.
368 Take one down and pass it around, 1 bottle of beer on the wall.
369
370 1 bottle of beer on the wall, 1 bottle of beer.
371 Take it down and pass it around, no more bottles of beer on the wall.
372
373 No more bottles of beer on the wall, no more bottles of beer.
374 Go to the store and buy some more, 99 bottles of beer on the wall.
375     SONG
376     assert_equal expected, ::Bottles.new.song
377   end
378 end

1. From the novel by Joseph Heller, a catch-22 is a paradoxical situation from which you cannot escape because of contradictory rules.
2. For those unfamiliar with the fairy tale, this is a reference to everything owned by the Little, Small, Wee Bear in Goldilocks (Goldenlocks) and the Three Bears
3. This quote was historically thought to originate with Mark Twain but is now widely attributed to Charles Dudley Warner. Twain and Warner were neighbors and the former apparently heard it from the latter.