Chad Perrin: SOB

28 March 2008

10 steps to 99 bottles: simple program dev 101

Filed under: Geek — Tags: , , , — apotheon @ 12:07

Thinking back, one of the most difficult things for me to really get the hang of at first when I initially started learning to program was how to get off to a good, orderly start when writing a simple program. I’m reminded of this by someone who popped up on the ruby-talk mailing list asking for help with a programming problem presented in Chris Pine’s Learn to Program online tutorial. (Note that page also gives you somewhere to go to buy the book Pine wrote as an “improved, expanded” version of the same idea.)

I figured I should share some of what I’ve learned since then about how to do exactly that — how to start writing a simple program. I’ll use the same programming problem that came up on ruby-talk: something that prints out the lyrics for 99 Bottles of Beer. It probably won’t be the most elegant implementation possible, but it should make the point about how to start writing software, and it’ll stay away from concepts that might be considered “advanced” or are idiomatic of a particular programming language. It will, however, assume that you know some very basic programming syntax.

So, here it is, a step-by-step example of how to start writing a simple program, using Ruby as the example language.

  1. Create the framework of the file. It should look something like this:

      #!/usr/bin/env ruby
    

  2. Come up with your flow control structure. Idiomatic Ruby would probably involve an iterator like each or downto, but we’ll stick with something common to most languages used by beginners — the while loop. There’ll also probably have to be a variable involved:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 0
      end
    

  3. Next, make sure you haven’t broken anything already. Of course, you need some way to indicate that things are working, if only very vaguely. As such, we’ll just make sure that the while loop terminates based on use of the variable, so you know you haven’t written an infinite loop. The following program should just run and end quickly without any output, indicating that your loop is behaving as expected:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 0
        bottles -= 1
      end
    

  4. Now you need to make it actually do something useful. Stick a puts statement in there to print out the first line of each stanza of the song. It should print out “n bottles of beer on the wall,” over and over, where n is a number counting down from 99 to 1.

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 0
        puts "#{bottles} bottles of beer on the wall,"
        bottles -= 1
      end
    

  5. Of course, there’s something wrong with saying “1 bottles of beer on the wall,” since “1” doesn’t really conjure up an idea of plurality. We’ll solve the problem of how to generate the correct behavior later, but for now we can at least eliminate the incorrect behavior by having it stop at 2 instead of 1.

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 1
        puts "#{bottles} bottles of beer on the wall,"
        bottles -= 1
      end
    

  6. Let’s finish off that while loop so that it does everything we need it to do — print out the rest of each stanza of the song:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 1
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
      end
    

  7. The last edit to the program reintroduced the singular/plural mismatch, because the last line generated says “1 bottles of beer on the wall!” Let’s just take that last stanza out of the mix, and deal with it separately. Increment your test value by one, again:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 2
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
      end
    

  8. Unfortunately, the song isn’t quite done. We still need to deal with the special cases we cut out of the while loop. Write something to deal with the last two stanzas:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 2
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
      end
    
      puts "2 bottles of beer on the wall,"
      puts "2 bottles of beer,"
      puts "Take one down, pass it around,"
      puts "1 more bottle of beer on the wall!"
    
      puts "1 bottle of beer on the awall,"
      puts "Only 1 bottle of beer,"
      puts "Take it down, pass it around,"
      puts "No more bottles of beer on the wall!"
    

  9. Looking at the output, it could be prettier. Add some empty puts statements in there to space out the stanzas:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 2
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
        puts
      end
    
      puts "2 bottles of beer on the wall,"
      puts "2 bottles of beer,"
      puts "Take one down, pass it around,"
      puts "1 more bottle of beer on the wall!"
    
      puts
    
      puts "1 bottle of beer on the awall,"
      puts "Only 1 bottle of beer,"
      puts "Take it down, pass it around,"
      puts "No more bottles of beer on the wall!"
    

  10. Now that everything works, all we need to do is make a better program out of it. If you learn some programming techniques that can help make the program more easily understood and maintained, or somehow more efficient, they might be useful. Some idiomatic Ruby techniques such as the aforementioned downto iteration method, conditional handling of singularity vs. plurality, and other changes might make for a more elegant solution.
  11. Refactoring your program isn’t necessarily a big deal for a trivial programming problem like this, but this is a great time to practice those techniques so you’ll have a handle on them when you start needing them for more complex problems.

All original content Copyright Chad Perrin: Distributed under the terms of the Open Works License