2011-01-19 6 views
13

estoy leyendo desarrollo web ágil con rieles y he encontrado el siguiente código¿Es una buena práctica usar excepciones para el flujo de control en Ruby on Ruby on Rails? (. 4ª ed)

class ApplicationController < ActionController::Base 
    protect_from_forgery 

    private 

    def current_cart 
    Cart.find(session[:cart_id]) 
    rescue ActiveRecord::RecordNotFound 
    cart = Cart.create 
    session[:cart_id] = cart.id 
    cart 
    end 
end 

Puesto que soy un desarrollador de Java, mi comprensión de esa parte del código es más o menos lo siguiente:

private Cart currentCard(){ 
    try{ 
    return CartManager.get_cart_from_session(cartId) 
    }catch(RecordNotFoundEx e){ 
    Cart c = CartManager.create_cart_and_add_to_session(new Cart()) 
    return c;  
    } 
} 

que lo que me llama la atención es que el manejo de excepciones se utiliza para controlar el flujo normal de aplicación (falta de la compra es un comportamiento perfectamente normal cuando visitas de los usuarios de aplicaciones Depot por primera vez).

Si se toma un libro de Java, dicen que esto es algo muy malo, y por una buena razón: el manejo de errores no se debe utilizar como un reemplazo de las declaraciones de control, es un poco engañoso para aquellos que leer el código

¿Hay alguna buena razón por la que tal práctica esté justificada en Ruby (Rails)? ¿Es esta una práctica común en Ruby?

+5

No creo que hay cualquier razón especial que justifique esto aquí, y mi sensación es que la comunidad de Ruby está dividida sobre la regla de "no usar el manejo de excepciones para el flujo de control". Muchas personas te dicen que no lo hagas; muchos otros lo hacen de todos modos. (Algunos están de acuerdo con la regla, pero no siempre les importa, algunos piensan que la regla es tonta (o simplemente incorrecta), otros simplemente no piensan mucho al respecto). De cualquier manera, creo que su percepción de lo que está sucediendo en ese código es correcto. Si no te gusta, seguramente podrías volver a escribir usando un flujo de control explícito. – Telemachus

+0

@Telemachus: esa es una buena respuesta, ¡así que publícala! – tokland

+0

@Telemachus Su comentario es interesante. Estoy descubriendo en JRuby que la creación de rastreos de excepción a menudo está en la parte superior del uso de la CPU informada por un generador de perfiles. El benchmarking en MRI ruby ​​muestra un impacto de rendimiento menos drástico pero aún visible por el uso de excepciones. Mi especulación es que debido a que las excepciones incluyen retrocesos y otros datos, construir uno es simplemente costoso. Ruby también tiene catch/throw (control de flujo) separado de rescue/raise (excepciones), y he oído que catch/throw no es un problema de rendimiento en JRuby. – Patrick

Respuesta

9

Rails no es de ninguna manera consistente en el uso de excepciones. find generará una excepción si no se encuentra ningún objeto, pero para guardarlo puede elegir qué comportamiento desea. La forma más común es la siguiente:

if something.save 
    # formulate a reply 
else 
    # formulate an error reply, or redirect back to a form, or whatever 
end 

es decir save devuelve verdadero o falso. Pero también hay save! que genera una excepción (agregar un signo de admiración al final de un nombre de método es un rubyismo para mostrar que un método es "peligroso", o destructivo, o simplemente que tiene efectos secundarios, el significado exacto depende en el contexto).

Existe una razón válida por la cual find plantea una excepción: si una excepción RecordNotFound sube hasta el nivel superior, activará la representación de una página 404. Como normalmente no detecta estas excepciones manualmente (es raro que vea un rescue ActiveRecord::RecordNotFound en una aplicación de Rails), obtiene esta función de forma gratuita. Sin embargo, en algunos casos, desea hacer algo cuando un objeto no existe y, en esos casos, debe detectar la excepción.

No creo que el término "mejores prácticas" realmente signifique algo, pero es mi experiencia que las excepciones no se usan para controlar el flujo en Ruby más que en Java o en cualquier otro idioma que haya usado. Dado que Ruby no tiene excepciones marcadas, en general se ocupa de excepciones y mucho menos.

Al final todo depende de la interpretación. Dado que el caso de uso más común para find es recuperar un objeto para mostrarlo, y que la URL de ese objeto habrá sido generada por la aplicación, puede ser una circunstancia excepcional que el objeto no pueda ser encontrado. Significa que la aplicación genera enlaces a objetos que no existen o que el usuario editó la URL manualmente. También puede darse el caso de que el objeto haya sido eliminado, pero aún existe un enlace a él en un caché, o a través de un motor de búsqueda, diría que eso también es una circunstancia excepcional.

Este argumento se aplica a find cuando se utiliza como en su ejemplo, es decir, con una ID. Hay otras formas de find (incluidas las muchas find_by_* variantes) que realmente buscan, y aquellas que no generan excepciones (y luego está where en Rails 3, que reemplaza muchos de los usos de find en Rails 2).

No quiero decir que usar excepciones como control de flujo sea algo bueno, solo que no es necesariamente incorrecto que find genere excepciones y que su caso de uso particular no sea el caso común.

+0

¿Quizás quiso decir algo consistente en lugar de conciso? – noodl

+0

Sí, gracias. – Theo

+0

Theo, ¡muchas gracias por aclarar cosas! –

3

Para el caso de uso específico, sólo tiene que hacer

def current_cart 
    cart = Cart.find_or_create_by_id(session[:cart_id]) 
    session[:cart_id] = cart.id 
    cart 
end 

Esto puede parecer como que va a establecer una específica id para un nuevo registro que crea, pero desde id es siempre un atributo protegido, no lo hará establecerse para un nuevo registro. Obtendrá el registro con el id especificado, o si no existe, un nuevo registro con un nuevo id.

0

creo que me gustaría hacer algo como lo siguiente (incluyendo el almacenamiento en caché el carro actual, de modo que no se carga desde la base de datos cada vez que se llama al método):

def current_cart 
    @current_cart ||= begin 
    unless cart = Cart.find_by_id(session[:cart_id]) 
     cart = Cart.create 
     session[:cart_id] = cart.id 
    end 
    cart 
    end 
end