genbetadev / Genbeta-Dev-Engine

Desarrollo de un Game Engine básico sobre C++ y SFML 2.1
MIT License
63 stars 32 forks source link

Creación de jerarquìa de excepciones #75

Open danigomez opened 10 years ago

danigomez commented 10 years ago

Hola gente! Estaba pensando que serai conveniente armar una jerarquia de excepciones, en el que cada una haga el loggin del error que corresponda y termine la ejecución del engine. También se que están los assert, yo la verdad nunca los he usado, ya que vengo del mundo Java, por lo que no puedo saber cual es más conveniente. Pero al haber usado excepciones, me parecen realmente prácticas y claras en el error que están indicando, qué opinan??

adrigm commented 10 years ago

Ese es un buen tema a discutir, aserciones o exepciones, yo soy más partidario de las aserciones ya que suele sen más fácil de implementar que un sistema de exepciones decente, pero abro el debate.

rickyah commented 10 years ago

En general las excepciones tienen un coste y este es relativamente elevado. Afortunadamente los compiladores actuales ya no añaden sobrecarga si no se lanzan excepciones, pero aún así lanzarlas es un problema, tanto en java como en c++

Los asserts son completamente distintos a las excepciones: con una assert el programa finaliza irremediablemente, de una excepción podrías recuperarte. Yo evitaría las excepciones completamente en código crítico y como método de depuración para obtener información cuando algo falla y no debería. Esto es IMHO la aproximación que hay que seguir dentro del bucle de juego. En el resto de operaciones, como inicialización, carga de recursos, etc no me parece mal usar excepciones.

InExtremaRes commented 10 years ago

Así es. En mi opinión, las excepciones y las aserciones están hechas para manejar casos diferentes.

Las aserciones abortan la ejecución inmediatamente y con un mensaje que nada importa al usuario y por tanto, están más pensadas para manejar los errores de los programadores y no de los programas. Parte de esto se explica en el por qué las aserciones suelen quitarse en builds tipo Release. Por ejemplo, si un método privado de una clase debe llamarse con un parámetro no nulo, es una excelente opción usar una aserción aquí, ya que si el método es privado, solo será llamado internamente por la clase; si el parámetro dado es nulo indica un claro error del programador.

Por otro lado las excepciones, como su nombre indica, manejan casos excepcionales que no deberían ocurrir, pero ocurren. A menudo se utilizan para reportar casos como la falta de memoria o que un determinado recurso de red no esté disponible cuando se supone que debiera. ¿Cómo se manejan estos casos? Normalmente, la idea de la excepción es usarla para subir de nivel o de contexto, porque si en el contexto actual tenemos toda la información necesaria, la excepción no es necesaria. Pero normalmente esto no es así, y es necesario avisar a los contextos superiores para que tomen una decisión adecuada. Sería importante aclarar cuándo una situación se considera excepcional y cuando no. Por ejemplo, que el usuario de un programa ingrese una entrada inválida, no es una situación excepcional; aun así he visto código donde estas situaciones se manejan con excepciones.

Ahora bien, tal como comenta @rickyah, usar excepciones tienen un costo extra, tanto en espacio como en tiempo. Aunque es cierto que el costo de tiempo es casi imperceptible mientras no sé lance una excepción (en la mayoría de los compiladores), lanzarla si es bastante costoso, pero si realmente estamos en una situación excepcional es porque el funcionamiento normal del programa se ve interrumpido y el costo no debiera ser preocupante. En cualquier caso, uno podría simplemente crear un método o macro similar a assert, pero que permita escribir un lindo mensaje de error que explique la razón de la falla y luego abortar la ejecución.

Por otro lado, hay que recordar que no estamos construyendo un juego, si no un motor, lo cual significa que nuestro código será usado por otro programador y que es este programador quien debe tomar las decisiones de lo que hace su programa en caso de error. Por ejemplo, si la clase que analiza el archivo INI falla porque el archivo es incorrecto, parece natural avisar al usuario con un mensaje y abortar la carga, pero el programador del juego podría decir «no, yo tengo guardado internamente una versión por defecto del archivo de configuración, si el otro no se puede cargar, que cargue este y continúe», lo cual es perfectamente válido. Entonces, ¿cómo se le avisa de esto al programador? O si una textura dada falla al cargarse, ¿es esto un caso excepcional en el que hay que abandonar inmediatamente el contexto acutal, o se puede usar una textura predeterminada y avisar al programador/usuario y continuar normalmente?

La aproximación mas usual es usar un valor de retorno de tipo bool que indique si el método llamado terminó adecuadamente u ocurrió algún error y deben tomarse medidas. El problema de esto son al menos 3:

El segundo punto nos lleva a una cuestión más: los programadores somos seres perezosos y no solemos verificar adecuadamente los estados de error. Si el método mencionado retorna un objeto inválido en caso de error, lo más probable es que el programador que llamó el método no lo verifique y continúe como si nada hubiera sucedido. Una excepción no puede evitarse y permite """retornar""" un valor distinto al esperado, incluso en un constructor.

Para ir terminando (esto salió más largo de lo que pensé en un comienzo), creo que lo que dice @rickyah es bastante sensato. Durante el game loop (y en el juego mismo) no deberían construirse ni destruirse objetos, por tanto las excepciones se pueden reservar para casos realmente excepcionales, en donde continuar no suele ser una buena opción (lanzar la excepción permite, por lo menos, escribir un mensaje adecuado en el log, liberar los recursos adquiridos y permitir al programador tomar la última palabra). Para el resto de cosas, un mecanismo del tipo true/false (o similar) me parece adecuado (con la correspondiente entrada en el log). Para los errores que cometa el programador al usar nuestra librería debería tomarse una medida similar a las aserciones, pero con un mensaje más claro para él respecto a qué está haciendo mal (abortar directamente en estos casos no es una mala idea). Para todas las otras cosas internas del motor, se deberían usar aserciones, las cuales creo, deberían usarse sin mesura y casi con exageración: los programadores cometemos más errores de lo que imaginamos.

He aquí mi punto de vista al respecto. Por supuesto que pueden discrepar conmigo y esa es la idea de un debate.

