El lenguaje Go de Google no tiene excepciones como elección de diseño, y Linus, de Linux, ha llamado a las excepciones una mierda. ¿Por qué?
El lenguaje Go de Google no tiene excepciones como elección de diseño, y Linus, de Linux, ha llamado a las excepciones una mierda. ¿Por qué?
Respuestas:
Las excepciones hacen que sea realmente fácil escribir código donde una excepción que se lanza romperá invariantes y dejará los objetos en un estado inconsistente. Básicamente, te obligan a recordar que casi todas las declaraciones que haces pueden arrojar y manejar eso correctamente. Hacerlo puede ser complicado y contrario a la intuición.
Considere algo como esto como un ejemplo simple:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Suponiendo que la FrobManager
voluntad delete
del FrobObject
, esto se ve bien, ¿verdad? O tal vez no ... Imagínense entonces si FrobManager::HandleFrob()
o operator new
lanza una excepción. En este ejemplo, el incremento de m_NumberOfFrobs
no se revierte. Por lo tanto, cualquiera que use esta instancia de Frobber
tendrá un objeto posiblemente dañado.
Este ejemplo puede parecer estúpido (ok, tuve que estirarme un poco para construir uno :-)), pero la conclusión es que si un programador no está pensando constantemente en excepciones y asegurándose de que cada permutación de estado se lleve a cabo siempre que hay lanzamientos, te metes en problemas de esta manera.
Como ejemplo, puede pensar en ello como si pensara en mutex. Dentro de una sección crítica, confía en varias declaraciones para asegurarse de que las estructuras de datos no estén dañadas y que otros subprocesos no puedan ver sus valores intermedios. Si alguna de esas declaraciones simplemente no se ejecuta al azar, terminará en un mundo de dolor. Ahora elimine los bloqueos y la concurrencia, y piense en cada método de esa manera. Piense en cada método como una transacción de permutaciones en el estado del objeto, por así decirlo. Al comienzo de la llamada al método, el objeto debe tener un estado limpio y, al final, también debe haber un estado limpio. En el medio, la variable foo
puede ser inconsistente conbar
, pero su código eventualmente lo rectificará. Lo que significan las excepciones es que cualquiera de sus declaraciones puede interrumpirlo en cualquier momento. La responsabilidad recae en usted en cada método individual para hacerlo bien y retroceder cuando eso suceda, u ordenar sus operaciones para que los lanzamientos no afecten el estado del objeto. Si se equivoca (y es fácil cometer este tipo de error), la persona que llama termina viendo sus valores intermedios.
Los métodos como RAII, que a los programadores de C ++ les encanta mencionar como la solución definitiva a este problema, ayudan en gran medida a protegerse contra esto. Pero no son una solución milagrosa. Se asegurará de que libere recursos en un lanzamiento, pero no lo liberará de tener que pensar en la corrupción del estado del objeto y en los llamadores que ven valores intermedios. Entonces, para mucha gente, es más fácil decirlo, por orden del estilo de codificación, sin excepciones . Si restringe el tipo de código que escribe, es más difícil introducir estos errores. Si no lo hace, es bastante fácil cometer un error.
Se han escrito libros completos sobre codificación segura de excepciones en C ++. Muchos expertos se han equivocado. Si es realmente tan complejo y tiene tantos matices, tal vez sea una buena señal de que debes ignorar esa característica. :-)
El motivo por el que Go no tiene excepciones se explica en las preguntas frecuentes sobre el diseño del lenguaje de Go:
Las excepciones son una historia similar. Se han propuesto varios diseños para excepciones, pero cada uno agrega una complejidad significativa al lenguaje y al tiempo de ejecución. Por su propia naturaleza, las excepciones abarcan funciones y quizás incluso gorutinas; tienen implicaciones de amplio alcance. También existe preocupación por el efecto que tendrían en las bibliotecas. Son, por definición, excepcionales, pero la experiencia con otros lenguajes que los soportan demuestra que tienen un efecto profundo en la especificación de la biblioteca y la interfaz. Sería bueno encontrar un diseño que les permita ser realmente excepcionales sin alentar los errores comunes a convertirse en un flujo de control especial que requiere que cada programador lo compense.
Al igual que los genéricos, las excepciones siguen siendo un tema abierto.
En otras palabras, aún no han descubierto cómo admitir excepciones en Go de una manera que consideren satisfactoria. No están diciendo que las Excepciones sean malas per se ;
ACTUALIZACIÓN - Mayo de 2012
Los diseñadores de Go ahora se han bajado de la valla. Sus preguntas frecuentes ahora dicen esto:
Creemos que acoplar excepciones a una estructura de control, como en el idioma try-catch-finalmente, da como resultado un código complicado. También tiende a alentar a los programadores a etiquetar demasiados errores comunes, como no abrir un archivo, como excepcionales.
Go adopta un enfoque diferente. Para el manejo sencillo de errores, las devoluciones de valores múltiples de Go facilitan la notificación de un error sin sobrecargar el valor devuelto. Un tipo de error canónico, junto con otras características de Go, hace que el manejo de errores sea agradable pero bastante diferente al de otros idiomas.
Go también tiene un par de funciones integradas para señalar y recuperarse de condiciones verdaderamente excepcionales. El mecanismo de recuperación se ejecuta solo como parte del estado de una función que se elimina después de un error, lo cual es suficiente para manejar una catástrofe pero no requiere estructuras de control adicionales y, cuando se usa bien, puede resultar en un código limpio de manejo de errores.
Consulte el artículo Aplazar, entrar en pánico y recuperar para obtener más detalles.
Entonces, la respuesta corta es que pueden hacerlo de manera diferente utilizando el retorno de múltiples valores. (Y de todos modos tienen una forma de manejo de excepciones).
... y Linus de Linux ha llamado a las excepciones una mierda.
Si quieres saber por qué Linus piensa que las excepciones son una mierda, lo mejor es buscar sus escritos sobre el tema. Lo único que he rastreado hasta ahora es esta cita que está incrustada en un par de correos electrónicos en C ++ :
"Todo el asunto del manejo de excepciones de C ++ está fundamentalmente roto. Está especialmente roto para los núcleos".
Notarás que está hablando de excepciones de C ++ en particular, y no de excepciones en general. (Y excepciones de C ++ no tienen aparentemente algunas cuestiones que los hacen difíciles de usar correctamente).
¡Mi conclusión es que Linus no ha llamado a las excepciones (en general) "basura" en absoluto!
Las excepciones no son malas en sí mismas, pero si sabe que van a suceder con frecuencia, pueden resultar caras en términos de rendimiento.
La regla general es que las excepciones deben marcar las condiciones excepcionales y que no debe utilizarlas para controlar el flujo del programa.
No estoy de acuerdo con "lanzar excepciones únicamente en una situación excepcional". Aunque en general es cierto, es engañoso. Las excepciones son para condiciones de error (fallas de ejecución).
Independientemente del idioma que utilice, obtenga una copia de las Pautas de diseño del marco : convenciones, expresiones idiomáticas y patrones para bibliotecas .NET reutilizables (segunda edición). El capítulo sobre lanzamiento de excepciones no tiene parangón. Algunas citas de la primera edición (la segunda en mi trabajo):
Hay páginas de notas sobre los beneficios de las excepciones (consistencia API, elección de la ubicación del código de manejo de errores, robustez mejorada, etc.) Hay una sección sobre rendimiento que incluye varios patrones (Tester-Doer, Try-Parse).
Las excepciones y el manejo de excepciones no están mal. Como cualquier otra función, se pueden utilizar incorrectamente.
Desde la perspectiva de golang, supongo que no tener un manejo de excepciones mantiene el proceso de compilación simple y seguro.
Desde la perspectiva de Linus, entiendo que el código del kernel es TODO sobre casos de esquina. Por tanto, tiene sentido rechazar las excepciones.
Las excepciones tienen sentido en el código donde está bien dejar la tarea actual en el piso y donde el código de caso común tiene más importancia que el manejo de errores. Pero requieren la generación de código del compilador.
Por ejemplo, están bien en la mayoría de los códigos de alto nivel orientados al usuario, como el código de aplicaciones web y de escritorio.
Las excepciones en sí mismas no son "malas", es la forma en que a veces se manejan las excepciones lo que tiende a ser malo. Hay varias pautas que se pueden aplicar al manejar excepciones para ayudar a aliviar algunos de estos problemas. Algunos de estos incluyen (pero seguramente no se limitan a):
Option<T>
lugar de null
hoy en día. Acabo de introducirlo en Java 8, por ejemplo, siguiendo una pista de Guava (y otros).
Los argumentos típicos son que no hay forma de saber qué excepciones saldrán de un fragmento de código en particular (dependiendo del lenguaje) y que se parecen demasiado a goto
s, lo que dificulta el seguimiento mental de la ejecución.
http://www.joelonsoftware.com/items/2003/10/13.html
Definitivamente no hay consenso sobre este tema. Yo diría que desde el punto de vista de un programador de C de núcleo duro como Linus, las excepciones son definitivamente una mala idea. Sin embargo, un programador típico de Java se encuentra en una situación muy diferente.
setjmp
/ longjmp
stuff, que es bastante malo.
Las excepciones no son malas. Encajan bien con el modelo RAII de C ++, que es lo más elegante de C ++. Si ya tiene un montón de código que no es seguro para excepciones, entonces son malos en ese contexto. Si está escribiendo software de muy bajo nivel, como el sistema operativo Linux, entonces son malos. Si le gusta ensuciar su código con un montón de comprobaciones de devolución de errores, entonces no son útiles. Si no tiene un plan para el control de recursos cuando se lanza una excepción (que proporcionan los destructores de C ++), entonces son malos.
Por lo tanto, un gran caso de uso para las excepciones es ...
Supongamos que está en un proyecto y cada controlador (alrededor de 20 principales diferentes) extiende un solo controlador de superclase con un método de acción. Luego, cada controlador hace un montón de cosas diferentes entre sí, llamando a los objetos B, C, D en un caso y F, G, D en otro caso. Las excepciones vienen al rescate aquí en muchos casos donde había toneladas de códigos de retorno y CADA controlador lo manejaba de manera diferente. Golpeé todo ese código, arrojé la excepción adecuada de "D", lo atrapé en el método de acción del controlador de superclase y ahora todos nuestros controladores son consistentes. Anteriormente, D devolvía nulo para MÚLTIPLES casos de error diferentes de los que queremos informar al usuario final, pero no pude y yo no '
sí, tenemos que preocuparnos por cada nivel y cualquier limpieza / fuga de recursos, pero en general ninguno de nuestros controladores tuvo recursos para limpiar después.
gracias a Dios tuvimos excepciones o habría tenido una gran refactorización y habría perdido demasiado tiempo en algo que debería ser un simple problema de programación.
Teóricamente son realmente malos. En el mundo matemático perfecto no se pueden encontrar situaciones excepcionales. Mire los lenguajes funcionales, no tienen efectos secundarios, por lo que prácticamente no tienen fuente para situaciones excepcionales.
Pero la realidad es otra historia. Siempre tenemos situaciones que son "inesperadas". Por eso necesitamos excepciones.
Creo que podemos pensar en excepciones como el azúcar de sintaxis para ExceptionSituationObserver. Solo recibe notificaciones de excepciones. Nada mas.
Con Go, creo que introducirán algo que se ocupará de situaciones "inesperadas". Puedo suponer que intentarán que suene menos destructivo como excepciones y más como lógica de aplicación. Pero esto es solo mi suposición.
El paradigma de manejo de excepciones de C ++, que forma una base parcial para el de Java, y a su vez .net, introduce algunos buenos conceptos, pero también tiene algunas limitaciones severas. Una de las intenciones clave del diseño del manejo de excepciones es permitir métodos para garantizar que satisfagan sus condiciones posteriores o lanzar una excepción, y también garantizar que se lleve a cabo cualquier limpieza que deba realizarse antes de que un método pueda salir. Desafortunadamente, los paradigmas de manejo de excepciones de C ++, Java y .net no brindan ningún medio adecuado para manejar la situación en la que factores inesperados impiden que se realice la limpieza esperada. Esto, a su vez, significa que uno debe arriesgarse a que todo se detenga de golpe si sucede algo inesperado (el enfoque de C ++ para manejar una excepción ocurre durante el desenrollado de la pila),
Incluso si el manejo de excepciones en general sería bueno, no es descabellado considerar inaceptable un paradigma de manejo de excepciones que no proporciona un buen medio para manejar los problemas que ocurren al limpiar después de otros problemas. Eso no quiere decir que no se pueda diseñar un marco con un paradigma de manejo de excepciones que pueda garantizar un comportamiento sensato incluso en escenarios de fallas múltiples, pero ninguno de los principales lenguajes o marcos todavía puede hacerlo.
No he leído todas las otras respuestas, por lo que es posible que esto ya se haya mencionado, pero una crítica es que hacen que los programas se rompan en largas cadenas, lo que dificulta la localización de errores al depurar el código. Por ejemplo, si Foo () llama a Bar () que llama a Wah () que llama a ToString () y luego accidentalmente empujar los datos incorrectos en ToString () termina pareciendo un error en Foo (), una función casi completamente no relacionada.
Bien, respuesta aburrida aquí. Supongo que realmente depende del idioma. Cuando una excepción puede dejar atrás los recursos asignados, deben evitarse. En los lenguajes de secuencias de comandos, simplemente abandonan o sobrepasan partes del flujo de la aplicación. Eso es desagradable en sí mismo, pero escapar de errores casi fatales con excepciones es una idea aceptable.
Para la señalización de errores, generalmente prefiero las señales de error. Todo depende de la API, el caso de uso y la gravedad, o si el registro es suficiente. También estoy tratando de redefinir el comportamiento y en su throw Phonebooks()
lugar. La idea es que las "Excepciones" son a menudo callejones sin salida, pero una "Agenda telefónica" contiene información útil sobre la recuperación de errores o rutas de ejecución alternativas. (Todavía no se ha encontrado un buen caso de uso, pero sigue intentándolo).
Para mi el tema es muy sencillo. Muchos programadores usan el manejador de excepciones de manera inapropiada. Más recursos lingüísticos es mejor. Ser capaz de manejar excepciones es bueno. Un ejemplo de mal uso es un valor que debe ser un número entero y no ser verificado, u otra entrada que puede dividir y no verificarse para la división de cero ... el manejo de excepciones puede ser una manera fácil de evitar más trabajo y pensar mucho, el programador puede querer hacer un atajo sucio y aplicar un manejo de excepciones ... La declaración: "un código profesional NUNCA falla" puede ser ilusoria, si algunos de los problemas procesados por el algoritmo son inciertos por su propia naturaleza. Quizás en las situaciones desconocidas por naturaleza sea bueno que entre en juego el manejador de excepciones. Las buenas prácticas de programación son materia de debate.