¿Por qué no debería envolver cada bloque en "try" - "catch"?


430

Siempre he creído que si un método puede arrojar una excepción, entonces es imprudente no proteger esta llamada con un bloque de prueba significativo.

Acabo de publicar ' SIEMPRE debe ajustar las llamadas que pueden intentar, atrapar bloques. "a esta pregunta y me dijeron que era un" consejo notablemente malo ". Me gustaría entender por qué.


44
Si supiera que un método arrojaría una excepción, lo habría corregido en primer lugar. Es porque no sé dónde y por qué el código arrojará excepciones que los atrapo en la fuente (cada método, un try-catch genérico). Por ejemplo, un equipo me dio una base de código con una base de datos anterior. Faltaban muchas columnas y se capturaron solo después de agregar try catch en la capa SQL. En segundo lugar, puedo registrar el nombre y el mensaje del método, para la depuración fuera de línea; de lo contrario, no sé dónde se originó.
Chakra

Respuestas:


340

Un método solo debería detectar una excepción cuando puede manejarlo de alguna manera sensata.

De lo contrario, pásalo, con la esperanza de que un método más arriba en la pila de llamadas pueda tener sentido.

Como otros han señalado, es una buena práctica tener un controlador de excepciones no controlado (con registro) en el nivel más alto de la pila de llamadas para garantizar que se registren los errores fatales.


12
También vale la pena señalar que hay costos (en términos de código generado) para los trybloques. Hay una buena discusión en "C ++ más eficaz" de Scott Meyers.
Nick Meyer

28
En realidad, los trybloques son gratuitos en cualquier compilador de C moderno, esa información está fechada por Nick. Tampoco estoy de acuerdo con tener un controlador de excepciones de nivel superior porque pierdes información de la localidad (el lugar real donde falló la instrucción).
Blindy

31
@Blindly: el controlador de excepción superior no está allí para manejar la excepción, pero de hecho para gritar en voz alta que hubo una excepción no controlada, dar su mensaje y finalizar el programa de una manera elegante (devuelva 1 en lugar de una llamada a terminate) . Es más un mecanismo de seguridad. Además, try/catchson más o menos gratuitos cuando no hay ninguna excepción. Cuando hay una que se propaga, consume tiempo cada vez que se arroja y se atrapa, por lo que una cadena de try/catchese solo lanzamiento no es gratuita.
Matthieu M.

17
No estoy de acuerdo con que siempre debas estrellarte en una excepción no detectada. El diseño moderno del software está muy dividido en compartimentos, entonces, ¿por qué debería castigar al resto de la aplicación (y, lo que es más importante, al usuario) solo porque hubo un error? Bloquear lo último que quiera hacer, al menos intente darle al usuario una pequeña ventana de código que le permita ahorrar trabajo incluso si no se puede acceder al resto de la aplicación.
Kendall Helmstetter Gelner

21
Kendall: si una excepción llega a un controlador de nivel superior, su aplicación está, por definición, en un estado indefinido. Aunque en algunos casos específicos puede ser útil preservar los datos del usuario (me viene a la mente la recuperación de documentos de Word), el programa no debe sobrescribir ningún archivo ni comprometerse con una base de datos.
Hugh Brackett

136

Como Mitch y otros declararon, no debe atrapar una excepción que no planea manejar de alguna manera. Debe considerar cómo la aplicación manejará sistemáticamente las excepciones cuando la esté diseñando. Esto generalmente lleva a tener capas de manejo de errores basadas en las abstracciones; por ejemplo, maneja todos los errores relacionados con SQL en su código de acceso a datos para que la parte de la aplicación que interactúa con objetos de dominio no esté expuesta al hecho de que es un DB debajo del capó en alguna parte.