rickyah commented 10 years ago

No estoy de acuerdo en ningún tipo de valor de retorno para comunicar errores de ningún tipo. Los códigos de error son crípticos y provocan un código más complejo de lo que debería.

if ( someFunction() > 1) 
{
     if( functionDepedantOnPrevious() < 0) 
     {
         handleError();
     }
} 
else 
{
    handleOtherError();
}
try
{
  someFunction();
  functionDepedantOnPrevious();
}
catch(ExceptionFromFunction1 ex) 
{
}
catch(ExceptionFromFunction2 ex)
{
}

El segundo código es mucho más claro.

Donde tengamos que retornar true o false, mejor ponemos una excepción o assert. Como ya he dicho, salvo en lugares críticos las excepciones están muy bien, son un gran mecanismo de control del flujo de errores, pero lo que no podemos hacer es gestionar los errores así dentro del gameloop. Ahí lo que hay que hacer es sacar una assertion que haga que todo explote y por supuesto nos haga log de dónde se ha producido, el estado actual, el call stack, etc.

Las excepciones las podemos usar antes para asegurarnos de que los recursos que necesita el motor para funcionar se han adquirido siempre, por ejemplo estableciendo valores por defecto.

Un ejemplo más claro: si al cargar una textura nos da un error, el resource manager saltaría una excepción. El usuario debería entonces capturarla y asignar al recurso que estábamos intentando cargar una textura placeholder De esa manera el motor seguiría funcionando sin problemas, pero veríamos rápidamente que una textura no se ha cargado (luego ya podemos consultar el log para ver qué ha pasado)

Thurok commented 10 years ago

Muy buenas. Aunque estoy muy interesado, no estoy siguiendo muy de cerca el desarrollo de este engine por temas que no vienen al caso. Acabo de leer el informe semanal y me ha parecido muy interesante este tema, de hecho acabo de crearme la cuenta de github para comentar aquí (sí, hasta ahora no tenia cuenta en github...).

Vamos a lo interesante: No estoy muy de acuerdo con la solución de rickyah. Las excepciones no deben ser usadas para controlar el flujo del programa. Copio un párrafo del libro Code Complete, punto 8.4 Exceptions: Throw an exception only for conditions that are truly exceptional Exceptions should be reserved for conditions that are truly exceptional, in other words, conditions that cannot be addressed by other coding practices. Exceptions are used in similar circumstances to assertions—for events that are not just infrequent, but that should never occur.

Me gustaría recalcar "conditions that cannot be addressed by other coding practices". Como dice InExtremaRes, que lanzar una excepción sea costoso no debe preocupar a nadie, es una situación excepcional que no debe ocurrir, y en caso de que ocurra me da igual esperar 1 segundo. Yo creo que si el engine no es capaz de cargar una textura tiene 2 opciones:

Llegados a este punto, de cara al programador del juego el manejo de errores creo que podría ser similar al de OpenGL: booleano para retornar si la función ha acabado correctamente o no y un método GetError() que devuelva el enum del error, en caso de haberlo. En cambio, el engine internamente puede devolver más información de retorno que un booleano, con enums específicos para cada módulo por ejemplo. Si se necesita devolver un puntero pueden usarse los parámetros de salida, que estamos hablando de C++.

rickyah commented 10 years ago

A ver, no estoy de acuerdo en prácticamente nada de lo que has puesto @Thurok

Lo primero de todo, es que gestionar errores es gestionar el flujo del programa, luego cualquier modelo que uses para gestionar errores implica "gestionar el flujo del programa". Para lo que NO hay que usar excepciones es para gestionar el flujo NORMAL del programa. Por ejemplo, si estas iterando una colección buscando un elemento, y cuando lo encuentras lanzas una excepción, eso está mal.

Seguimos con el Code Complete Lo primero es que ningún libro es una "biblia", ni consiste en una serie de dogmas a seguir sin cuestionárselo. Son sugerencias. De hecho, ese punto del Code Complete reza:

This section contains suggestions for realizing the benefits of exceptions and avoiding the difficulties often associated with them.

La parte que resaltas creo que las has entendido mal. El autor mismo pone como ejemplo que una excepción lanzada para gestionar un caso que no debería ocurrir, no es una excepción, es una asserción, por ejemplo un parámetro no válido pasado a un función. Ahí no hay error que puedas recuperar, sino que es un fallo de uso: el programa debe fallar.

Sin embargo, en mi ejemplo, la carga de textura de un fichero que no existe no es un caso que no debería ocurrir, sino un error. En el ejemplo el engine no carga la textura. La carga el gestor de recursos. Hago esta distinción por que el gestor de recursos no sabe qué hacer si no encuentra el fichero. Ahí es donde debe lanzar la excepción para notificar del error.

El engine, que usa el gestor de recursos es el que debe decidir qué hacer cuando el gestor de recursos le notifica del caso excepcional de que no encuentre un fichero. Aunque esto sea un error, tampoco es algo que debiera tumbar el engine. Por ello la idea de que el usuario del engine cargue el placeholder. ¿Por qué no debe tumbar el engine? Por que destrozarías el flujo de trabajo de todo aquel que no tenga lo assets igual que tu. Y no lo digo yo, es una idea que saqué del libro Game Engine Architecture

Por último que recomiendes códigos de error en mi opinión denota que no has trabajado de forma exhaustiva con ese tipo de gestión de errores, y por ello no te has encontrado con la gran cantidad de problemas que produce:

Espero que no tires la carta de que "OpenGL usa esa gestión de errores" por que es con mucho lo peor que tiene. Hay que lidiar con eso por retrocompatibildad, y porque la API de OpenGL fue definida en C que no tiene excepciones. http://28byteslater.com/2010/08/28/opengl-debug-api/ http://www.altdevblogaday.com/2011/06/23/improving-opengl-error-messages/

En cambio, el engine internamente puede devolver más información de retorno que un booleano, con enums específicos para cada módulo por ejemplo. Si se necesita devolver un puntero pueden usarse los parámetros de salida, que estamos hablando de C++

