A Programming Primer for Counting and Other Unconventional Tasks

Style, Conventions, and Debugging

Some basics on reading, annotating, and debugging code.

This section covers some look-and-feel conventions when writing code. These are concepts so basic to learned programmers that this chapter will seem trivial. But I've found these basics to be a stumbling block to any non-programmer whose life experience with digital writing involves the rule of "one space after a period-ending sentence."

General program structure

Ruby programs execute from top to bottom:

puts "Hello world"
puts "This will be printed second"
puts "This will be printed third"   
puts "Bye world"

But like mathematical expressions, they may not always go left from right if there are parentheses or other order-changing constructs:

 10 * (3 + 4)

In this shorthand variation of an if statement, "Hello" is only printed if the mathematical comparison at the end is true:

puts "Hello" if 2 + 2 > 3

We'll learn more about if statements in a later chapter.

Comments

Comments are how programmers annotate their code. The comments themselves are ignored while a program runs.

To comment out a line of code, use the pound sign #.


puts "Hello world. This is code"   # and this here is a comment
# this entire line, from the beginning to the line break, is a comment      
   

Everything after the pound sign – on the same line – will be ignored by the program.

If you're using one of the text-editors I mentioned in the installation chapter, they use a different color to show comments, making it easy to tell comments apart from real code:

Compare color code
The top code excerpt is plain text. The bottom version is how TextWrangler colors Ruby code.

Throughout this book, I will use the convention of #=> inside example code as a shorthand way of saying, "This is what your program will output at this point in the script."

The symbol #=>, to the Ruby interpreter, is equivalent to # because as I mentioned earlier, everything after the pound sign on the same line is considered a comment.


puts 8 + 9 #    This expression adds eight and nine together
puts 8 + 9# + 7 This also adds eight and nine; the  "+ 7" is considered part of the comment

# I will frequently use comments to show the expected result of an expression

puts 12 + 3    
#=> 15

puts "dog" + "cat"
#=> dogcat
   
Multi-line comments

When you want to comment out more than a few lines, it is tedious to put the pound sign in front of all of them. You can comment an entire group of lines at once by using =begin at the start of the comment block and =end at the...end.

Note: The =begin and =end must be at the beginning of their respective lines, with no empty whitespace preceding them.

Multi-line comments
What multi-line comments look like.

Commenting code makes it easier for you and anyone to understand it. As in day-to-day writing, the effectiveness of comments varies with writing skill.

Whitespace

What is "whitespace"? It includes the carriage returns made when you hit "Enter", as well as space and tab characters.

For the most part, whitespace in the code doesn't matter to the Ruby interpreter:


puts "hello world"
         puts       "hello world"   # same deal here
      
      
      
         # all that above space is irrelevant to the interpeter
                  puts   "hello world"
      

For readability's sake, I might break up long sections of code into several lines. These sections have some kind of opening and closing symbol, such as curly braces {} or square brackets []

The result is not more commands as interpreted by Ruby, just more lines for a human to read.


minimum_value = [65,3,100,42,-7].inject(nil){  |x, num| x = num if x == nil || x > num }

# multi-line:
minimum_value = [65,3,100,42,-7].inject(nil){ |x, num| 
   x = num if x == nil || x > num 
}

array = ["alpha", "bravo", "charlie", "delta", "echo"]

# multi-line:
array = [
   "alpha", 
   "bravo", 
   "charlie", 
   "delta", 
   "echo"
]
Whitespace and symbols

Typically, you can use as many regular spaces between operators and operands as you'd like. The following three expressions do the same thing in Ruby:

4+4
4      +       4
4 + 4 

The last version, with a single space between each operator and operand, is the generally preferred version.

Indentation

Just as it's used in a traditional essay's outline, indentation is used by programmers to indicate nested elements and subroutines. In Ruby, the interpreter pays no mind to it. It's purely for human readability.

One of the most common programming constructs is the loop, which executes a given block of code a given number of times. Try this out:

3.times.each do |num|
   puts "start of the block..."
   puts num
   puts "...ending the block"
