Sie sind auf Seite 1von 32

Ruby

Gotchas
by Dave Aronson,
T. Rex of Codosaurus, LLC
Though "engineered to
maximize programmer
happiness", with the
"principIe of Ieast surprise",
Ruby stiII has gotchas.
This presentation wiII
proceed from newbie triviaI
gotchas, to more advanced
and confusing gotchas.
we = 2
class Fixnum
def rb; self; end
end
we <3 .rb

but = 3
still = 1
perfect = 4
but - still .rb < perfect

Ruby be surprising!

puts "x = #{x}\n\n"
x = 3
puts 'x = #{x}\n\n'
x = #{x}\n\n
Don't quote me on this, but . . . .
String interpoIation
(incIuding speciaI chars
Iike \n) requires "doubIe"
quotes, and faiIs with
'singIe' quotes.
(Just Iike most
Ianguages with string
interpoIation.)
OnIy two things are false
(faIsey): false, and nil.
Everything eIse is true
(truthy), even 0 (faIse in C),
"" (faIse in JS), [], etc.
(Trips up peopIe from C, JS,
etc. where some of these are
faIse.)
It's twue! It's twue!
true ? "true" : "false"
=> "true"
false ? "true" : "false"
=> "false"
nil ? "true" : "false"
=> "false"
1 ? "true" : "false"
=> "true"
0 ? "true" : "false"
=> "true"
"false"? "true" : "false"
=> "true"
"" ? "true" : "false"
=> "true"
[] ? "true" : "false"
=> "true"
bash> irb-1.9
str = "string"
=> "string"
str[2]

# makes sense.
String... or nothing!
bash> irb-1.8
str = "string"
=> "string"
str[2]

# wtf?! ascii code!
str[2..2]

# that's better!
str[2,1]

# works too....
SymboIs != strings.
Even if same printed.
Remember which one to
use for what (args).
IdeaIIy, take either: "Be
IiberaI in what you accept,
and conservative in what
you send." (PosteI's Law)
str = "string"
sym = :string
puts str
string
puts sym
string
str == sym
=> false
Hang him in effigy
(String him up, symboIicaIIy)



(irb):3: warning: already
initialized constant FOO
=> 7

=> 7
Constants Aren't (Part One)
(InitiaI uppercase means
constant, in Ruby.)
Try to change a constant.
Ooooh, you get a
WARNING! BFD.
Even freezing doesn't
work for Fixnums.
(It does work for arrays
and such -- he said
foreshadowingIy.)
FOO
=> 7
FOO.freeze
=> 7
FOO += 2
(irb):5: warning: already
initialized constant FOO
=> 9
FOO
=> 9
Constants Aren't (Part Two)
Some are more equaI than others
Effectively:
== is the usuaI
(same vaIue)
.eql? is vaIue and class
(1 is Fixnum, 1.0 is FIoat)
.equal? is same object
It's reaIIy even hairier;
see docs on cIass Object
1 == 1.0
=> true
1.eql? 1.0
=> false
a = "foo"
b = "foo"
a == b
=> true
a.eql? b
=> true
a.equal? b
=> false
a.equal? a
=> true
Effectively:
=== is "case equaIity", as in
case statements. A better name
(IMHO) might be ".describes?",
or overIoad ".includes?"!
Again, it's reaIIy hairier; see docs
on Object.
Gets peopIe from Ianguages
where === is identity, or same
value and class.
=== != ==!
1 === 1
=> true
Fixnum === 1
=> true
1 === Fixnum
=> false
Class === Class
Object === Object
Class === Object
Object === Class
=> all true
Fixnum === Fixnum
=> false
(1..3) === 2
=> true
2 === (1..3)
=> false
&& has higher precedence
than =, so
x = true && false
means
x = (true && false)
and has lower precedence, so
x = true and false
means
(x = true) and false
Ruby StyIe Guide: Use &&/|| for
booIean expressions, [use]
and/or for controI fIow.
and != &&
x = true && false
=> false
x
=> false
# OK so far, but:
x = true and false
=> false
x
=> true
Why the mismatch?!
Return is faIse but
variabIe is true!
or != ||
|| has higher precedence
than =, so
x = false || true
means
x = (false || true)
or has lower precedence so
x = false or true
means
(x = false) or true
Ruby StyIe Guide: Use &&/|| for
booIean expressions, [use]
and/or for controI fIow.
x = false || true
=> true
x
=> true
# OK so far, but:
x = false or true
=> true
x
=> false
Why the mismatch?!
Return is true but
variabIe is faIse!
Whitespace-insensitive?
NOT ALWAYS!
With muItipIe args:
- No parens, no probIem.
- Parens w/o space, OK.
- Parens & space, NO!
Parser thinks it's an
expression, as 1st arg.
(1,2) not OK expression.
(AII work fine w/ 1 arg.)
def meth(arg1, arg2); end
meth 1, 2
=> nil
meth(1, 2)
=> nil
meth (1, 2)
syntax error, unexpected
',', expecting ')'
meth (l, 2)
^
Don't be so sensitive! (Part A)
def meth; 42; end
num = 21