Usar parámetros de salida o no dependería del error, con lo que añades más complejidad para una gestión de errores cuando ya tenemos un sistema que solventa todo eso de forma más limpia y elegante.

Thurok commented 10 years ago

Creo que tenemos un concepto diferente del game engine. Para mí, que el gestor de recursos no encuentre un fichero de textura es algo totalmente normal y previsible, por lo tanto lanzar una excepción no sería nada excepcional. El usuario del game engine puede haberse equivocado al escribir el filename, no ha copiado el fichero donde tocaba o lo que sea.

La parte que resaltas creo que las has entendido mal. El autor mismo pone como ejemplo que una excepción lanzada para gestionar un caso que no debería ocurrir, no es una excepción, es una asserción, por ejemplo un parámetro no válido pasado a un función. Ahí no hay error que puedas recuperar, sino que es un fallo de uso: el programa debe fallar.

¿Puedes indicar en qué parte dice eso?

Por supuesto que los libros muestran sugerencias, también el que tu mencionas, pero justamente dices que el usuario del game engine puede no tener los assets igual que tu y, como he comentado, esto debe ser previsible en un game engine, con lo cual no creo que sea algo excepcional.

Lo de los parámetros de salida no me he explicado bien. Me refería a usar el valor de retorno para tratar los errores y los parámetros de salida para la devolución de los objetos creados. Creo que con un ejemplo se me entenderá mejor. Siendo esto código en el game engine, prefiero esto:

CTexture *texture;
int return_code = ResourceManager->LoadTexture(filename, texture);
if(return_code == OK) 
{
    GameObject->SetTexture(texture);
}
else if(return_code == FILE_NOT_FOUND)
{
    // Texture not loaded. What we should do?
    ...
}

En vez de:

CTexture *texture;
try
{
    texture = ResourceManager->LoadTexture(filename);
    if(texture)
    {
        GameObject->SetTexture(texture);
    }
}
catch {Exception File_not_found)
{
    // Texture not loaded. What we should do?
    ...
}

Y para acabar, el problema de OpenGL es que sus funciones no te avisan si han ido bien o mal. Yo creo que este tema se puede resolver usando el valor de retorno (que no tiene por qué ser booleano, ni propagarse hasta el infinito, alguien debe decidir qué hacer si no se encuentra una textura), un buen sistema de log y las excepciones en casos realmente excepcionales (disco duro lleno al escribir en un fichero por ejemplo).

Por supuesto aquí decidís vosotros, es vuestro engine, yo sólo he venido a comentar mi opinión.

rickyah commented 10 years ago

Que el gestor de recursos no encuentre una textura es algo excepcional, ya que si intentar cargar una textura es por que tú se lo has indicado. Si no la encuentra es por que tu te has equivocado bien al determinar el path de la textura o bien al colocar la textura en el filesystem, lo que es algo excepcional, obviamente.

La cita de code complete:

Exceptions should be reserved for conditions that are truly exceptional, in other words, conditions that cannot be addressed by other coding practices. Exceptions are used in similar circumstances to assertions—for events that are not just infrequent, but that should never occur.

La cita de códing practices no se refiere a que no debas usar excepciones para resolver un problema si lo puedes hacer de otra forma, por que entonces NUNCA usarías excepciones. Lo has entendido mal, el asunto va de que no uses excepciones cuando quieres usar aserciones, o para gestionar -por ejemplo- el control de un flujo o para comprobar si la entrada de usuario es correcta. De hecho no hay referencias a usar error codes en Code Complete.

Tu ejemplo es erróneo. Con excepciones sería así:

try
{
    GameObject->SetTexture( ResourceManager->LoadTexture(filename) );
}
catch {Exception File_not_found)
{
    // Texture not loaded. What we should do?
    ...
}

Por que no hace falta comprobar si hay textura ya que dará un error. Ese código es mucho más corto y claro.

Y tu ejemplo con error codes aún tiene un problema. Has encontrado el error en el engine, bien. En tus ejemplos debemos suponemos que el código está en una función del engine. ¿Cómo se lo notificas al usuario? Tienes que propagar ese error y sacar fuera de ahí el error code FILE_NOT_FOUND para que el usuario pueda actuar en base a ese error lo que implica hacer un return de ese error code.

En el caso de las excepciones, propagar el error al usuario es aún más fácil y claro si cabe:

GameObject->SetTexture( ResourceManager->LoadTexture(filename) );

Que OpenGL no te avise de si las funciones fallan o no es simplemente un detalle de implementación y una muestra más de que usar códigos de error añade complejidad.. Si usas error codes pero no compruebas el código de error, el resultado es que tus funciones tampoco avisan de que ha fallado. Da igual que el código de error te lo devuelva la función, o que el código de error se muestre en un objeto global a la que accedes llamando a una función, en cualquier caso es una comprobación.

Tu opinión es bienvenida por supuesto, simplemente opino que en este caso no tienes razón. En serio, he trabajado con códigos de error y aumentan la complejidad del código una barbaridad. Las excepciones también añaden complejidad, pero un poco menos, y además te proporcionan más ventajas. Todos los lenguajes modernos implementan excepciones, no es por moda.

Thurok commented 10 years ago

Que el gestor de recursos no encuentre una textura es algo excepcional, ya que si intentar cargar una textura es por que tú se lo has indicado. Si no la encuentra es por que tu te has equivocado bien al determinar el path de la textura o bien al colocar la textura en el filesystem, lo que es algo excepcional, obviamente.

Si funcionalmente queréis que sea así, entonces OK. Pero ahora alguien más a parte del resource manager tendrá que saber cosas acerca del recurso en cuestión. Por lo que dices entiendo que el game engine se encargará de comprobar si la texture existe o no, o simplemente le das esa responsabilidad al usuario del engine.

Tu ejemplo es erróneo. Por que no hace falta comprobar si hay textura ya que dará un error. Ese código es mucho más corto y claro.

Realmente he llegado a pensar que me había equivocado en el código. ¿Vas a dejar en manos de las excepciones comprobar los punteros? Entonces para ti las excepciones no son excepcionales y por supuesto te supone un problema que sean costosas.

