2010-04-09 8 views
24

Me preguntaba si la gente compartiría sus mejores prácticas/estrategias en el manejo de excepciones & errores. Ahora no estoy preguntando cuándo lanzar una excepción (se ha respondido completamente aquí: SO: When to throw an Exception). Y no estoy usando esto para mi flujo de aplicaciones, pero existen excepciones legítimas que ocurren todo el tiempo. Por ejemplo, el más popular sería ActiveRecord :: RecordNotFound. ¿Cuál sería la mejor manera de manejarlo? ¿La manera SECA?¿Cuál es la mejor estrategia para manejar excepciones y errores en Rails?

Ahora mismo estoy comprobando mucho en mi controlador, por lo que si Post.find(5) devuelve Nil, lo verifico y lanzo un mensaje intermitente. Sin embargo, aunque esto es muy granular, es un poco engorroso en el sentido de que necesito verificar excepciones como esa en cada controlador, mientras que la mayoría son esencialmente iguales y tienen que ver con registros no encontrados o registros no encontrados, tales como Post.find(5) no encontrado o si está intentando mostrar comentarios relacionados con la publicación que no existe, eso arrojaría una excepción (algo como Post.find(5).comments[0].created_at)

Sé que puede hacer algo como esto en ApplicationController y sobrescribirlo más tarde en un controlador/método particular para obtener un soporte más granular, ¿sería una forma adecuada de hacerlo?

class ApplicationController < ActionController::Base 
    rescue_from ActiveRecord::RecordInvalid do |exception| 
     render :action => (exception.record.new_record? ? :new : :edit) 
    end 
end 

También esto funcionaría en caso de que no se encuentra Post.find(5), pero ¿qué pasa Post.find(5).comments[0].created_at - Me refiero a que no puedo lanzar una excepción soplado completo si existe el cargo, pero no tiene comentarios, ¿verdad?

Para resumir, hasta ahora estaba haciendo muchas comprobaciones manuales usando if/else/unless o case/when (y confieso ocasionalmente begin/rescue) y buscando nil? ¿o vacío ?, etc., pero tiene que haber una mejor manera de hacerlo.

Respuestas:

@Milan: Hola Milán gracias por una respuesta - Estoy de acuerdo con lo que ha dicho, y creo que un mal uso de la palabra es una excepción. Lo que quería decir es que en este momento hago un montón de cosas como:

if Post.exists?(params[:post_id]) 
    @p = Post.find(params[:post_id]) 
else 
    flash[:error] = " Can't find Blog Post" 
end 

Y hago un montón de este tipo de "manejo de excepciones", trato de evitar el uso de comenzar/rescate. Pero me parece que este es un resultado/verificación/situación lo suficientemente común como para que haya una manera más SECA de hacer esto, ¿no? ¿Cómo harías este tipo de cheque?

¿También cómo lo manejaría en este caso? Digamos que desea mostrar los comentarios fecha de creación, en su opinión:

Last comment for this post at : <%= @post.comments[0].created_at %> 

Y este post no tiene ningún comentario. Usted puede hacer

Last comment for this post at : <%= @post.comments.last.created_at unless @post.comments.empty? %> 

que podría hacer una comprobación en el controlador. Etc. Hay varias formas de hacerlo. Pero, ¿cuál es la "mejor" forma de manejar esto?

Respuesta

14

El hecho de que haga mucho de comprobación manual de excepciones sugiere que simplemente no las está utilizando correctamente. De hecho, ninguno de tus ejemplos es excepcional.

En cuanto a la publicación no existente, debe esperar que los usuarios de la API (por ejemplo, un usuario que utiliza su web a través del navegador) soliciten publicaciones inexistentes.

Su segundo ejemplo (Post.find (5) .comments [0] .created_at) tampoco es excepcional. Algunas publicaciones simplemente no tienen comentarios y lo sabes desde el principio. Entonces, ¿por qué debería lanzar una excepción?

Lo mismo ocurre con el ejemplo ActiveRecord :: RecordInvalid. Simplemente no hay razón para manejar este caso por medio de una excepción. Que un usuario ingrese datos no válidos en un formulario es algo bastante habitual y no tiene nada de excepcional.

Usar el mecanismo de excepción para este tipo de situaciones puede ser muy conveniente en algunas situaciones, pero es incorrecto por las razones mencionadas anteriormente.

Dicho esto, no significa que no pueda SECAR el código que encapsula estas situaciones. Existe una gran posibilidad de que puedas hacerlo, al menos hasta cierto punto, ya que estas son situaciones bastante comunes.

Entonces, ¿qué pasa con las excepciones? Bueno, la primera regla realmente es: úsalos tan poco como sea posible.