Hay algunos olores de código relacionados que definitivamente desea evitar, además del olor de "atrapar todo en todas partes" .

  1. "catch, log, rethrow" : si desea un registro basado en el alcance, escriba una clase que emita una declaración de registro en su destructor cuando la pila se desenrolla debido a una excepción (ala std::uncaught_exception()). Todo lo que necesita hacer es declarar una instancia de registro en el ámbito que le interesa y, voila, tiene un registro y no es innecesario try/ catchlógico.

  2. "atrapar, lanzar traducido" : esto generalmente apunta a un problema de abstracción. A menos que esté implementando una solución federada en la que esté traduciendo varias excepciones específicas a una más genérica, probablemente tenga una capa innecesaria de abstracción ... y no diga que "podría necesitarla mañana" .

  3. "atrapar, limpiar, volver a lanzar" : esta es una de mis manías. Si ve mucho de esto, debe aplicar las técnicas de Adquisición de recursos es Inicialización y colocar la parte de limpieza en el destructor de una instancia de objeto de conserje .

Considero que el código que está lleno de try/ catchblocks es un buen objetivo para la revisión y refactorización de código. Indica que el manejo de excepciones no se comprende bien o que el código se ha convertido en un amœba y que necesita una refactorización grave.


66
El # 1 es nuevo para mí. +1 por eso. Además, me gustaría señalar una excepción común al n. ° 2, que es que si está diseñando una biblioteca a menudo querrá traducir excepciones internas en algo especificado por la interfaz de su biblioteca para reducir el acoplamiento (esto puede ser lo que quiere decir por "solución federada", pero no estoy familiarizado con ese término).
rmeador


1
# 2, donde no es un olor a código pero tiene sentido, puede mejorarse manteniendo la antigua excepción como anidada.
Deduplicador

1
Con respecto al n. ° 1: std :: uncaught_exception () le dice que hay una excepción no detectada en vuelo, pero AFAIK solo una cláusula catch () le permite determinar cuál es realmente esa excepción. Entonces, aunque puede registrar el hecho de que está saliendo de un ámbito debido a una excepción no detectada, solo un try / catch adjunto le permite registrar cualquier detalle. ¿Correcto?
Jeremy

@ Jeremy - tienes razón. Normalmente registro los detalles de la excepción cuando manejo la excepción. Tener un rastro de los marcos intermedios es muy útil. Por lo general, debe registrar el identificador de subproceso o algún contexto de identificación para correlacionar las líneas de registro. Utilicé una Loggerclase similar a la log4j.Loggerque incluye la ID del hilo en cada línea de registro y emití una advertencia en el destructor cuando una excepción estaba activa.
D.Shawley

48

Debido a que la siguiente pregunta es "He detectado una excepción, ¿qué hago después?" ¿Qué harás? Si no hace nada, se trata de un error de ocultación y el programa podría "simplemente no funcionar" sin ninguna posibilidad de encontrar lo que sucedió. Debes entender qué harás exactamente una vez que hayas detectado la excepción y solo si sabes.


29

No necesita cubrir cada bloque con try-catches porque un try-catch todavía puede detectar excepciones no controladas lanzadas en funciones más abajo en la pila de llamadas. Entonces, en lugar de que todas las funciones tengan un try-catch, puede tener uno en la lógica de nivel superior de su aplicación. Por ejemplo, puede haber una SaveDocument()rutina de nivel superior, que llama a muchos métodos que llaman a otros métodos, etc. Estos submétodos no necesitan sus propios intentos de captura, porque si arrojan, aún se captura por SaveDocument()la captura.

Esto es bueno por tres razones: es útil porque tiene un solo lugar para informar un error: los SaveDocument()bloques de captura. No es necesario repetir esto en todos los submétodos, y de todos modos es lo que desea: un solo lugar para darle al usuario un diagnóstico útil sobre algo que salió mal.

Dos, el guardado se cancela cada vez que se lanza una excepción. Con cada sub-método de prueba atractivo, si se produce una excepción, se obtiene en el bloque de captura de ese método, las hojas de ejecución de la función, y se lleva a cabo a través SaveDocument(). Si algo ya salió mal, es probable que desee detenerse allí.

Tres, todos sus submétodos pueden asumir que cada llamada tiene éxito . Si falla una llamada, la ejecución saltará al bloque catch y el código posterior nunca se ejecutará. Esto puede hacer que su código sea mucho más limpio. Por ejemplo, aquí hay códigos de error:

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

Así es como se podría escribir con excepciones:

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Ahora está mucho más claro lo que está sucediendo.