Exceptions are used in similar circumstances to assertions

Esto no significa que "una excepción para gestionar un caso que no debería ocurrir, no es una excepción, es una asserción" sino que ninguno los dos mecanismos deben usarse para controlar un error previsible y gestionable. Creo que las aserciones deben usarse para asegurar (que es lo que significa assert en inglés) hechos que deberían ser ciertos (precondiciones y postcondiciones de una función, por ejemplo) y las excepciones para capturar errores no previstos (HDD lleno, por ejemplo).

En tus ejemplos debemos suponemos que el código está en una función del engine. ¿Cómo se lo notificas al usuario? Tienes que propagar ese error y sacar fuera de ahí el error code FILE_NOT_FOUND para que el usuario pueda actuar en base a ese error lo que implica hacer un return de ese error code.

No. El engine puede devolvere al usuario un TEXTURE_ERROR o simplemente ERROR (por ejemplo) y en el log debería describirse el problema. La cuestión aquí es que no puedes dejar que el usuario tome una decisión acerca del error y esperar que haga algo, porque probablemente no lo hará. Si te ocurre este error, completamente previsible, simplemente se lo notificas y en vez de tener un modelo con una textura pues tienes el modelo sin textura, el objeto sólido. Una vez notificado, el usuario ya es libre de actuar en consecuencia si quiere, pero el engine se protege de que el usuario no lo haga e intente mostrar una textura que no se ha cargado.

rickyah commented 10 years ago

Por lo que dices entiendo que el game engine se encargará de comprobar si la texture existe o no, o simplemente le das esa responsabilidad al usuario del engine.

El usuario del engine es el encargado de indicar qué recursos cargar, no cómo. Esto último es responsabilidad del engine. Sin embargo el engine NO debe decidir qué hacer si no encuentra esos recursos, y la pelota debe volver al usuario, por que es el que sabe qué hacer.

Realmente he llegado a pensar que me había equivocado en el código. ¿Vas a dejar en manos de las excepciones comprobar los punteros? Entonces para ti las excepciones no son excepcionales y por supuesto te supone un problema que sean costosas.

He dicho que tu ejemplo es erróneo, no que tu código lo fuera. El problema es que es un mal código si pretendes usar excepciones. No tengo ningún puntero que comprobar por que la idea no es retornar un puntero inválido si hay un error, sino lanzar la excepción al encontrar un error en LoadTexture al no encontrar el fichero en el filesytem, y entonces dejar que se propague la excepción para que alguien (el usuario) la gestione. Me parece que el problema es que no entiendes cómo usar las excepciones.

Esto no significa que "una excepción para gestionar un caso que no debería ocurrir, no es una excepción, es una asserción" sino que ninguno los dos mecanismos deben usarse para controlar un error previsible y gestionable. Creo que las aserciones deben usarse para asegurar (que es lo que significa assert en inglés) hechos que deberían ser ciertos (precondiciones y postcondiciones de una función, por ejemplo) y las excepciones para capturar errores no previstos (HDD lleno, por ejemplo).

No, no significa eso, lo siento pero lo has entendido mal. Mira cualquier código bien hecho de gestión de errores con excepciones y verás que no es así. Ejemplos hay a patadas en un montón de frameworks, desde java a .NET, pasando por STL o Boost. Me parece muy bien tus creencias, pero mi experiencia dice otra cosa. Y la experiencia de Code Complete también debe de ser similar, ya que el único momento en el que habla de códigos de error reconoce que no hay manera de notificar a sistemas externos, y de hecho recomienda usar excepciones para eso. Hay más literatura que respalda el uso de excepciones la tienes a patadas, como el capítulo 7 de Clean Code Por último pareces tomar como dogma esta recomendación. En ese mísmo capítulo hay muchas más:

Use exceptions to notify other parts of the program about errors that should not be ignored Don’t use an exception to pass the buck (handle the exception locally if you can) Avoid throwing exceptions in constructors and destructors unless you catch them in the same place Throw exceptions at the right level of abstraction Include all information that led to the exception in the exception message

Incluso por sugerir hasta recomienda NO usarlas. Como siempre el sentido común y cada caso ha de ser valorado. Tu has establecido dogmáticamente cómo usarlas.

No. El engine puede devolvere al usuario un TEXTURE_ERROR o simplemente ERROR (por ejemplo) y en el log debería describirse el problema. La cuestión aquí es que no puedes dejar que el usuario tome una decisión acerca del error y esperar que haga algo, porque probablemente no lo hará.

Según tu forma de actuar en el engine estás gestionando un error el usuario obligando a que el engine se comporte de la manera que tu quieres, en vez de darle la elección al usuario, como debería ser. Si el usuario decide no hacer algo, perfecto: el programa terminará y el usuario sabrá perfectamente por qué y dónde falló. Tu aproximación simplemente oculta el error debajo de la alfombra. Te recuerdo que el engine está al servicio del usuario, no al revés. Si un usuario carga un recurso de forma errónea, no es responsabilidad del engine gestionar el error del usuario, sino del propio usuario.

Thurok commented 10 years ago

A ver, vuelvo a empezar que sino esto será eterno. Yo no digo que se tenga que substituir el uso de excepciones por códigos de errores sino que las excepciones son para casos excepcionales. Para mí, que no se encuentre el fichero de una textura no es un caso excepcional en un game engine, en cambio sí que veo lógico lanzar una excepción si no hay espacio en memoria para cargar la textura. Para otros casos sí que defiendo los códigos de error pero bien usados: un código de error no debe propagarse más de 2 - 3 funciones, sino qué tipo de abstracción se está haciendo? :S

Si el usuario decide no hacer algo, perfecto: el programa terminará y el usuario sabrá perfectamente por qué y dónde falló.

Robustnes vs Correctness. Tu prefieres que el game engine sea correcto y que finalice su ejecución si encuentra un error. Yo prefiero que sea robusto y que, mientras el error no sea crítico, continue su ejecución (aunque sea con un modelo sin textura).

rickyah commented 10 years ago