Si realmente necesitan para su uso hay dos tipos de excepciones en general (como yo lo veo):

  1. excepciones que no se rompen flujo de trabajo general del usuario dentro de su aplicación (imaginar una excepción dentro de la rutina de generación de miniaturas de imágenes de perfil) y puede ocultarlas al usuario o simplemente notificarlo sobre el problema y sus consecuencias cuando sea necesario

  2. excepciones que impiden al usuario utilizar la aplicación. Estos son el último recurso y deben manejarse a través del error interno del servidor 500 en las aplicaciones web.

que tienden a utilizar el método rescue_from en el ApplicationController sólo para este último, ya que hay lugares más adecuados para la primera clase y el ApplicationController como el más alto de las clases de controlador parece ser el lugar adecuado para caer volver a en tales circunstancias (aunque hoy en día algún tipo de middleware Rack podría ser un lugar más apropiado para poner tal cosa).

- EDITAR -

La parte constructiva:

En cuanto a lo primero, mi consejo sería comenzar a usar find_by_id en lugar de encontrar, ya que no una excepción pero devuelve nil si no tiene éxito. Su código sería algo así como:

unless @p = Post.find_by_id(params[:id]) 
    flash[:error] = "Can't find Blog Post" 
end 

que habla mucho menos.

Otra expresión común para DRYing en este tipo de situaciones es utilizar el controlador before_filters para establecer las variables más utilizadas (como @p en este caso).Después de eso, el controlador podría tener el siguiente

controller PostsController 
    before_filter :set_post, :only => [:create, :show, :destroy, :update] 

    def show 
     flash[:error] = "Can't find Blog Post" unless @p 
    end 

private 

    def set_post 
    @p = Post.find_by_id(params[:id]) 
    end 

end 

En cuanto a la segunda situación (no existente último comentario), una solución obvia a este problema es mover todo el asunto en un ayudante:

# This is just your way of finding out the time of the last comment moved into a 
# helper. I'm not saying it's the best one ;) 
def last_comment_datetime(post) 
    comments = post.comments 
    if comments.empty? 
    "No comments, yet." 
    else 
    "Last comment for this post at: #{comments.last.created_at}" 
    end 
end 

Luego, en sus puntos de vista, que le acaba de llamar

<%= last_comment_datetime(post) %> 

de esta manera el caso extremo (post sin comentarios) serán tratados en su propio lugar y no va a saturar la vista.

Lo sé, ninguno de estos sugiere ningún patrón para el manejo de errores en Rails, pero quizás con algunas refactorizaciones como éstas, encontrará que gran parte de la necesidad de algún tipo de estrategia para el manejo de excepciones/errores simplemente desaparece .

+0

favor - ver mi respuesta dada en la respuesta - que no puedo publicar código correctamente sangría en los comentarios :-) – konung

+0

Hey Nick, lo siento por sonar tan Ranty. Simplemente no tuve tiempo suficiente para agregar una parte constructiva a mi respuesta. Ahora, está allí. Espero que toda la respuesta sea más útil ahora. –

+0

No hay problema, sin ofenderse, mi pregunta tampoco era muy clara, hasta que lo limpie. Me diste un par de buenos consejos: esperaré un poco, tal vez alguien tenga algunas sugerencias más y luego marcaré esto como respondido. Gracias por una respuesta detallada. :-) – konung

1

Las excepciones son por circunstancias excepcionales. La mala entrada del usuario generalmente no es excepcional; en todo caso, es bastante común. Cuando tiene una circunstancia excepcional, quiere darse la mayor cantidad de información posible. Según mi experiencia, la mejor manera de hacerlo es mejorar religiosamente el manejo de excepciones en función de la experiencia de depuración. Cuando te encuentras con una excepción, lo primero que debes hacer es escribir una prueba unitaria para ello. Lo segundo que debe hacer es determinar si hay más información que se puede agregar a la excepción. En este caso, más información generalmente toma la forma de capturar la excepción más arriba en la pila y manejarla o lanzar una nueva excepción más informativa que tiene el beneficio de un contexto adicional. Mi regla personal es que no me gusta capturar excepciones de más de tres niveles en la pila. Si una excepción tiene que viajar más allá de eso, debe detectarla antes.

En cuanto a la exposición de errores en la interfaz de usuario, if/case las declaraciones son totalmente correctas siempre que no las anide demasiado profundamente. Ahí es cuando este tipo de código se vuelve difícil de mantener. Puedes abstraer esto si se convierte en un problema.

Por ejemplo:

def flash_assert(conditional, message) 
    return true if conditional 
    flash[:error] = message 
    return false 
end 

flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return 
Cuestiones relacionadas