Tenga en cuenta que el código seguro de excepción puede ser más complicado de escribir de otras maneras: no desea perder ninguna memoria si se produce una excepción. Asegúrese de conocer RAII , contenedores STL, punteros inteligentes y otros objetos que liberan sus recursos en destructores, ya que los objetos siempre se destruyen antes de las excepciones.


2
Espléndidos ejemplos. Sí, captura lo más alto posible, en unidades lógicas, como alrededor de alguna operación 'transaccional' como cargar / guardar / etc. Nada parece peor que el código salpicado de repetitivo y redundante try:catch bloques que intentan marcar cada permutación ligeramente diferente de algún error con un mensaje ligeramente diferente, cuando en realidad todos deberían terminar de la misma manera: ¡falla de transacción o programa y salir! Si ocurre una falla digna de una excepción, apuesto a que la mayoría de los usuarios solo quieren recuperar lo que pueden o, al menos, quedarse solos sin tener que lidiar con 10 niveles de mensajes al respecto.
underscore_d

Solo quería decir que esta es una de las mejores explicaciones de "lanzar temprano, atrapar tarde" que he leído: conciso y los ejemplos ilustran perfectamente sus puntos. ¡Gracias!
corderazo00

27

Herb Sutter escribió sobre este problema aquí . Seguro que vale la pena leerlo.
Un adelanto:

"Escribir código seguro para excepciones se trata fundamentalmente de escribir 'try' y 'catch' en los lugares correctos". Discutir.

Dicho sin rodeos, esa declaración refleja un malentendido fundamental de la seguridad de excepción. Las excepciones son solo otra forma de informe de errores, y ciertamente sabemos que escribir código a prueba de errores no se trata solo de dónde verificar los códigos de retorno y manejar las condiciones de error.

En realidad, resulta que la seguridad de excepción rara vez se trata de escribir 'intentar' y 'atrapar', y cuanto más rara vez mejor. Además, nunca olvide que la seguridad de excepción afecta el diseño de una pieza de código; nunca es solo una idea de último momento que se puede adaptar con unas pocas declaraciones de captura adicionales como para sazonar.


15

Como se indicó en otras respuestas, solo debe detectar una excepción si puede hacer algún tipo de manejo de errores sensible para ello.

Por ejemplo, en la pregunta que generó su pregunta, el interlocutor pregunta si es seguro ignorar las excepciones lexical_castde un número entero a una cadena. Tal elenco nunca debería fallar. Si falló, algo salió terriblemente mal en el programa. ¿Qué podrías hacer para recuperarte en esa situación? Probablemente sea mejor dejar que el programa muera, ya que se encuentra en un estado en el que no se puede confiar. Por lo tanto, no manejar la excepción puede ser lo más seguro.


12

Si siempre maneja las excepciones inmediatamente en la llamada de un método que puede arrojar una excepción, entonces las excepciones se vuelven inútiles, y será mejor que use códigos de error.

Todo el punto de excepciones es que no necesitan ser manejados en todos los métodos en la cadena de llamadas.


9

El mejor consejo que he escuchado es que solo debería detectar excepciones en los puntos en los que puede hacer algo sensiblemente sobre la condición excepcional, y que "atrapar, registrar y liberar" no es una buena estrategia (si en ocasiones es inevitable en las bibliotecas).


2
@KeithB: Lo consideraría una segunda mejor estrategia. Es mejor si puede escribir el registro de otra manera.
David Thornley

1
@KeithB: Es una estrategia de "mejor que nada en una biblioteca". "Atraparlo, registrarlo, tratarlo adecuadamente" es mejor siempre que sea posible. (Sí, sé que no siempre es posible.)
Donal Fellows

6

Estoy de acuerdo con la dirección básica de su pregunta para manejar tantas excepciones como sea posible en el nivel más bajo.

Algunas de las respuestas existentes son como "No necesita manejar la excepción. Alguien más lo hará en la pila". Según mi experiencia, es una mala excusa para no pensar en el manejo de excepciones en el código actualmente desarrollado, lo que hace que el manejo de excepciones sea el problema de otra persona o posterior.