En fin con lo de "no debería propagarse más de 2-3 funciones" ya me has dicho bastante. Si tu supieras las trazas que yo he visto... Lo raro es que con la literatura que citas no te des cuenta de lo que implica la recomendación de tener muchas funciones / objetos que hagan una sola cosa. Y, de todas maneras, propagarlo 2-3 funciones (que serán más) ya es propagarlo demasiado.

Tampoco pareces darte cuenta de que estoy poniendo ejemplos, no se ha decidido nada de la forma de gestionar los recursos así que puedes ahorrarte las definiciones teorías por que no hablamos de eso :)

Thurok commented 10 years ago

Si realmente has visto tantas trazas como dices sabrás que lanzar una excepción y capturarla N niveles más arriba te puede generar un memory leak bastante interesante (y que te puede hacer perder unas cuantas horas) si hay algún new por ahí en medio.

puedes ahorrarte las definiciones teorías por que no hablamos de eso

No, no hablamos de eso, hablamos de usar o no usar excepciones en casos controlables y previsibles en un game engine.

rickyah commented 10 years ago

He visto muchas trazas. Y también muchas discusiones donde la gente empieza con un tema y sigue moviendo la línea de meta, introduciendo nuevos temas para desviar la atención.

Ese problemas que citas es viejo conocido, y no tiene que ver con usar excepciones, es un fallo al gestiona el errór sin liberar recursos, que dependiendo del caso tambien se puede gestionar mejor con excepciones haciendo un rethrow, y normalmente se gestiona con RAII porque el desenenrollado de pila llama a los destructores :/ En serio, o bien no sabes utilizar las excepciones o bien te inventas falsos problemas para dar validez a tu opinión. En cualquier caso ese no es la manera de tratar este tema.

Por ultimo que son casos controlables y previsibles es tu opinión. En la mía no, simplemente acéptalo. Aunque para mejorar tu opinión, tendrías que pensar en lo robusto que sería tu diseño si el usuario decide crear sus propios tipos de recursos, a ver que hace sí falla al cargar uno de esos recusos personalizados. (Pista: poco por que no sabrá que tipo de recurso poner por defecto :)

Thurok commented 10 years ago

es un fallo al gestiona el errór sin liberar recursos

Por supuesto, pero cuando estás en el nivel Z, lanzas una excepción y lo captura quien inició todas las llamadas, nivel A, no hay manera de liberar recursos locales del nivel N. Y con un rethrow aún menos.

Aunque para mejorar tu opinión, tendrías que pensar en lo robusto que sería tu diseño........

Con un diseño robusto simplemente ignoras ese recurso y si no se puede cargar nada muestras la pantalla en negro. Creo que es mejor solución para un engine hacer éso que finalizar la ejecución.

Por ultimo que son casos controlables y previsibles es tu opinión. En la mía no, simplemente acéptalo.

Entonces si no los consideras casos controlables y previsibles para un game engine, como dije ayer, OK. Fin discusión.

danigomez commented 10 years ago

Wow, menudo lío que se armó jeje, sobre los que decís de la liberación de recursos de un nivel N, puedes agregar un catch, liberar los recursos y hacer un throw nuevamente, hasta llegar al nivel del usuario, con eso creo que no hay problema en la liberación de los recursos, igualmente estamos pensando en usar las excepciones para indicar errores recuperables por lo que no se que recurso habría que liberar, ya que el engine no finalizaría su ejecución, y esto hace que me surga una pregunta, cómo manejariamos los leaks en el caso de que se tire un assert?? Porque como aborta la ejecución... Se puede indicar un callback o algo similar?

rickyah commented 10 years ago

Si se aborta la ejecución no hay leaks. El SO mata el proceso y la memoria asociada al mismo se recupera.

Thurok commented 10 years ago

Dios, por fin alguien que aporta algo más que decir "no lo entiendes".

Ok, entendí mal lo del rethrow, pensaba que se refería a ponerlo en el nivel A, cosa que no tiene ningún sentido. En el N no lo veo mal, pero lo que no me gusta es que cuando trabajas en el N no tienes porque saber (aunque se debería si todo está bien documentado) que el nivel Z, al cual tu puedes desconocer ya que tu sólo llamas al siguiente nivel O, lanzará una excepción en ciertos casos.

igualmente estamos pensando en usar las excepciones para indicar errores recuperables por lo que no se que recurso habría que liberar, ya que el engine no finalizaría su ejecución

Justamente ésa es la gracia, como el engine no finaliza su ejecución debes liberar recursos que tenías por ahí en medio y que ya no vas a utilizar. En caso de un assert se aborta la ejecución y el SO se encarga de liberar el espacio usado por el programa.

rickyah commented 10 years ago

Si no sabes si un método al que llamas lanza una excepción entonces no te has leído bien la documentación por que si en un nivel superior capturas esa excepción es que sabes que existe. O en ese nivel superior estás creando un try catch genérico que sólo oculta los errores, lo que es un error de uso de excepciones, y grave.

En cualquier caso es importante documentar las excepciones lanzadas. No voy a revisar todo el hilo pero creo ya se comentó que es una posible desventaja de las excepciones, no son explícitas

@Thurok No se qué problema tienes por que te digan "creo que no lo entiendes" cuando se hace en base a todos los comentarios que has escrito. Deja de hacerte la víctima. Si todos los problemas que le ves a las excepciones en realidad no son un problema, a lo mejor es que no entiendes correctamente cómo funcionan. No pasa nada por ello, nadie ha nacido con conocimiento adquirido, y aquí estamos todos para aprender. También puede ser que nos hayamos explicado mal por supuesto, pero es cada vez que te daba la solución a uno de los supuestos problemas me sacas otro distinto, como el problema al liberar recursos, que no es un problema intrínseco de las excepciones, sino de mala gestión de los recursos (y por tanto también lo puedes hacer mal usando error codes)

Todo esto induce a pensar que no estás interesado en debatir qué sistema es mejor, sino en que simplemente aceptemos tus opiniones.

rickyah commented 10 years ago

Para apoyar mi argumentación, dejaré que el conocimiento acumulado hable por si mismo: http://stackoverflow.com/questions/253314/exceptions-or-error-codes