end

The output should look like this:

start of the block...
0
...ending the block
start of the block...
1
...ending the block
start of the block...
2
...ending the block

The first and last line defines the loop. The lines in between consist of the subroutine, or block that is to be repeated. A loop within a loop would have another level of indentation:


3.times.each do |x|
   puts "In the first loop"
   4.times.each do |y|
      puts "In the second loop "
      puts x * y
   end
end

And to reiterate the point about whitespace being insignificant in Ruby, the following code is equivalent to the code above, albeit harder to read:


3.times.each do |x|
puts "In the first loop"
4.times.each do |y|
puts "In the second loop "
puts x * y
end
end

I use the tab key to indent. Other programmers prefer to double-space. Most programming text-editors are smart enough to indent subsequent lines – just as Microsoft Word will automatically indent and number items in a numbered list after you type in something like: "1. A first step..."

Whitespace exceptions

There are various exceptions to how freely you can use whitespace. Again, it's hard to get into specifics without being more familiar with the language.

Carriage returns between operations: In general, don't hit Enter to break up an operation. For example, this:

puts "hello"

– is not equivalent to this:

puts
"hello"

However, this:

2 + 2 + 2

– can actually be written as this:

2 + 2
+ 2 

– but why would you do it? It's going to confuse anyone else who reads it.

The following involves a conditional statement, something we cover a few chapters down the road:


if 4 == 2 + 2
   puts "Ergo, 2+2 is not 5"
end

– and it is not equivalent to this:

if 4 
   == 2 + 2
   puts "Ergo, 2+2 is not 5"
end

The elements of the conditional statement (4 == 2 + 2, i.e. "Is four equal to two plus two?") need to be on the same line – again, don't worry about knowing why yet. Just stick to what makes intuitive sense: why would you do the extra work of separating a statement with a line break like that?

The =begin and =end comment block:

When setting an entire block of code to be commented out with =begin and =end, those two keywords must be on their own lines, with no whitespace preceding them. So this:


=begin
these 
   are comments
=end   

– and not this:


 =begin
this will NOT be a comment
=end

=begin
 this is OK
   =end
  And the above =end did NOT close this section of comments   

The main point is that you can be liberal in your spacing and indenting in order to write code that is readable and organized for you and anyone else who has to read it, as long as you don't do anything that just seems really bizarre.

The upside to Ruby's general indifference to whitespace is that you can quickly write code without having to be neat and organized. The downside is that you can quickly code without having to be neat and organized.

Variations in style

Just as Ruby gives programmers leeway with whitespace, the language allows for different ways to write the same expression. This is sometimes referred to as syntactic sugar. You'll appreciate it as you get better, but it can definitely lead to confusion early on.

For example, here are some pairs of expressions that are similar to each other. Don't worry about actually understanding or memorizing them, I'll point these style variations out in the appropriate chapters. Just get used to seeing how things might be changed up:


puts "Hello World"
puts("Hello World")      
   

# if statements can be put inline      
if 12/6 == 2
   puts "12/6 is 2"
end

puts "12/6 is 2" if 12 / 6 == 2   

# curly braces can be substituted for do...end blocks
[1,2,3,4].each do |x|
   puts(x)
end

[1,2,3,4].each{ |x| puts(x) }      
   

It can be a bit confusing, like trying to learn English from a Star Wars novel with Yoda as the main character. I will attempt to be consistent in my conventions and as non-Ruby specific as possible. But where I feel that the code is easy enough to follow, I may throw in slight variations of style.

If you're confused by something, practice it. If one code snippet seems like a variation in style – rather than meaning – from another snippet, then write and run both variations to see if that's the case.

Debugging with a deep breath

Programming isn't for people who don't like making mistakes or can't stand the feeling of being lost. Because when you start out, you will screw up a lot. And even worse, you're going to be chewed out in a foreign language.

But the best part about programming – at least when it's just you and your code – is that every screwup has a logical, consistent reason behind it. Code that worked last night isn't going to stop working when you wake up in the morning. Hammering on the keyboard-shortcut for Run won't make bad code run faster.

And when you get an unexpected error message, you just have to follow what it says, even if it isn't in the clearest English.

Deduce the unfamiliar

Copy and paste the following code into your text editor (or irb) and execute it:


c, d = "X++-"[0..1].split('').map{|x| x[0].to_i*1*4*"9"[0] }
c * rand + 100; puts c; c*d - (c==9+'a'[3].to_i ? 3.14 : (
c==d ? 12 * 6.62606957*rand(100*c*d) :? rand(c+(9*c)) )); a = [42,12]; puts a.compact.map{|x| 
d -= 9 +'a'[3].to_i+('?'.to_i+100) *5.12 
x.to_s.upcase.to_i *rand(12) }.join("||!@ --")
c * (rand(10) *rand(5020 + c) * ran(d*1000) + rand(c))
c+d-c* 64.001*rand(3.141516 * 100.0101 * c)
e = a.each_with_index.map{|r, i| a[i] = a[i] * -2 }

You should get an error like this:

/Users/dnguyen/ttest.rb:3: warning: invalid character syntax; use ?\s
/Users/dnguyen/ttest.rb:3: syntax error, unexpected '?'
... * 6.62606957*rand(100*c*d) :? rand(c+(9*c)) )); a = [42,12]...

At this stage, most of the words above probably make no sense. Where are you supposed to use ?\s. What's a rand? And forget about understanding the actual code: I don't even know what it does and I just made it up.

So just focus on what you do know. Below, I've highlighted what appears to be the filename:

/Users/dnguyen/ttest.rb:3: warning: invalid character syntax; use ?\s
/Users/dnguyen/ttest.rb:3: syntax error, unexpected '?'
... * 6.62606957*rand(100*c*d) :? rand(c+(9*c)) )); a = [42,12]...

What's the :3 stand for? Considering the code is only 8 lines long, and that there happens to be a couple of question marks on line 3, you can reasonably guess that :3 stands for Line 3.

So there's an unexpected '?' on Line 3, but there are two of them in the code:

c==d ? 12 * 6.62606957*rand(100*c*d) :? rand(c+(9*c)) )); a = [42,12]; puts a.compact.map{|x| 

Let's go back to the error message and focus on the line that actually says error:

/Users/dnguyen/ttest.rb:3: syntax error, unexpected '?'
... * 6.62606957*rand(100*c*d) :? rand(c+(9*c)) )); a = [42,12]...

The error message has helpfully pointed out the excerpt of Line 3 with the question mark. Let's take it out and see what happens; copy and execute the code below:


c, d = "X++-"[0..1].split('').map{|x| x[0].to_i*1*4*"9"[0] }
c * rand + 100; puts c; c*d - (c==9+'a'[3].to_i ? 3.14 : (
c==d ? 12 * 6.62606957*rand(100*c*d) : rand(c+(9*c)) )); a = [42,12]; puts a.compact.map{|x| 
d -= 9 +'a'[3].to_i+('?'.to_i+100) *5.12 
x.to_s.upcase.to_i *rand(12) }.join("||!@ --")
c * (rand(10) *rand(5020 + c) * ran(d*1000) + rand(c))
c+d-c* 64.001*rand(3.141516 * 100.0101 * c)
e = a.each_with_index.map{|r, i| a[i] = a[i] * -2 }   

And you'll get another error. I've highlighted the parts that most resemble English:

20064
0||!@ --11
NoMethodError: undefined method ‘ran’ for main:Object 
   at top level   in ttest.rb at line 6

We know from past experience that the reported line number – Line 6, in this case – is pretty accurate. And though we don't know why an undefined method is bad, there is apparently one such thing with the name of 'ran'. So let's look at Line 6:

c * (rand(10) *rand(5020 + c) * ran(d*1000) + rand(c))

So ran isn't right, but what is it supposed to be? Well, there's some use of the term rand; it's possible that the programmer made a typo. So tack on the letter 'd' and see what happens:


