Search This Blog

May 30, 2014

When to freeze, clone or dup in Ruby?

I try out expressing my intent in different forms to see what works effective. This time around, I'd like to story tell the code as if it's evolving over a pair-programming session. I'd appreciate your constructive feedback.

Warning the examples in story you're about to read might be super contrived and redundant. I'm merely trying the story-telling way to explain concepts with examples in the hope that you get near first-hand experience of pair-coding and experience learning. Chew on the concept leaving the contrived examples and story behind :P

The Context


In a fairy tale island called Maamu in Divided States of Fundia (DSF), there was this super successful Agile consulting shop that boasts itself of crafting high-end, complex, mission-critical social-networking software solutions in Ruby on Rails.

In this company there was a veteran TDD practitioner by the name Bob and expert Ruby/Rails programmer by name David. While Bob loves and evangelises TDD sharing his experience, David from time to time bowls people over with his ideas, opinions and experiences. Incidentally, they both were once assigned in the same project and had to pair program. As client would have it, David suddenly became not so passionate of TDD and thankfully Bob being the veteran he is, was very accommodative to different ideas to see if he can be of influence or be influenced by David for right cause.

The Pairing Session


In one of the pair programming sessions between David and Bob, David ended up writing the following method in one of his classes.
def salutation(name, informal=true)
  return name.replace "Hi #{name}!" if informal
  name.replace "Hello #{name}!"
end
and said, "Yay!, I'm done". Bob smiled and said "May be not. Mind if I write the test for the method to check for its correctness?" David being David said, "Sure, have fun!"

By writing a few tests, Bob said, "Dude, we've a problem. This method mutates the string argument name that is passed to it. And this is bad. Want to change the code to pass my tests?". David was delighted to see value in tests and went about writing code to pass the tests. David sighed, "I now see some value in tests but test first TDD is something I'm still not really convinced about." Bob smiled, "You're awesome.. some day you'll appreciate writing code Test first. And I'll see if I can be of any help to you in this." Bob then, casts his eye on the code that David just wrote and it looked like below:
def salutation!(name, informal=true)  # Note the ! suffix to the method name
  return name.replace "Hi #{name}!" if informal
  name.replace "Hello #{name}!"
end
Bob remarked, "I thought you'd remove the name.replace thingy altogether". David replied, "Well, that is not a bad idea. I'd still wish to not change that piece of code and rather warn the client of using the method's mutant property with that single bang in the method suffix. Let the sensible caller pass on the name's duplicate like the code snippets below:"
salutation!(name.clone);
salutation!(name.dup);
Bob sighed, "What if the object is frozen before the call to salutation? Won't your code throw an error?"

David, "No problem. The client will then have to call name.dup instead of name.clone because the only difference between clone and dup is that clone copies freeze-state as well which dup method doesn't....Also, when somebody marks an object frozen it is especially for the purpose of protecting the object's state from any accidental mutation."

Bob, "Interesting..Do you know that there is a little catch in freezing the object?"

David, "Now what is it?"

Bob, "Gimme the keyboard, lemme explain you with an example."
class User
  attr_accessor :name, :age, :password

  def initialize(name, password, age)
    @name = name
    @password = password
    @age = age
  end
end

user = User.new("karthik", "mypassword", 17)
user.freeze

greetings = salutation!(user.name) #will raise an error??
Bob asks quizzically, "What do you think will happen to the above code when you execute it?". And David replies, "Well it's gonna spit out a runtime error saying you can't modify a frozen object".

Bob sighs "See, that is what happens to the human mind - it tends to miss the nuances. It doesn't throw an error. The code executes and after the code execution the username changes its value with the salutation prefixed. And that is because when an object is frozen it's static values or object references cannot be changed. But the value of the object it refers to can change, as in the case of the User object's username."

David extends his hand and says, "Yeah, I agree and second your thoughts. It was a pleasure pairing with you". Bob leans forward, shakes his hand, hugs David and whispers in his ears, "The pleasure is mine. I had my share of learning. Pairing and TDD is fun any day. Will pair again sooner".

Lessons learnt


1. Pair programming is real awesome, even when you're pairing with a good if not great developer instead of a jerk.
2. TDD is fun and productive. Give it a genuine try, pairing with a few seasoned folks setting aside your ego. Practise, practise practise solving problems with and without it.  You'll end up being pragmatic instead of being dogmatic about or rebel to it.
3. Needless to mention the importance and nuances of freeze, clone and dupe methods in Ruby's Object.