=> 2

=> 2

=> 2

SyntaxError:
unterminated regexp
" " is an unended
regex or string! Ruby thinks
you might be giving an
argument to method .
GeneraI principIe: use
BALANCED whitespace;
both sides or neither.
Don't be so sensitive! (Part B)
" " makes Ruby
think you might be giving
an argument to method
.
Again: use BALANCED
whitespace; both sides or
neither.
(Credit: Josh Cheek)


=> 0

=> 0

=> 0

ArgumentError: wrong number
of arguments (l for 0)
Don't be so sensitive! (Part C)
dbl = ->(x) { x * 2 }
=> #<Proc:... (lambda)>
dbl = ->x{ x * 2 }
=> #<Proc:... (lambda)>
dbl = -> x { x * 2 }
=> #<Proc:... (lambda)>
two = -> { 2 }
=> #<Proc:... (lambda)>
dbl = -> (x) { x * 2 }
syntax error, unexpected
tLPAREN_ARG, expecting
keyword_do_LAMBDA or tLAMBEG
two = -> () { 2 }
same syntax error
"Stabby" Iambdas (1.9+)
Parentheses optionaI
Space before/after args
without parens, OK.
Space after parens, OK.
Again, space before
parens, NO!
Don't be so sensitive! (Part D)
When wiII it end? (Or start?)
str = "One\nTwo\nThree"
str =~ /^Two$/
=> 4
str =~ /\ATwo\Z/
=> nil
str =~ /\AOne/
=> 0
str =~ /Three\Z/
=> 8
In "standard" regexps:
^ is start and $ is end...
of the whole string.
In Ruby:
^ is start and $ is end...
of any line!
\A is start and \Z is end
of the whoIe string.
[].any?
=> false
[1].any?
=> true
[:foo, :bar].any?
=> true
# ok so far, BUT:
[nil].any?
=> false
[false].any?
=> false
[false, nil].any?
=> false
.any? does not mean
"any eIements?"!
With bIock: "do any
make bIock true?".
Without: "are any
truthy?" ImpIicit
bIock: { | o | o }
getting.any?
class Foo
attr_reader :value
def initialize(v)
value = v
end
def set_val(v)
@value = v
end
end
f = Foo.new(3)
f.value
=> nil # not 3?!
f.set_val 5
=> 5
f.value
=> 5
'Ang onto yer @!
Naked value becomes a
temporary local variabIe!
SoIution: remember the @! (Or
"self.".)
Gets peopIe from Java/C++,
not so much Python (which
needs "self." too).
"You keep on using that variabIe. I don't
think it means what you think it means."
Parent.set_value
Child.value

Why?! We didn't change
@@value! Or did we?
@@ vars are shared with
subcIasses... not just that
they exist, but the var itseIf.
Best to just forget they exist.
class Parent
@@value = 3
def self.value
@@value
end
def self.set_value
@@value = 5
end
end
class Child < Parent
@@value = 4
end
Oversharing
Superman vs. the InvisibIe Man
Child1.new.add 17, 76

Child2.new.add 1, 2, 3, 5

super with no arg Iist
sends what caIIer got
super with expIicit args
sends those args
to send NO args, use
empty parens: super()
class Parent
def add *args
args.inject :+
end
end
class Child1 < Parent
def add arg1, arg2
super arg1, arg2
end
end
class Child2 < Parent
def add *arg
super
end
end
class Parent
def initialize
puts "Parent init"
end
end
class NoInitChild < Parent
end
NoInitChild.new
Parent init
class NormalChild < Parent
def initialize
puts "NormalChild init"
end
end
NormalChild.new
"NormalChild init"
With init(iaIize) or without it
class SuperChild < Parent
def initialize
puts "SuperChild"
super
puts "init"
end
end
SuperChild.new
SuperChild
Parent init
init
Parent's initialize runs
automagicaIIy only if child
has none. EIse, parent's
must be called to run.
3.times do |loop_num|
sum ||= 0
sum += loop_num + 1
puts sum
end

for loop_num in 1..3


sum ||= 0
sum += loop_num
puts sum
end