Thurok commented 10 years ago

No voy a entrar en más discusiones tontas. Yo lo que defiendo es que las excepciones deben usarse para casos excepcionales. Si el equipo considera que no encontrar el fichero de una textura es un caso excepcional, adelante con ello, para mí un game engine no puede hacer suposiciones de este tipo.

rickyah commented 10 years ago

Si cuando te argumentan algo tu salida es que "no vas a entrar en discusiones tontas" estamos apañados.

No hay una definición objetiva de "excepcional", así que tu elección de "circustancia excepcional" es tan arbitraria como la de otros, algo que parece costarte aceptar.

Y de nuevo mezclamos la gestión de errores con cómo gestiona el engine un recurso que no se ha encontrado, que es un error específico, y no es de lo que va este hilo. Por si no ha quedado: claro gestionar con excepciones la carga de recursos era un ejemplo de uso de excepciones, eso no significa que la carga de recursos se vaya a hacer obligatoriamente así con ese criterio.

Por favor, centrémonos en argumentar las ventajas de los códigos de error vs excepciones. que es de lo que va esta discusión. Digo centrémonos por que también es mi culpa por seguir discutiendo eso en este hilo. Si quieres discutir la arquitectura del gestor de recursos hay otro issue.

Thurok commented 10 years ago

Con lo de discusiones tontas me referia a:

Deja de hacerte la víctima.

y a:

sino en que simplemente aceptemos tus opiniones.

Sigamos:

tu elección de "circustancia excepcional" es tan arbitraria como la de otros, algo que parece costarte aceptar.

Te respondo copiando lo que he puesto en mi último comentario: "Si el equipo considera que no encontrar el fichero de una textura es un caso excepcional, adelante con ello". No creo que me cueste aceptarlo, de hecho ya lo acepté en mi 3er comentario: "Si funcionalmente queréis que sea así, entonces OK".

Por favor, centrémonos en argumentar las ventajas de los códigos de error vs excepciones. que es de lo que va esta discusión.

Repito, yo no estoy debatiendo eliminar excepciones para usar únicamente los códigos de error, sino usar excepciones en circunstancias realmente excepcionales (que como ya hemos visto, son subjetivas). Cuando yo decidí comentar aquí fue porque había leído lo siguiente:

Donde tengamos que retornar true o false, mejor ponemos una excepción o assert. Como ya he dicho, salvo en lugares críticos las excepciones están muy bien, son un gran mecanismo de control del flujo de errores, pero lo que no podemos hacer es gestionar los errores así dentro del gameloop. Ahí lo que hay que hacer es sacar una assertion que haga que todo explote y por supuesto nos haga log de dónde se ha producido, el estado actual, el call stack, etc.

Hablas de hacer petar el engine al mínimo error y lanzar excepciones o asserts en vez de retornar true/false. Y además acababas de exponer un ejemplo de uso de excepciones que, repito, bajo mi punto de vista, no es para nada excepcional.

rickyah commented 10 years ago

No hablo de petar el engine al mínimo error, hablo de que una vez entrado en el bucle de juego gestionar errores con excepciones es costoso, y en el bucle de juego normalmente se algo va mal lo normal es petar el engine. El código previo es el que debe ocuparse de que todo lo que vaya al gameloop sea lo más correcto posible. ¿por qué? Por que las comprobaciones de error lastrarían el rendimiento.

Las excepciones son mecanismos muy válidos de control de errores no de "circustancias excepcionales" por que por esa regla de tres, los errores son algo aceptable, y claro, no se gestionan con excepciones. Pero que te quedes sin espacio en disco también es algo que pasa a menudo, no es algo excepcional ni extraño, así que tampoco se usan excepciones, etc, etc, etc.

Por último, hablar de "discusiones tontas" y frases como "Dios, por fin alguien que aporta algo más que decir 'no lo entiendes'" después de mantener una conversación donde los problemas que me pones sobre las excepciones no son tales, en mi opinión es hacerse la víctima. Tu me has sacado todos los problemas que ves con las excepciones y hemos debatido sobre ello. Yo he puesto sobre la mesa los errores que yo veo por la gestión de errores con códigos de error y no se ha debatido nada. (retornar true/false y consultar una función global es gestión por códigos de error)

ficion commented 10 years ago

Creo que entiendo el punto de @rickyah.

A pesar del nombre que llevan las excepciones no siempre son usadas para algo totalmente excepcionales. Vienen bien para errores comunes, pero que por lo general no significan nada irrecuperable, de esta manera el "costo" de las excepciones se ve reducido. Concuerdo con la idea de usar otros métodos de error para las partes de menor nivel del engine, básicamente porque, si algo falla ahí, es que de verdad hay un problema. Usar excepciones para las zonas de mayor nivel del engine parece la mejor opción.

Entendí bien, ¿supongo?

Ahora, en mi opinión, no hay que reinventar la rueda, eso es lo que he leído una y otra vez. Las excepciones son parte del lenguaje, son más estándar y son una facilidad que no habría que desaprovechar.

rickyah commented 10 years ago

@ficion has entendido bien :+1: Excepciones es un nombre que en mi opinión no tiene que ver con la frecuencia que se dan los eventos que quieres controlar, sino que es una referencia clara a las excepciones hardware, donde un elemento del harware "lanza" una excepción para notificar de que algo ha pasado para notificar a la CPU que la "captura" y trata. La diferencia, claro está, es que en las excepciones hardware se restaura la ejecución después de tratada en el mismo punto donde se interrumpió, y en las software en principio no se restaura por que se cambia el flujo del programa.

Es decir, que en el fondo estamos comparando excepciones hardware con pooling (error codes)

ghost commented 10 years ago

Puedo dar mi granito de arena, al parecer me parece un proyecto sorprendente y muy ambicioso, y si miráis en mi github no veréis nada (más que nada porque no lo uso, tengo la cuenta para responder a comentarios y demás)

