Skip to content

Instantly share code, notes, and snippets.

@stacietaylorcima
Last active January 8, 2018 23:02
Show Gist options
  • Save stacietaylorcima/b0bd55acefd6cc211413032dc87fd067 to your computer and use it in GitHub Desktop.
Save stacietaylorcima/b0bd55acefd6cc211413032dc87fd067 to your computer and use it in GitHub Desktop.
Ruby: Understand how to read errors and handle them.

Ruby: Debugging Code

Objectives

  • Understand how to debug syntax errors.
  • Understand the concept of error handling.
  • Raise exceptions.
  • Properly format code.

Some terminology:

  • When something goes wrong in code, the interpreter will raise an error.
  • If you predict that error ahead of time, you can determine how that error is handled by rescuing it and then specifying the behavior to follow.

Error Types

Indentation

Ruby doesn't care much about whitespace. An exception to that is if the whitespace is included within a string.

  • Best Practices:
    • The definition is right against the left margin of the box.
    • The scope defined by the method is indented 2 spaces (1 tab) from where our definition began.
    • If we defined a scope inside our method, its contents would be indented 2 spaces from where our new scope was defined and not 2 spaces from where our print_number method was defined.
    • For example:
def print_number(num)
  p "You are printing the number #{num}"

  if num < 0 
    p "Don't be so negative."
  end

  p "back outside"
end

Debugging Thought Process

Some methods behave incorrectly without raising errors. Create another example by writing a method that uses a birthday date as an argument. The method should return the day of the week that the birthday will fall on in the coming year

require 'date'    # Makes Ruby's date library available to our code

def next_birthday(birthday)
  a_year_off = birthday.next_year
  a_year_off.strftime("%D")
end

next_birthday(Date.new(2017, 6, 1))
  • This method returns: "06/01/18", so it returned the next year's date, the first of June 2018, but not the day of the week. Where did we go wrong?
  • Break this method down:
    • We defined a next_birthday method that uses one argument, birthday.
    • Within the method, we assigned the value of birthday.next_year to a variable called a_year_off
    • We called strftime("D%") on a_year_off.
  • The method finds the correct date, but returns a string representing the whole date, not the word represetning the day of the week.
    • We can assume that next_date is working correctly and our problem is likely the next line of code.
    • We're assigning the correct value to the variable a_year_off, but failing to convert that value to the desired string.
    • We've narrowed the problem down to the strftime method.
  • Now that we're relatively confident about what's wrong, let's review the Ruby Docs on the strftime method, which we suspect we're using incorrectly.
    • We need to pass the formatting string %A instead of %D

Playing Computer

Means following your code from the top as if you were the computer. When the program fails, the programmer can understand what happened by looking at the incomplete list of instructions and "executing" them in their mind, on at a time.


Tracing

When you're "playing computer", you can get the computer to help you follow the code.

  • For example:
def add_four(num)
  num + 4
end
  • Imagine that we’re calling that method with another variable as the argument
    • Something like result = add_four(counter)
  • But when we run the code, we’re surprised by the value of the result, because we might have assumed that the value of counter was 5, we expected to get 9 back in the result. Since the code looks fine, we’re confused.
  • All the bugs in code come from assumptions. The hard part is figuring out what the assumption is. What if the value of counter was -1, but we were pretty sure it would be 5?
  • A way to help us check our assumption about the value of counter is to simply print it out while the code is running:
def add_four(num)
puts num
  num + 4
end
  • Adding p or puts lines is a way to trace how your program is running.
  • Be sure to remove them when you're done with them.
    • Tip: Don't indent those lines at all so that they attract your attention and you'll be eager to remove them.

Handling Errors

When a program raises and exception, it can crash the application if it's not handled. If you anticipate the possibility of an operation causing an error, you can wrap it in a rescue block.

  • Take a look at this hello method:
def hello(name)
  "Hello #{name}"
end
  • If you call this method with 2 arguments instead of 1, you'll get an ArgumentError and the application will crash.
  • Prevent that crash by calling the method inside a rescue block.