Undef Leppard?
VariabIes decIared in blocks
passed to iterators (e.g.,
times or each) are undefined
at the top of each iteration!
Iterators caII the bIock
repeatedIy, so vars are out of
scope again after each caII.
Built-in looping constructs (e.
g., while or for) are OK.
Freeze (Ar)ray (Part I)
Freezing an array or hash
freezes it, not the items it
contains.
Changing Fixnum to new
vaIue means new object.
They can't be modified in
pIace. A Fixnum's
object_id = vaIue * 2 + 1.
THIS is why Fixnum
constants aren't!
ARR = [1,2,3]
=> [l, 2, 3]
ARR << 4
=> [l, 2, 3, 4]
ARR.freeze
=> [l, 2, 3, 4]
ARR << 5
RuntimeError: can't modify
frozen Array
ARR[0] += 2
RuntimeError: can't modify
frozen Array
1.object_id
=> 3
3.object_id
=> 7
ARR = ["one", "two",
"three"]
ARR.freeze
ARR << "four"
RuntimeError: can't modify
frozen Array
ARR[0] = "eno"
RuntimeError: can't modify
frozen Array
ARR[0].object_id
=> l234567890
ARR[0].reverse!
ARR
=> ["eno", "two", "three"]
ARR[0].object_id
=> l234567890
UnIike Fixnums, Strings
be modified in pIace.
Can thus modify a given
in an Array of Strings.
Freeze (Ar)ray (Part II)
WeII-known gotcha: bang
versions of methods
usuaIIy modify arg.
BUT ALSO:
Many return niI if no
change needed.
DO NOT RELY ON THEM
RETURNING SAME VALUE
AS NON-BANG VERSION!
To Bang Or Not To Bang
str = "foo"
[str.upcase, str]
=> ["FOO", "foo"]
[str.upcase!, str]
=> ["FOO", "FOO"]
arr = [1, 3, 3, 7]
[arr.uniq, arr]
=> [[l, 3, 7], [l, 3, 3,
7]]
[arr.uniq!, arr]
=> [[l, 3, 7], [l, 3, 7]]
str = "FOO"
[str.upcase!, str]
=> [nil, "FOO"]
arr = [1, 3, 7]
[arr.uniq!, arr]
=> [nil, [l, 3, 7]]
InitiaI vaIue given as
object is same object for
each sIot (if modded in
pIace, not reassigned as
with = or +=).
InitiaI vaIue given as
block gets evaluated
separately for each sIot.
Use this to create new
vars for each.
class Emp
attr_accessor :name, :pay
def set(n,p);
@name=n; @pay=p;
end
end
emps = Array.new(3, Emp.new)
emps[0].set('Andy', 80)
emps[1].set('Bob', 100)
e=emps[0]; "#{e.name}, #{e.pay}"
=> "Bob, l00"
# should have been "Andy, 80"!
emps = Array.new(3) { Emp.new }
emps[0].set("Andy", 80)
emps[1].set("Bob", 100)
e=emps[0]; "#{e.name}, #{e.pay}"
=> "Andy, 80"
An Array of New Gotchas
MostIy same probIem (and
soIution) as Arrays.
WARNING: creates new
object on Might
create excessive new
objects; ruins use of .size,
niI-checking, etc.
langs = Hash.new []
langs[:carol] << "C"
langs[:rachel] << "Ruby"
langs[:carol]
langs[:rachel]
=> both ["C", "Ruby"]
langs = Hash.new { |h, k|
h[k] = []
}
langs[:carol] << "C"
langs[:rachel] << "Ruby"
langs[:carol]
=> ["C"]
langs[:rachel]
=> ["Ruby"]
Making a Hash of it
/* JAVA: */
try {
throw new MyException("blah");
} catch(MyException e) {
fix_it();
}
# RUBY:
index = catch(:idx) {
arr.each_with_index do |v, i|
throw :idx, i if v == target
end
-1
}
begin
raise MyException.new "blah"
rescue MyException => e
fix_it
end
Rescue Me, Throw a Line, I'II Try to Catch It!
Ruby uses raise and
rescue for exceptions.
In Ruby, throw and catch
are NOT for exceptions!
They are advanced flow
control, to exit deep
nesting.
I'm Gonna Getcha Getcha Getcha Getcha!
- Watch out for these gotchas as you code.
- If Ruby behaves badIy, refer to these sIides.
- If it's not here, teII me; maybe I'II add it!
Add gotchas:
- to_s vs. to_str
- need to coordinate method_missing and responds_to_missing
- rescue from a StandardError, not an Exception
- instance_eval with caIIs in IocaI scope
- private isn't reaIIy, and not at aII w/ cIass methods
- lambda vs. proc vs. bIock vs. method vs. GodziIIa
- attribute=(val) wiII always return the argument
- Hash.new with bIock may ruin size/niI checks
- Proxies are aIways truthy, even if the target is not
- class Foo::Bar, defined outside Module Foo, won't see inside Foo
- In debugging, "next" skips over bIocks
Rails gotchas?
Screencast series?
The Someday-Maybe List
Questions/Contact/Etc.
questions.any? ; gotchas[:more].any?
Contact information and shameless plugs:
YourNameHere.2.TRex [at] Codosaur [dot] us
(yes, put your name there, without spaces, punctuation, etc.)
+1-571-308-6622
http://www.codosaur.us (Codosaurus, LLC main site)
http://blog.codosaur.us (code blog)
http://www.dare2XL.com (excellence blog)
http://linkedin.com/in/davearonson
http://facebook.com/dare2xl
@davearonson

Das könnte Ihnen auch gefallen