c, d = "X++-"[0..1].split('').map{|x| x[0].to_i*1*4*"9"[0] }
c * rand + 100; puts c; c*d - (c==9+'a'[3].to_i ? 3.14 : (
c==d ? 12 * 6.62606957*rand(100*c*d) : rand(c+(9*c)) )); a = [42,12]; puts a.compact.map{|x| 
d -= 9 +'a'[3].to_i+('?'.to_i+100) *5.12 
x.to_s.upcase.to_i *rand(12) }.join("||!@ --")
c * (rand(10) *rand(5020 + c) * rand(d*1000) + rand(c))
c+d-c* 64.001*rand(3.141516 * 100.0101 * c)
e = a.each_with_index.map{|r, i| a[i] = a[i] * -2 }   

You get this non-sensical output:

20064
42||!@ --6

But there's no error message. So at least that's off our backs! There's nothing more we can really do since we were given no insight on what the program is supposed to do. But fixing the show-stopping errors on an indecipherable chunk of code without knowing what it does – or, if you're new to this, not knowing code period – was just a matter seeing past the clutter and following directions.

It may seem counter-intuitive, but it's actually good when a flawed program crashes and sends out a helpful error message. The worst errors are the ones that go by without anyone realizing that a bug exists.

Using puts is not just for putz

There are specialized debugger programs that give a more sophisticated picture of the internal workings of your code. But oftentimes, especially for small scripts, it's quicker to directly "ask" a buggy program what it is doing at each step of the way.

In the installation guide, we ran our first program:

puts "Hello world"

All it did was print out to the screen:

Hello world

So, even without knowing that puts is a method – or that "Hello world" is a string – we can assume for now that puts puts things on the screen for us to read.

The following program is supposed to simply print out a message comparing values in the variables a and b:


a = 1 + 1
b = 2 + 2

if a = b
   puts "a is equal to b"
elsif a > b
   puts "a is greater than b"
elsif a < b 
   puts "a is less than b"
end   

Without knowing exactly what variables are, or what the if/elsif statements are about, you can still deduce the gist of it: I mentioned previously that this program will print out a message comparing a and b. Since a and b seem to be set equal to the values of 2 and 4, respectively (1 + 1 and 2 + 2), you probably are surprised to see this as the output of the program:

a is equal to b
Step-by-step

Well, it's possible that + doesn't mean what it did in elementary school. So lets modify the code to insert a couple of puts statements to see what's actually in a and b:


a = 1 + 1
b = 2 + 2
 
puts "a is: "
puts a

puts "b is:"
puts b

if a = b
   puts "a is equal to b"
elsif a > b
   puts "a is greater than b"
elsif a < b 
   puts "a is less than b"
end   

And the output is:

a is: 
2
b is:
4
a is equal to b

So addition works. And yet the value of a is clearly not equal to the value in b. So let's move those puts statements to test if the equals sign in if a = b does the comparison that we assume it to do:

a = 1 + 1
b = 2 + 2

if a = b
 
   puts "a is: "
   puts a

   puts "b is:"
   puts b
    
   puts "a is equal to b"

elsif a > b
   puts "a is greater than b"
elsif a < b 
   puts "a is less than b"
end

And the output of that code is:

a is: 
4
b is:
4
a is equal to b

Bingo. It appears that the equals sign in if a = b is setting the value of a equal to the value in b

Well, maybe that's what the program is supposed to do – who knows? And (at least until we get to the conditionals chapter to find out what really is supposed to be in that if statement) we don't know how to fix it, but at least we can report it to the proper authority.

Keep calm and carry on

I've just covered what amounts to about 80% of the stupid errors I make in my own code: typos and poor use of the equals sign. As you get better, your code is going to get more complicated. And, so will the bugs. But the attributes for finding and fixing those bugs – patience and logical reasoning, plus a little experience – will always be the same.

Even just patience might be enough. We're all very lucky to be programming at a time when the Internet – specifically Google and StackOverflow – can connect us to countless others who are willing to help. You just have to commit to take the time to ask – and listen.