begin
  hello("George", "Washington")
rescue
  p "An error was raised but rescue prevented a crash!" 
end
  • begin keyword starts our block
    • The code we want to execute is nested inside.
  • rescue keyword can be caclled with a specific type of of error that we'd like to rescue from.
    • In this case, not passing anything will raise a StandardError that can catch any type of error.
    • Anything we want to do after the error is "rescued" would be nested here.

@stacietaylorcima
Copy link
Author

Exercises

NoMethodError

  • This is the easiest error to diagnose, but it's worth practicing because it's important to see what the errors look like in the context of RSpec.
  • Don't type anything; just hit "Run". You will see something that looks like:

NoMethodError: undefined method `hello` for main:Object

  • This means that RSpec tried to run the hello method but it couldn't find the method definition (because we haven't defined it yet).
  • Now that you've seen the error, read the test and write a method to make it pass.

Specs:

describe "hello" do

  it "returns 'Hello World' when passed 'World'" do
    expect(hello("World")).to eq("Hello World")
  end

  it "returns 'Hello Bob' when passed 'Bob'" do
    expect(hello("Bob")).to eq("Hello Bob")
  end
end

Method:

def hello(name)
  "Hello #{name}"
end

NameError

  • The NameError is very similar to the NoMethodError error. The NameError occurs when the interpreter can't find a declared variable or method that's being called in a program. Very often, this error will be the result of a typing mistake. Click the "Run" button to execute the code in the editor.
  • You will see the following:
NameError
undefined local variable or method `firs'....
  • The error is telling us that it couldn't find a variable named "firs". This is because the variable we declared in the argument list is named "first". Correct the typing mistake and the the method should pass the specs.

Specs:

describe "hello" do
  it "returns 'Hello first name last name'" do
    expect(hello("Steve", "Jobs")).to eq("Hello Steve Jobs")
  end
end

Method:

def hello(first, last) 
  "Hello #{first} #{last}"
end

Wrong Number of Arguments

  • Try to run the code in the editor.
  • You will see wrong number of arguments (2 for 0) because the method is being invoked with two arguments in the test spec, but our method definition doesn't use any arguments.
  • Change the method to include too many arguments. See how the error changes? Now fix the method so it is correct.

Specs:

describe "hello" do

  it "returns a full greeting for Abraham Lincoln" do
    expect(hello("Abraham", "Lincoln")).to eq("Hello Abraham Lincoln")
  end

  it "returns a full greeting for George Washington" do
    expect(hello("George", "Washington")).to eq("Hello George Washington")
  end

end

Method:

def hello(first, last)
  "Hello #{first} #{last}"
end

TypeError

  • Type the following:
def add(a,b)
  a + " plus " + b
end
  • You will see a String can't be coerced into Fixnum error. Implement a working method using string interpolation.

Specs:

describe "add" do

  it "returns a string with 1 and 2 added" do
    expect(add(1,2)).to eq("1 + 2 = 3")
  end

  it "returns a string with 5 and 7 added" do
    expect(add(5,7)).to eq("5 + 7 = 12")
  end

end

Method:

def add(a,b)
  c = a + b
  
  p "#{a} + #{b} = #{c}"
end

Unexpected end

  • When we start writing more code we will be writing end a lot, but it can be easy to forget to write ‘end’. Fortunately, the interpreter's job is to let us know when we forget something.
  • Tip: The trick with this type of error is to look for every keyword that requires an end.
  • Type the following:
def hello
  "Hello World"
  • You should see:
syntax error, unexpected end-of-input, expecting keyword_end (SyntaxError)
  • A missing end is one type of syntax error, but there are others where we might have a missing quotation mark, or a missing closing parenthesis.
  • Anytime you see a syntax error it's because something is missing or there is an extra keyword or character somewhere.
  • Implement a method that passes the test.

Specs:

describe "hello" do

  it "returns 'Hello World'" do
    expect(hello).to eq("Hello World")
  end

end

Method:

def hello
  "Hello World" 
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment