I've been bitten a couple of times by forgetting that x = y
in Ruby makes x refer to the same object as y; I'm too used to languages where it means, in Ruby terms, x = y.dup
. Forgetting this, I inadvertently change y
when I think it's safe on the right side of the assignment.
I can see that it would make sense to avoid simple x = y
assignments without a special reason, but the same thing can be lurking in other places such as
name = (person.last_name.blank? ? 'unknown' : person.last_name)
where a later name << title
would actually be changing person.last_name and not just name.
If this has happened to you, too, how have you learned to avoid it? Are there certain red flags or patterns to look for? Do you look with suspicion at each assignment you make? Do you use .dup
a lot? I don't know if Ruby's usage will ever become second nature to me, so any useful tips would be welcome.
A method should not modify a variable (e.g. by using the shift operator) unless its definition says it will modify it.
So: never modify an object in a method that didn't either (a) create it or (b) document that it modifies it.
This may sound unorthodox in a (essentially imperative) language like Ruby, but my advice is: avoid collateral damages by not updating objects at all (except when strictly necessary); create new ones instead. You pay a bit of performance but you'll get code which is clearer, more compact, more modular and easier to debug.
http://en.wikipedia.org/wiki/Functional_programming
So, in your example, just create a new string with a new name:
Just an addition to tokland's answer:
Functional approach insists on immutability - i.e. not altering existing objects, but creating another whenever you want to change the original one. This is somewhat against the object-orientated paradigm that Ruby also brings (objects keep their state internally, which can be altered by calling methods on it), so you have to balance a bit between the two approaches (on the other hand, we benefit by having multiple paradigms easily accessible in a single language).
So, three things to remember for now:
y=x
, you are only saying "we give another namey
to whatever was namedx
".name << title
mutates object calledname
.name += title
takes objects namedname
andtitle
, concatenates them into another object, and assigns that new object namename
. It doesn't mutate anything.I also came across such a situation and it resulted in a bug, which I took half a day to figure out. I essentially did something like this
This code was inside a loop and in the loop, I expected the variable
file_name
to be again set to original value. But the object.file_name was changed, as I was performingfile_name.gsub!
. There are 2 ways to solve this. Either replace the.gsub!
call withfile_name = file_name.gsub
or dofile_name = object.file_name.dup
. I opted for the second option.I think we should be careful with methods having
!
and<<
, as they change the original object on which they are acting, especially after assignments like this.