Ese problema crece dramáticamente en el desarrollo distribuido, donde es posible que necesite llamar a un método implementado por un compañero de trabajo. Y luego debe inspeccionar una cadena anidada de llamadas a métodos para averiguar por qué él / ella le está lanzando alguna excepción, que podría haberse manejado mucho más fácilmente en el método anidado más profundo.


5

El consejo que me dio una vez mi profesor de ciencias de la computación fue: "Use los bloques Try and Catch solo cuando no sea posible manejar el error usando medios estándar".

Como ejemplo, nos dijo que si un programa se topaba con un problema grave en un lugar donde no es posible hacer algo como:

int f()
{
    // Do stuff

    if (condition == false)
        return -1;
    return 0;
}

int condition = f();

if (f != 0)
{
    // handle error
}

Entonces deberías usar try, catch blocks. Si bien puede usar excepciones para manejar esto, generalmente no se recomienda porque las excepciones son costosas en cuanto al rendimiento.


77
Esa es una estrategia, pero muchas personas recomiendan nunca devolver códigos de error o estados de falla / éxito de las funciones, utilizando excepciones en su lugar. El manejo de errores basado en excepciones es a menudo más fácil de leer que el código basado en códigos de error. (Vea la respuesta de AshleysBrain a esta pregunta como ejemplo). Además, recuerde siempre que muchos profesores de informática tienen muy poca experiencia escribiendo código real.
Kristopher Johnson

1
-1 @Sagelika Su respuesta consiste en evitar excepciones, por lo que no es necesario intentarlo.
Vicente Botet Escriba

3
@Kristopher: Otras grandes desventajas para el código de retorno es que es realmente fácil olvidar verificar un código de retorno, y justo después de la llamada no es necesariamente el mejor lugar para manejar el problema.
David Thornley

ehh, depende, pero en muchos casos (dejando de lado a las personas que tiran cuando realmente no deberían), las excepciones son superiores a los códigos de retorno por muchas razones. En la mayoría de los casos, la idea de que las excepciones son perjudiciales para el rendimiento es muy grande [cita requerida]
subrayado_d

3

Si desea probar el resultado de cada función, use códigos de retorno.

El propósito de Excepciones es para que pueda probar los resultados MENOS a menudo. La idea es separar las condiciones excepcionales (inusuales, más raras) de su código más común. Esto mantiene el código ordinario más limpio y simple, pero aún así puede manejar esas condiciones excepcionales.

En un código bien diseñado, podrían lanzar funciones más profundas y podrían capturarse funciones más altas. Pero la clave es que muchas funciones "intermedias" estarán libres de la carga de manejar condiciones excepcionales. Solo tienen que estar "a salvo de excepciones", lo que no significa que deben atraparlos.


2

Me gustaría agregar a esta discusión que, desde C ++ 11 , tiene mucho sentido, siempre y cuando cada catchbloque sea rethrowla excepción hasta el punto en que pueda / deba manejarse. De esta forma, se puede generar una traza inversa . Por lo tanto, creo que las opiniones anteriores están en parte desactualizadas.

Uso std::nested_exceptionystd::throw_with_nested

En StackOverflow se describe aquí y aquí cómo lograr esto.

Como puede hacer esto con cualquier clase de excepción derivada, ¡puede agregar mucha información a dicha traza inversa! También puede echar un vistazo a mi MWE en GitHub , donde una traza inversa se vería así:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

2

Me dieron la "oportunidad" de rescatar varios proyectos y los ejecutivos reemplazaron a todo el equipo de desarrollo porque la aplicación tenía demasiados errores y los usuarios estaban cansados ​​de los problemas y los problemas. Todas estas bases de código tenían un manejo centralizado de errores en el nivel de la aplicación como se describe en la respuesta más votada. Si esa respuesta es la mejor práctica, ¿por qué no funcionó y permitió que el equipo de desarrollo anterior resolviera problemas? ¿Quizás a veces no funciona? Las respuestas anteriores no mencionan cuánto tiempo pasan los desarrolladores arreglando problemas individuales. Si el tiempo para resolver los problemas es la métrica clave, la mejor práctica es instrumentar el código con bloques try..catch.