Entrando en el tema que nos concierne es seguro que el manejo de excepciones es más costoso, y no se puede permitir que una excepción suba más de un 20% del número de niveles totales que presenta el software (y más sobre todo en el loop), pero también es cierto que es una forma muy sencilla de evidenciar fallos y de estructurar fallos (que nadie ha hablado de ello, o por lo menos no de la forma que lo tengo yo en mente). Si bien es cierto que un sistema eficiente y bien organizado (algo que por definición no es fácil y menos en un proyecto como este) de códigos de error y un "Logger" personalizado que el solito capturase esos errores (un thread independiente que compruebe si existe en el valor "error_code" algo distinto del valor "NO_ERROR=0" y lanzase lo que se debe lanzar para hacer log, o para lo que sea) sería una opción en aplicaciones bien organizadas y con poca carga en el thread principal (este no es el caso). Ante eso las aserciones está claro que quedan descartadas para el despacho de errores salvo que sean errores críticos, es decir, que el loop no pueda usar un salvo conducto (tipo un objeto de tipo distinto pasado como argumento, un objeto del engine faltante etc... Una vez dejado eso claro me decanto por las excepciones, porque aunque se refieran a algo "excepcional" te facilitan el trabajo un montón, y más si creas tu librería de excepciones personalizadas, que en función de la excepción realiza una función, registra en un log o en otro (eso abre la puerta al log en múltiples archivos, de manera que queda más organizado por zonas, por ejemplo, muy conflictivas: no necesitas leer 400 líneas de log para saber donde está el fallo, si en el nivel A o en B, si por causa M o N, ya que la excepción simplemente crearía un log de los niveles y causas determinados). Otra cosa que se debe organizar es el número y tipo de "excepciones visibles", es decir, que excepciones deberá procesar el engine por si solo y cuales serán lanzadas al usuario (en este caso al programador, nunca al usuario final, salvo deseo expreso de ese programador): por ejemplo, las texturas el usuario final no tiene por qué enterarse (a lo mejor un mensaje de error indicándolo, pero no más) pero si el programador, para que

A) compruebe si es fallo suyo, B) compruebe si es fallo del archivo, etc...

Con respecto al comentario de @rickyah, en realidad no hace falta cambiar el flujo del programa (y menos si estas en un loop de un engine, que puedes terminar acomplejando mucho ese "cambio de flujo" para luego reengancharlo en el flujo normal), simplemente (si se puede) seleccionar un valor que venga por defecto, pero vamos, estoy totalmente de acuerdo contigo; basándome en mis conocimientos y experiencia de c++, c# y java, sobre todo.

rickyah commented 10 years ago

Lo siento @DrkWzrd pero hay unas cuantas cosas de las que dices que no entiendo correctamente.

no se puede permitir que una excepción suba más de un 20% del número de niveles totales que presenta el software

Ese número (20%) ¿de dónde sale?

Un sistema eficiente y bien organizado (algo que por definición no es fácil y menos en un proyecto como este) de códigos de error y un "Logger" personalizado que el solito capturase esos errores (un thread independiente que compruebe si existe en el valor "error_code" algo distinto del valor "NO_ERROR=0" y lanzase lo que se debe lanzar para hacer log, o para lo que sea) sería una opción en aplicaciones bien organizadas y con poca carga en el thread principal (este no es el caso)

Gestionar errores usando un thread aparte que monitorice una flag global para cuando se ponga ON haga el log es una locura de proporciones bíblicas que añade una complejidad y una pila de potenciales problemas. Es un diseño malísimo :scream:

