tutorial - ruby wikipedia
¿Cuáles son las Ruby Gotchas que debe advertirse a un novato? (25)
Recientemente aprendí el lenguaje de programación Ruby, y en general es un buen lenguaje. Pero me sorprendió bastante ver que no era tan simple como esperaba. Más precisamente, la "regla de la mínima sorpresa" no me pareció muy respetada (por supuesto, esto es bastante subjetivo). Por ejemplo:
x = true and false
puts x # displays true!
y el famoso:
puts "zero is true!" if 0 # zero is true!
¿Cuáles son los otros "errores" sobre los que advertirías a un novato de Ruby?
Presta atención a la notación de rango.
(¡Por lo menos, presta más atención de lo que inicialmente hice!)
Hay una diferencia entre 0..10
(dos puntos) y 0...10
(tres puntos).
Disfruto mucho a Ruby. Pero esta cosa de punto-punto contra punto-punto-punto me molesta. Creo que una "característica" sutil de doble sintaxis es:
- fácil de escribir mal, y
- fácil de perder con la vista mientras se mira el código
no debería ser capaz de causar devastadores errores "uno por uno" en mis programas.
Al llamar a super
sin argumentos, el método reemplazado se llama con los mismos argumentos que el método de anulación.
class A
def hello(name="Dan")
puts "hello #{name}"
end
end
class B < A
def hello(name)
super
end
end
B.new.hello("Bob") #=> "hello Bob"
Para llamar realmente super
sin argumentos, debes decir super()
.
Comprender la diferencia entre la clase de fecha y hora. Ambos son diferentes y han creado problemas al usarlos en rieles. La clase Time a veces entra en conflicto con otras bibliotecas de clase Time presentes en la biblioteca estándar de ruby / rails. En lo personal, me llevó mucho tiempo entender lo que estaba sucediendo exactamente en mi aplicación Rails. Más tarde, pensé cuando lo hice
Time.new
Se refería a una biblioteca en un lugar del que ni siquiera era consciente.
Lo siento si no tengo claro lo que quiero decir exactamente. Si otros han enfrentado problemas similares, por favor vuelva a explicar.
Creo que " and
" y " or
" son guiños a Perl, que es uno de los "padres" más obvios de Ruby (el otro más destacado es Smalltalk). Ambos tienen una precedencia mucho más baja (más baja que la asignación, de hecho, que es de donde proviene el comportamiento observado) que &&
y ||
cuáles son los operadores que deberías estar usando
Otras cosas a tener en cuenta que no son inmediatamente obvias:
Realmente no llama a métodos / funciones, aunque se ve de esa manera. En cambio, como en Smalltalk, envía un mensaje a un objeto. Así que el method_missing
es más como message_not_understood
.
some_object.do_something(args)
es equivalente a
some_object.send(:do_something, args) # note the :
Los símbolos son muy utilizados. Esas son las cosas con las que se empieza :
y no son inmediatamente obvias (bueno, no lo eran para mí), pero cuanto antes se familiarice con ellas, mejor.
Ruby es grande en el "tipado de patos", siguiendo el principio de que "si camina como un pato y grazna como un pato ..." que permite la sustitución informal de objetos con un subconjunto común de métodos sin ninguna herencia explícita o relación de mezcla.
Creo que siempre es bueno usar .length en cosas ... ya que el tamaño es compatible con casi todo y Ruby tiene tipos dinámicos, puedes obtener resultados realmente extraños llamando a .size cuando tienes el tipo incorrecto ... Preferiría obtener un NoMethodError: método indefinido `length '', por lo que generalmente nunca llamo tamaño a los objetos en Ruby.
me mordió más de una vez.
También recuerde que los objetos tienen identificadores, por lo que trato de no usar las variables call id o object_id solo para evitar confusiones. Si necesito una identificación en un objeto de los usuarios, lo mejor es llamarlo como id_usuario.
Solo mis dos centavos
De En Ruby, ¿por qué no foo = true unless defined?(foo)
hacer la tarea?
foo = true unless defined?(foo) #Leaves foo as nil
Porque foo
se define como nil
cuando se defined?(foo)
.
El siguiente código me sorprendió. Creo que es un juego peligroso: fácil de encontrar y difícil de depurar.
(1..5).each do |number|
comment = " is even" if number%2==0
puts number.to_s + comment.to_s
end
Esto imprime:
1
2 is even
3
4 is even
5
Pero si solo agrego comment =
anything antes del bloque ...
comment = nil
(1..5).each do |number|
comment = " is even" if number%2==0
puts number.to_s + comment.to_s
end
Entonces obtengo:
1
2 is even
3 is even
4 is even
5 is even
Básicamente, cuando una variable solo se define dentro de un bloque, se destruye al final del bloque y luego se restablece a nil
en cada iteración. Eso es generalmente lo que esperas. Pero si la variable se define antes del bloque, entonces la variable externa se usa dentro del bloque y, por lo tanto, su valor es persistente entre iteraciones.
Una solución sería escribir esto en su lugar:
comment = number%2==0 ? " is even" : nil
Creo que mucha gente (incluyéndome a mí) tiende a escribir " a = b if c
" en lugar de " a = (c ? b : nil)
", porque es más fácil de leer, pero obviamente tiene efectos secundarios.
En relación con la respuesta de to_foo
métodos de Ruby to_foo
insinúan cuán estricta será la conversión que harán.
Los cortos como to_i
, to_s
dicen que es flojo, y los convierte al tipo de destino, incluso si no se pueden representar con precisión en ese formato. Por ejemplo:
"10".to_i == 10
:foo.to_s == "foo"
Las funciones explícitas más largas como to_int
, to_s
significan que el objeto se puede representar de forma nativa como ese tipo de datos. Por ejemplo, la clase Rational
representa todos los números racionales, por lo que se puede representar directamente como un entero Fixnum (o Bignum) llamando a to_int
.
Rational(20,4).to_int == 5
Si no puede llamar al método más largo, significa que el objeto no se puede representar de forma nativa en ese tipo.
Básicamente, al convertir, si eres perezoso con los nombres de los métodos, Ruby se quedará perezosa con la conversión.
Este me hizo enojar una vez:
1/2 == 0.5 #=> false
1/2 == 0 #=> true
La iteración sobre los hashes de ruby no está garantizada en ningún orden en particular. (No es un error, es una característica)
Hash#sort
es útil si necesita un pedido en particular.
Pregunta relacionada: ¿Por qué la matriz de Ruby de 1000 pares de clave y valor de hash siempre está en un orden particular?
Los bloques y métodos devuelven el valor de la última línea de forma predeterminada. Agregar sentencias puts
al final para fines de depuración puede causar efectos secundarios desagradables
Los métodos se pueden redefinir y pueden convertirse en un rasguño mental hasta que descubras la causa. ( Es cierto que este error es probablemente un poco "más difícil" de detectar cuando una acción del controlador Ruby on Rails se redefine por error ).
#demo.rb
class Demo
def hello1
p "Hello from first definition"
end
# ...lots of code here...
# and you forget that you have already defined hello1
def hello1
p "Hello from second definition"
end
end
Demo.new.hello1
Correr:
$ ruby demo.rb
=> "Hello from second definition"
Pero llámalo con advertencias habilitadas y puedes ver el motivo:
$ ruby -w demo.rb
demo.rb:10: warning: method redefined; discarding old hello1
=> "Hello from second definition"
Los principiantes tendrán problemas con los métodos de igualdad :
- a == b : verifica si a y b son iguales. Este es el más útil.
- a.eql? b : también verifica si a y b son iguales, pero a veces es más estricto (podría verificar que a y b tienen el mismo tipo, por ejemplo). Se usa principalmente en Hashes.
- a.equal? b : comprueba si a y b son el mismo objeto (comprobación de identidad).
- a === b : utilizado en las declaraciones de casos (lo leí como " a coincide con b ").
Estos ejemplos deberían aclarar los primeros 3 métodos:
a = b = "joe"
a==b # true
a.eql? b # true
a.equal? b # true (a.object_id == b.object_id)
a = "joe"
b = "joe"
a==b # true
a.eql? b # true
a.equal? b # false (a.object_id != b.object_id)
a = 1
b = 1.0
a==b # true
a.eql? b # false (a.class != b.class)
a.equal? b # false
Tenga en cuenta que == , eql? e igual? siempre debe ser simétrica: si a == b entonces b == a.
También tenga en cuenta que == y eql? Ambos se implementan en la clase Objeto como alias para igualar? , entonces si creas una nueva clase y quieres == y eql? para significar algo más que identidad simple, entonces necesitas anularlos a ambos. Por ejemplo:
class Person
attr_reader name
def == (rhs)
rhs.name == self.name # compare person by their name
end
def eql? (rhs)
self == rhs
end
# never override the equal? method!
end
El método === se comporta de manera diferente. En primer lugar, no es simétrico (a === b no implica que b === a). Como dije, puedes leer a === b como "a coincide con b". Aquí están algunos ejemplos:
# === is usually simply an alias for ==
"joe" === "joe" # true
"joe" === "bob" # false
# but ranges match any value they include
(1..10) === 5 # true
(1..10) === 19 # false
(1..10) === (1..10) # false (the range does not include itself)
# arrays just match equal arrays, but they do not match included values!
[1,2,3] === [1,2,3] # true
[1,2,3] === 2 # false
# classes match their instances and instances of derived classes
String === "joe" # true
String === 1.5 # false (1.5 is not a String)
String === String # false (the String class is not itself a String)
La declaración de caso se basa en el método === :
case a
when "joe": puts "1"
when 1.0 : puts "2"
when (1..10), (15..20): puts "3"
else puts "4"
end
es equivalente a esto:
if "joe" === a
puts "1"
elsif 1.0 === a
puts "2"
elsif (1..10) === a || (15..20) === a
puts "3"
else
puts "4"
end
Si define una nueva clase cuyas instancias representen algún tipo de contenedor o rango (si tiene algo así como un método de inclusión? O una coincidencia ), entonces puede serle útil anular el método === de esta manera:
class Subnet
[...]
def include? (ip_address_or_subnet)
[...]
end
def === (rhs)
self.include? rhs
end
end
case destination_ip
when white_listed_subnet: puts "the ip belongs to the white-listed subnet"
when black_listed_subnet: puts "the ip belongs to the black-listed subnet"
[...]
end
Si declaras un setter (aka mutator) usando attr_writer
o attr_accessor
(o def foo=
), ten cuidado de llamarlo desde el interior de la clase. Como las variables se declaran implícitamente, el intérprete siempre debe resolver que foo = bar
declare una nueva variable llamada foo, en lugar de llamar al método self.foo=(bar)
.
class Thing
attr_accessor :foo
def initialize
@foo = 1 # this sets @foo to 1
self.foo = 2 # this sets @foo to 2
foo = 3 # this does *not* set @foo
end
end
puts Thing.new.foo #=> 2
Esto también se aplica a los objetos Rails ActiveRecord, que obtienen accesoadores definidos en función de los campos de la base de datos. Como ni siquiera son variables de instancia de estilo @, la forma correcta de establecer esos valores individualmente es con self.value = 123
o self[''value''] = 123
.
Soy nuevo en ruby, y en mi primera ronda me encontré con un problema relacionado con el cambio de flotantes / cadenas a un número entero. Empecé con las carrozas y codifiqué todo como f.to_int . Pero cuando continué y utilicé el mismo método para las cadenas, sentí una curva cuando se trataba de ejecutar el programa.
Aparentemente, una cadena no tiene un método to_int , pero los flotantes y los ints lo hacen.
irb(main):003:0* str_val = ''5.0''
=> "5.0"
irb(main):006:0> str_val.to_int
NoMethodError: undefined method `to_int'' for "5.0":String
from (irb):6
irb(main):005:0* str_val.to_i
=> 5
irb(main):007:0> float_val = 5.0
=> 5.0
irb(main):008:0> float_val.to_int
=> 5
irb(main):009:0> float_val.to_i
=> 5
irb(main):010:0>
Los paréntesis arbitrarios también me arrojaron al principio. Vi un código con y sin él. Me tomó un tiempo darme cuenta de que cualquiera de los estilos es aceptado.
Tuve muchos problemas para comprender las variables de clase, los atributos de clase y los métodos de clase. Este código podría ayudar a un novato:
class A
@@classvar = "A1"
@classattr = "A2"
def self.showvars
puts "@@classvar => "+@@classvar
puts "@classattr => "+@classattr
end
end
A.showvars
# displays:
# @@classvar => A1
# @classattr => A2
class B < A
@@classvar = "B1"
@classattr = "B2"
end
B.showvars
# displays:
# @@classvar => B1
# @classattr => B2
A.showvars
# displays:
# @@classvar => B1 #Class variables are shared in a class hierarchy!
# @classattr => A2 #Class attributes are not
Tuve problemas con mixins que contienen métodos de instancia y métodos de clase. Este código podría ayudar a un novato:
module Displayable
# instance methods here
def display
puts name
self.class.increment_displays
end
def self.included(base)
# This module method will be called automatically
# after this module is included in a class.
# We want to add the class methods to the class.
base.extend Displayable::ClassMethods
end
module ClassMethods
# class methods here
def number_of_displays
@number_of_displays # this is a class attribute
end
def increment_displays
@number_of_displays += 1
end
def init_displays
@number_of_displays = 0
end
# this module method will be called automatically
# after this module is extended by a class.
# We want to perform some initialization on a
# class attribute.
def self.extended(base)
base.init_displays
end
end
end
class Person
include Displayable
def name; @name; end
def initialize(name); @name=name; end
end
puts Person.number_of_displays # => 0
john = Person.new "John"
john.display # => John
puts Person.number_of_displays # => 1
jack = Person.new "Jack"
jack.display # => Jack
puts Person.number_of_displays # => 2
Al principio, pensé que podría tener módulos con métodos de instancia y métodos de clase simplemente haciendo esto:
module Displayable
def display
puts name
self.class.increment_displays
end
def self.number_of_displays # WRONG!
@number_of_displays
end
[...]
end
Lamentablemente, el método number_of_displays nunca se incluirá ni se ampliará porque es un "método de clase de módulo". Solo los "métodos de instancia de módulo" pueden incluirse en una clase (como métodos de instancia) o extenderse a una clase (como métodos de clase). Esta es la razón por la cual necesita poner los métodos de instancia de su mezcla en un módulo, y los métodos de clase de su mezcla en otro módulo (generalmente pone los métodos de clase en un submódulo "ClassMethods"). Gracias al método mágico incluido , puede hacer que sea fácil incluir métodos de instancia y métodos de clase en una sola llamada "incluir Displayable" (como se muestra en el ejemplo anterior).
Este mixin contará cada pantalla por clase . El contador es un atributo de clase, por lo que cada clase tendrá su propio (su programa probablemente fallará si obtiene una nueva clase de la clase Person ya que el contador @number_of_displays para la clase derivada nunca se inicializará). Es posible que desee reemplazar @number_of_displays por @@ number_of_displays para convertirlo en un contador global. En este caso, cada jerarquía de clases tendrá su propio contador. Si desea un contador global y exclusivo, probablemente deba convertirlo en un atributo de módulo.
Todo esto definitivamente no fue intuitivo para mí cuando comencé con Ruby.
Sin embargo, todavía no puedo entender cómo hacer que algunos de estos métodos de mixin sean privados o protegidos (solo los métodos display y number_of_displays deberían incluirse como métodos públicos).
Una cosa que aprendí fue usar el operador || = cuidadosamente. y tenga especial cuidado si se trata de booleanos. usualmente usaba un || = b como un catch all para dar ''a'' un valor predeterminado si todo lo demás fallaba y ''a'' permanecía nulo. pero si a es falso yb es verdadero, entonces a se le asignará verdadero.
Uno que me sorprendió en el pasado es que la secuencia de escape del carácter de nueva línea ( /n
), entre otros, no está respaldada por cadenas dentro de comillas simples. La barra invertida en sí se escapó. Debe usar comillas dobles para que el escapado funcione como se espera.
Del artículo:
- Los nombres que comienzan con una letra mayúscula se tratan como constantes, por lo que las variables locales deben comenzar con una letra minúscula.
- Los caracteres
$
y@
no indican el tipo de datos variables como en Perl, sino que funcionan como operadores de resolución de alcance. - Para indicar los números de punto flotante, uno debe seguir con un dígito cero (
99.0
) o una conversión explícita (99.to_f
). No es suficiente agregar un punto (99.
), porque los números son susceptibles a la sintaxis del método. - La evaluación booleana de datos no booleanos es estricta:
0
,""
y[]
se evalúan comotrue
. En C, la expresión0 ? 1 : 0
0 ? 1 : 0
evalúa a0
(es decir, falso). En Ruby, sin embargo, rinde1
, ya que todos los números se evalúan comotrue
; solonil
yfalse
evalúan afalse
. Un corolario de esta regla es que los métodos de Ruby por convención, por ejemplo, las búsquedas de expresiones regulares, devuelven números, cadenas, listas u otros valores no falsos en caso de éxito, peronil
en caso de error (por ejemplo, falta de coincidencia). Esta convención también se usa en Smalltalk, donde solo se pueden usar los objetos especialestrue
yfalse
en una expresión booleana. - Las versiones anteriores a 1.9 carecen de un tipo de datos de caracteres (compárense con C, que proporciona el tipo de caracteres para los caracteres). Esto puede causar sorpresas al cortar cadenas:
"abc"[0]
produce97
(un entero, que representa el código ASCII del primer carácter en la cadena); para obtener"a"
use"abc"[0,1]
(una subcadena de longitud 1) o"abc"[0].chr
. El
statement until expression
notaciónstatement until expression
, a diferencia de los enunciados equivalentes de otros idiomas (por ejemplo,do { statement } while (not(expression));
en C / C ++ / ...), en realidad nunca ejecuta el enunciado si la expresión ya estrue
. Esto se debe a que elstatement until expression
es en realidad azúcar sintáctico sobreuntil expression statement end
, cuyo equivalente en C / C ++ es
while (not(expression)) statement;
al igual que lastatement if expression
es equivalente aif expression statement end
Sin embargo, la notación
begin statement end until expression
en Ruby, de hecho, ejecutará la declaración una vez, incluso si la expresión ya es verdadera.
- Como las constantes son referencias a objetos, cambiar a qué se refiere una constante genera una advertencia, pero modificar el objeto no lo hace. Por ejemplo,
Greeting << " world!" if Greeting == "Hello"
Greeting << " world!" if Greeting == "Hello"
no genera un error o advertencia. Esto es similar a las variablesfinal
en Java, pero Ruby también tiene la funcionalidad de "congelar" un objeto, a diferencia de Java.
Algunas características que difieren notablemente de otros idiomas:
Los operadores habituales para las expresiones condicionales, yy
or
, no siguen las reglas de precedencia normales:and
no se vinculan más estrictamente queor
. Ruby también tiene operadores de expresión||
y & que funcionan como se esperaba.def
dentro dedef
no hace lo que un programador de Python podría esperar:def a_method x = 7 def print_x; puts x end print_x end
Esto da un error acerca de que
x
no está definido. Necesita usar unProc
.
Características del lenguaje
- La omisión de paréntesis alrededor de los argumentos del método puede conducir a resultados inesperados si los métodos toman múltiples parámetros. Los desarrolladores de Ruby han declarado que la omisión de paréntesis en los métodos de múltiples parámetros puede no estar permitida en futuras versiones de Ruby; el actual (noviembre de 2007) intérprete de Ruby lanza una advertencia que alienta al escritor a no omitir
()
, para evitar el significado ambiguo del código. No usar()
sigue siendo una práctica común, y puede ser especialmente útil usar Ruby como un lenguaje de programación específico de dominio legible para el ser humano, junto con el método llamadomethod_missing()
.
1..5.each {|x| puts x}
no funciona Tienes que poner el rango entre paréntesis, como
(1..5).each {|x| puts x}
así que no creo que estés llamando a 5.each
. Creo que este es un tema de precedencia, al igual que el x = true and false
gotcha.
x = (true and false) # x is false
0 y '''' son verdaderos, como usted señaló.
Puede tener un método y un módulo / clase con el mismo nombre (lo cual tiene sentido, porque el método realmente se agrega a Object y, por lo tanto, tiene su propio espacio de nombres).
No hay herencia múltiple, pero con frecuencia los "módulos mixin" se usan para agregar métodos comunes a múltiples clases.
Los bloques son realmente importantes de entender, se usan en todas partes.
No necesita paréntesis alrededor de los parámetros del método. Si los usa o no depende de usted. Algunos dicen que siempre debes usarlos .
Use raise and rescue para manejo de excepciones, no throw and catch.
Puedes usar
;
pero no tienes que hacerlo a menos que quieras poner varias cosas en una línea.
Monkey parches . Ruby tiene clases abiertas, por lo que su comportamiento se puede cambiar dinámicamente en el tiempo de ejecución ...
Los objetos pueden responder a métodos indefinidos si se ha anulado
method_missing
osend
. Esto explota la invocación de método basada en mensajes de Ruby. El sistema ActiveRecord Rails usa esto con gran efecto.