¿Cómo solucionó mi equipo los problemas sin cambiar significativamente la interfaz de usuario? Simple, cada método se instrumentó con try..catch bloqueado y todo se registró en el punto de falla con el nombre del método, los valores de los parámetros del método concatenados en una cadena que se pasa junto con el mensaje de error, el mensaje de error, el nombre de la aplicación, la fecha, y versión Con esta información, los desarrolladores pueden ejecutar análisis sobre los errores para identificar la excepción que ocurre más. O el espacio de nombres con el mayor número de errores. También puede validar que un error que ocurre en un módulo se maneja adecuadamente y no es causado por múltiples razones.

Otro beneficio profesional de esto es que los desarrolladores pueden establecer un punto de interrupción en el método de registro de errores y con un punto de interrupción y un solo clic en el botón de depuración "salir", están en el método que falló con acceso completo al acceso real objetos en el punto de falla, convenientemente disponibles en la ventana inmediata. Hace que sea muy fácil depurar y permite arrastrar la ejecución al inicio del método para duplicar el problema y encontrar la línea exacta. ¿El manejo centralizado de excepciones permite a un desarrollador replicar una excepción en 30 segundos? No.

La afirmación "Un método solo debería detectar una excepción cuando puede manejarlo de una manera sensata". Esto implica que los desarrolladores pueden predecir o encontrarán todos los errores que puedan ocurrir antes del lanzamiento. Si esto fuera cierto en un nivel superior, el controlador de excepciones de la aplicación no sería necesario y no habría mercado para Elastic Search y logstash.

¡Este enfoque también permite a los desarrolladores encontrar y solucionar problemas intermitentes en la producción! ¿Desea depurar sin un depurador en producción? ¿O prefieres recibir llamadas y recibir correos electrónicos de usuarios molestos? Esto le permite solucionar problemas antes de que nadie más lo sepa y sin tener que enviar un correo electrónico, mensajería instantánea o Slack con soporte, ya que todo lo necesario para solucionar el problema está ahí. El 95% de los temas nunca necesitan ser reproducidos.

Para funcionar correctamente, debe combinarse con el registro centralizado que puede capturar el espacio de nombres / módulo, el nombre de clase, el método, las entradas y el mensaje de error y almacenarlos en una base de datos para que pueda agregarse y resaltar qué método falla más para que pueda ser arreglado primero.

A veces, los desarrolladores eligen lanzar excepciones desde un bloque catch, pero este enfoque es 100 veces más lento que el código normal que no arroja. Se prefiere la captura y liberación con registro.

Esta técnica se utilizó para estabilizar rápidamente una aplicación que fallaba cada hora para la mayoría de los usuarios en una compañía Fortune 500 desarrollada por 12 desarrolladores durante 2 años. Con esto, se identificaron, arreglaron, probaron y desplegaron 3000 excepciones diferentes en 4 meses. Esto promedia una solución cada 15 minutos en promedio durante 4 meses.

Estoy de acuerdo en que no es divertido escribir todo lo necesario para instrumentar el código y prefiero no mirar el código repetitivo, pero a la larga vale la pena agregar 4 líneas de código a cada método.


1
Envolver cada bloque parece una exageración. Rápidamente hace que su código se hinche y sea doloroso de leer. Registrar un seguimiento de pila desde una excepción en niveles superiores muestra dónde ocurrió el problema y que, combinado con el error en sí mismo, generalmente es suficiente información para continuar. Me gustaría saber dónde lo encontraste que no es suficiente. Solo para poder ganar la experiencia de otra persona.
user441521

1
"Las excepciones son de 100 a 1000 veces más lentas que el código normal y nunca deben volverse a lanzar", esa afirmación no es cierta en la mayoría de los compiladores y hardware modernos.
Mitch Wheat

Parece excesivo y requiere un poco de mecanografía, pero es la única forma de realizar análisis de excepciones para encontrar y corregir los errores más grandes primero, incluidos los errores intermitentes en la producción. El bloque catch maneja errores específicos si es necesario y tiene una sola línea de código que registra.
user2502917

No, las excepciones son muy lentas. La alternativa es códigos de retorno, objetos o variables. Vea esta publicación de desbordamiento de pila ... "las excepciones son al menos 30,000 veces más lentas que los códigos de retorno" stackoverflow.com/questions/891217/…
user2502917

1