Ante eso las aserciones está claro que quedan descartadas para el despacho de errores salvo que sean errores críticos, es decir, que el loop no pueda usar un salvo conducto (tipo un objeto de tipo distinto pasado como argumento, un objeto del engine faltante etc...

Ni idea de qué significa eso en negrita

Si creas tu librería de excepciones personalizadas, que en función de la excepción realiza una función, registra en un log o en otro

No entiendo "en función de la excepción realiza una función". Registrar en varios logs tampoco me parece buena idea. Es más sencillo filtrar las entradas de un único log con herramientas que mirar diferentes logs.

Con respecto al comentario de @rickyah, en realidad no hace falta cambiar el flujo del programa (y menos si estas en un loop de un engine, que puedes terminar acomplejando mucho ese "cambio de flujo" para luego reengancharlo en el flujo normal),

No se a qué comentario mío concreto te refieres, pero aún así no entiendo ni papa de la frase en negrita

¿Eres de latinoamérica? Yo soy español, por eso creo que algunas expresiones no las entiendo bien.

ghost commented 10 years ago

@rickyah

  1. Como bien has visto he puesto: y con poca carga en el thread principal (este no es el caso). En ningún caso me refiero a este (ni en casi ningún otro) proyecto (esto es más una respuesta "diplomática" con mucha gente que he conocido, es que se me ha quedado la costumbre). La complejidad por supuesto que es insalvable, pero oye, proyectos más complejos se hacen.
  2. El número lo saco a partir de los muchos (y ya te digo, muchos) casos en los que me he puesto a hacer excepciones (en mis tiempos mozos) y "casi" me petaban algunas partes de mis programas, además de muchos "debates" (discusiones acaloradas) con muchos amigos míos de cervezas. Por supuesto es una opinión personal (por aquello de tirar un número, ea)
  3. Por salvoconducto me refiero a: las aserciones solo se pueden usar cuando el loop no puede recuperarse del error, ni haciendo "cualquier cosa" para que se salvase
  4. Pues que al lanzar cada excepción se llame a determinados métodos. Con respecto a lo del multilog, aunque no digo que se deba usar, bien usado resuelve muchos problemas de tiempo, pero realmente no es necesario, y menos actualmente que se usan IDEs potentes, con debugs potentes y demás.
  5. Tu en el comentario en que lo has comparado con el hardware has puesto "se cambia el flujo" y yo te digo que realmente no hace falta (siendo puristas sí cambia el flujo), simplemente hacer que se sustituya el causante de la excepción por un default.
  6. Soy español, pero ha sido una respuesta más pensada para stackoverflow (muy muy extensa y pensada en inglés) que para aquí.
rickyah commented 10 years ago
  1. Es que ni con mucha carga en el thread principal ni nada, ese diseño con un toread monitorizando un flag, es, con perdón, una tontería como un piano, al menos para el tema que nos ocupa. No se, una cola Producer-Consumer tiene mucho más sentido en este caso IMO.
  2. Es que no veo sentido esa limitación, los niveles del stack ¿los cuentas a partir de tu código o de la aplicación entera incluido thirds? ¿Qué quieres decir con "te ponías a hacer excepciones"?
  3. ¿Para qué podrías querer llamar a un método al lanzar una excepción? Es que además no se cómo se me ocurre hacerlo a no ser llamar ese método en el constructor de la excepción, y de mano huele a muy mala idea.
  4. Yo al menos no conozco ningún lenguaje que permita recuperar el flujo del programa desde donde se lanzó la excepción al tratarla. No digo que no exista, pero es que nunca oí hablar del tema. No entiendo esto: " hacer que se sustituya el causante de la excepción por un default"
  5. Francamente, no entiendo el sentido de que, siendo castellano-hablante, pienses una respuesta en inglés para luego escribirla en español. En cualquier caso no te preocupes por la extensión de las respuestas, en este mismo hilo hay algunas más largas y no estaban pensadas para SO.
ghost commented 10 years ago
  1. Estamos de acuerdo, yo solo daba posibilidades (de las más remotas a las más plausibles)
  2. Pues que si tienes que enviar hacia niveles superiores una excepción, como el código sea grande te puedes perder y perderse produce por norma general muchos fallos picando código. Pues que en mis tiempos mozos (cosas que tiene aprender con profesores que no enseñaban nada y te dejaban al libre albedrío) pues cada cosa que se me ocurría que tenía que enviar a un nivel superior, excepción al canto y yo, tan tranquilo como unas castañuelas (pobre de mí).
  3. Aunque sea una mala idea puede ser útil en el modo debug, no en el release (aunque también se puede llamar a ese mismo método en le catcher, punto también para tí, es decir, yo opinaba y opino que lo que se debe hacer aquí es hacerlo cuanto más sencillo y claro mejor, total que no discernimos mucho, yo daba posibilidades, por dar... que no queden)
  4. En este caso, tenemos un concepto de flujo de programa distinto, como flujo de programa yo me refiero a las diferentes funciones por las que va a ir pasando durante su ejecución. A lo mejor mi concepto es erróneo, si me explicas cual es el tuyo nos entenderemos sin problema. Además, recuperar el flujo de programa no es tanto cuestión de lenguaje sino de diseñar la aplicación para que se pueda hacer.
  5. La costumbre hijo mío, la costumbre, ajajjaa, que uno termina siendo más ingles que español después de todo, jajaja...
rickyah commented 10 years ago
  1. Que te pierdas en el código no tiene que ver con producir muchos fallos, no hay argumento que sustente esa correlación.
  2. El flujo del programa es exactamente eso. El problema es que si sigues una programación estructurada (con POO también lo haces), no puedes dar saltos en el código de cualquier manera. El anti-ejemplo de ese tipo de programación es el goto, que permite saltos arbitrarios en el flujo. Y una excepción produce el mismo efecto: no hay manera de capturar una excepción en niveles superiores, tratarla, y volver a seguir ejecutando código en el mismo lugar donde se lanzó originalmente.
rickyah commented 10 years ago

Sobre el tema de las excepciones (SEH) como parte de mi refresco de c++ , he estado leyendo y consultando con gente con experiencia profesional en creación de videojuego y en general informándome sobre diseño de engines. En estos projectos (engine) las excepciones o bien no se activan directamente, o bien sólo se permiten de forma muy muy limitada y en debug. Las razones son las siguientes:

Esto parecen argumentos particularmente potentes para desaconsejar el uso de excepciones en este proyecto concreto, o al menos de limitarlas sólo a entornos debug; así que ¿qué opinais?

PD: Creo que aquí deberíamos debatir el uso o no de excepciones como mecanismos de error, y depende de lo que se decida, en otro hilo debatir cómo gestionar esos errores con algo mejor que error codes.

danigomez commented 10 years ago

No es posible indicar que se activen las excepciones solo en ciertos módulos del engine? O activarlas afecta a todo el engine y a quien lo use? Quiero decir si es posible por ejemplo compilar un módulo del engine por separado con excepciones y luego linkearlo al engine sin excepciones, qué pasaría en ese caso?

Thurok commented 10 years ago

Me parece muy interesante este tema que comentas rickyah. Pero me surgen varias dudas al respecto ¿Cómo sabe el compilador si se van a usar excepciones o no y decide si añadir o no esos metadatos? Una posible respuesta sería que el compilador lo busque mientras hace el análisis sintáctico. Pero, ¿y si se compila en paralelo?

¿Podrías poner algún link sobre este tema?

Igualmente creo que no deberías mirarte mucho el SEH ya que es específico de Microsoft, incluso ellos recomiendan no usarlo (http://msdn.microsoft.com/en-us/library/swezty51.aspx) para proyectos portables y si no me equivoco este engine será multiplataforma, no?

rickyah commented 10 years ago

Pensé que SEH era una forma genérica de llamar a una forma de gestionar excepciones. Sin embargo C++ en Windows lo usa por debajo si mal no recuerdo.

@LeonardoJegigzem código que tenga habilitadas excepciones creo que puede usar código que no las tenga habilitadas, pero el caso contrario, que es justo lo que describes me parece que no es posible. Siento no hablar en absolutos, pero lo verificaré.

@Thurok Activar / desactivar el soporte de excepciones se hace con flags para el compilador, en gcc y en clang creo que se pasa el flag -fno-exceptions En Visual Studio ni idea del flag que se pasa ya que siempre lo activé como una opción en las opciones de compilación del proyecto de forma visual, y o no me fijé o no lo recuerdo, pero la opción estaba ahí. (Edit: es /EH : http://msdn.microsoft.com/en-us/library/1deeycx5.aspx)

Thurok commented 10 years ago

Entonces está activado por defecto y se tiene que desactivar a conciencia, interesante. Gracias por la info.