Además del consejo anterior, personalmente uso algunos try + catch + throw; por la siguiente razón:

  1. En el límite de un codificador diferente, utilizo try + catch + throw en el código escrito por mí mismo, antes de que la persona que llama escriba la excepción que haya escrito, esto me da la oportunidad de saber que se produjo una condición de error en mi código, y este lugar está mucho más cerca del código que inicialmente arroja la excepción, cuanto más cerca, más fácil es encontrar la razón.
  2. En el límite de los módulos, aunque se puede escribir un módulo diferente en mi misma persona.
  3. Objetivo Learning + Debug, en este caso utilizo catch (...) en C ++ y catch (Exception ex) en C #, para C ++, la biblioteca estándar no arroja demasiadas excepciones, por lo que este caso es raro en C ++. Pero en lugar común en C #, C # tiene una gran biblioteca y una jerarquía de excepciones madura, el código de la biblioteca de C # arroja toneladas de excepciones, en teoría yo (y usted) debería conocer todas las excepciones de la función que llamó, y saber la razón / caso por qué estas excepciones se lanzan y saben cómo manejarlas (pasar o atrapar y manejar en su lugar) con gracia. Desafortunadamente, en realidad es muy difícil saber todo sobre las posibles excepciones antes de escribir una línea de código. Así que capto todo y dejo que mi código hable en voz alta iniciando sesión (en el entorno del producto) / cuadro de diálogo de afirmación (en el entorno de desarrollo) cuando se produce alguna excepción. De esta manera agrego código de manejo de excepciones progresivamente. Sé que conflit con buenos consejos, pero en realidad funciona para mí y no conozco una mejor manera para este problema.

1

Me siento obligado a agregar otra respuesta, aunque la respuesta de Mike Wheat resume bastante bien los puntos principales. Lo pienso así. Cuando tienes métodos que hacen varias cosas, multiplicas la complejidad, no la agregas.

En otras palabras, un método que está envuelto en un intento de captura tiene dos resultados posibles. Tiene el resultado sin excepción y el resultado con excepción. Cuando se trata con muchos métodos, esto explota exponencialmente más allá de la comprensión.

Exponencialmente, porque si cada método se ramifica de dos maneras diferentes, cada vez que llame a otro método, estará cuadrando el número anterior de resultados potenciales. Para cuando haya llamado a cinco métodos, tiene hasta 256 resultados posibles como mínimo. Compare esto con no hacer una prueba / captura en cada método y solo tiene un camino a seguir.

Así es básicamente como lo veo. Puede sentirse tentado a argumentar que cualquier tipo de ramificación hace lo mismo, pero try / catches es un caso especial porque el estado de la aplicación básicamente se vuelve indefinido.

En resumen, try / catch hace que el código sea mucho más difícil de comprender.


-2

No tiene necesidad de ocultar cada parte de su código dentro try-catch. El uso principal del try-catchbloque es el manejo de errores y errores / excepciones en su programa. Algún uso de try-catch-

  1. Puede usar este bloque donde desee manejar una excepción o simplemente puede decir que el bloque de código escrito puede generar una excepción.
  2. Si desea deshacerse de sus objetos inmediatamente después de su uso, puede usar el try-catchbloqueo.

1
"Si desea deshacerse de sus objetos inmediatamente después de su uso, puede usar el bloque try-catch". ¿Pretendía esto para promover RAII / vida mínima del objeto? Si es así, bueno, try/ catchestá completamente separado / ortogonal de eso. Si desea deshacerse de los objetos en un ámbito más pequeño, puede abrir uno nuevo { Block likeThis; /* <- that object is destroyed here -> */ }; no es necesario que lo envuelva try/ a catchmenos que realmente necesite catchalgo, por supuesto.
underscore_d

# 2 - Eliminar objetos (que fueron creados manualmente) en la excepción me parece extraño, esto puede ser útil en algunos idiomas, sin duda, pero generalmente lo haces en un intento / finalmente "dentro del bloque try / except", y no específicamente en el bloque excepto en sí mismo, ya que el objeto en sí puede haber sido la causa de la excepción en primer lugar y, por lo tanto, causar otra excepción y potencialmente un bloqueo.
TS
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.