¿Cómo deben los compiladores informar errores y advertencias?

11

No planeo escribir un compilador en el futuro cercano; Aún así, estoy bastante interesado en las tecnologías de compilación y en cómo esto podría mejorarse.

Comenzando con los lenguajes compilados, la mayoría de los compiladores tienen dos niveles de error: advertencias y errores, el primero es la mayoría de las veces cosas no fatales que debe corregir, y los errores que indican la mayoría de las veces que es imposible producir máquina (o byte) código de la entrada.

Sin embargo, esta es una definición bastante débil. En algunos lenguajes como Java, ciertas advertencias son simplemente imposibles de eliminar sin usar la @SuppressWarningdirectiva. Además, Java trata ciertos problemas no fatales como errores (por ejemplo, el código inalcanzable en Java desencadena un error por una razón que me gustaría saber).

C # no tiene los mismos problemas, pero tiene algunos. Parece que la compilación se produce en varios pases, y un error de pase evitará que se ejecuten los pases posteriores. Debido a eso, el recuento de errores que obtienes cuando falla tu construcción a menudo se subestima enormemente. En una ejecución, podría decir que tiene dos errores, pero una vez que los arregle, tal vez obtendrá 26 nuevos.

Excavar en C y C ++ simplemente muestra una mala combinación en las debilidades de diagnóstico de compilación de Java y C # (aunque podría ser más exacto decir que Java y C # simplemente siguieron su camino con la mitad de los problemas cada uno). Algunas advertencias realmente deberían ser errores (por ejemplo, cuando no todas las rutas de código devuelven un valor) y aún son advertencias porque, supongo, en el momento en que escribieron el estándar, la tecnología del compilador no era lo suficientemente buena como para hacer este tipo de controles obligatorios En la misma línea, los compiladores a menudo verifican más de lo que dice el estándar, pero aún usan el nivel de error de advertencia "estándar" para los hallazgos adicionales. Y a menudo, los compiladores no informarán todos los errores que puedan encontrar de inmediato; Puede tomar algunas compilaciones deshacerse de todos ellos. Sin mencionar los errores crípticos que a los compiladores de C ++ les gusta escupir,

Ahora, agregando que muchos sistemas de compilación son configurables para informar fallas cuando los compiladores emiten advertencias, solo obtenemos una mezcla extraña: no todos los errores son fatales, pero algunas advertencias deberían; no todas las advertencias son merecidas, pero algunas se suprimen explícitamente sin mencionar más su existencia; y a veces todas las advertencias se convierten en errores.

Los idiomas no compilados aún tienen su parte de informes de errores deficientes. Los errores tipográficos en Python no se informarán hasta que el código se ejecute realmente, y nunca se puede eliminar más de un error a la vez porque el script dejará de ejecutarse después de que se encuentre con uno.

PHP, por su parte, tiene un montón de niveles de error más o menos significativos y excepciones. Los errores de análisis se informan uno a la vez, las advertencias son a menudo tan malas que deberían abortar su secuencia de comandos (pero no lo hacen de manera predeterminada), las notificaciones a menudo muestran problemas lógicos graves, algunos errores realmente no son lo suficientemente graves como para detener su secuencia de comandos, pero aún así hacer, y como de costumbre con PHP, hay algunas cosas realmente extrañas allí (¿por qué demonios necesitamos un nivel de error para errores fatales que no son realmente fatales?, E_RECOVERABLE_E_ERRORestoy hablando contigo).

Me parece que todas las implementaciones de informes de errores del compilador que se me ocurren están rotas. Lo cual es una verdadera lástima, ya que todos los buenos programadores insisten en lo importante que es tratar los errores correctamente y, sin embargo, no pueden obtener sus propias herramientas para hacerlo.

¿Cuál crees que debería ser la forma correcta de informar los errores del compilador?

zneak
fuente
-1: "Los lenguajes no compilados aún tienen su parte de informes de errores". Subjetivo y argumentativo. Realmente inútil. ¿Es esta una pregunta o una queja?
S.Lott
2
@ S.Lott Creo que estás un poco al límite aquí. Me parece que era mucho más difícil en los lenguajes compilados, y no pareció molestarte.
zneak
@zneak: las otras declaraciones están más cerca de ser objetivas y más difíciles de analizar. Esa afirmación se demostró más fácilmente como subjetiva y argumentativa.
S.Lott
1
@ S.Lott ¿Me equivoco al decir que Python indica un error a la vez?
zneak
1
@ S.Lott Entonces, las cosas deben haber cambiado, porque la última vez que lo intenté, cualquier error de sintaxis haría que Python dejara de intentar "compilarse" y un error de nombre arrojaría una excepción y no comprobaría el resto de la función (aunque esto dejó espacio para informar un error por unidad comprobable). Mi declaración subjetiva y argumentativa fue una introducción a lo que creía que era un hecho, pero si ya no es cierto, iré a editar mi pregunta. ¿Cómo funciona ahora?
zneak

Respuestas:

6

Su pregunta no parece ser sobre cómo informamos los errores del compilador, sino sobre la clasificación de los problemas y qué hacer al respecto.

Si comenzamos suponiendo, por el momento, que la dicotomía advertencia / error es correcta, veamos qué tan bien podemos construir sobre eso. Algunas ideas:

  1. Diferentes "niveles" de advertencia. Muchos compiladores implementan esto (por ejemplo, GCC tiene muchos interruptores para configurar exactamente sobre qué advertirá), pero necesita trabajo, por ejemplo, informar la gravedad de una advertencia informada y la capacidad de establecer "advertencias" son errores "solo para advertencias por encima de una gravedad especificada.

  2. Clasificación sana de errores y advertencias. Solo se debe informar un error si el código no cumple con la especificación y, por lo tanto, no se puede compilar. Las declaraciones inalcanzables, aunque probablemente sean un error de codificación, deberían ser una advertencia , no un error: el código sigue siendo "válido" y hay instancias legítimas en las que uno querría compilar con código inalcanzable (modificaciones rápidas para la depuración, por ejemplo) .

Ahora cosas en las que no estoy de acuerdo contigo:

  1. Hacer un esfuerzo extra para informar cada problema. Si hay un error, eso rompe la compilación. La construcción está rota. La compilación no funcionará hasta que se solucione ese error. Por lo tanto, es mejor informar ese error inmediatamente, en lugar de "continuar" para intentar identificar todo lo demás "incorrecto" con el código. Especialmente cuando muchas de esas cosas probablemente sean causadas por el error inicial de todos modos.

  2. Su ejemplo específico de una advertencia que debería ser un error. Sí, probablemente sea un error del programador. No, no debería romper la construcción. Si sé que la entrada a la función es tal que siempre devolverá un valor, debería poder ejecutar la compilación y hacer algunas pruebas sin tener que agregar esas comprobaciones adicionales. Sí, debería ser una advertencia. Y una maldita de alta severidad. Pero no debería romper la compilación en sí misma, a menos que se compile con advertencias son errores.

Pensamientos?

Luego.
fuente
Estoy de acuerdo con usted, excepto por los puntos en los que no estamos de acuerdo (duh), así que eso es +1 de mi parte. Creo que es bastante fácil hacer que cada ruta de código devuelva un valor o cancele su programa, considerando lo malo que es cuando realmente cae en el caso del comportamiento indefinido.
zneak
7

Un problema que mencionó fue el informe incompleto de errores, por ejemplo, informar 2 errores, y cuando los arregla, obtiene un montón más.

Esto es (en gran parte) un compromiso por parte del escritor compilador. Dependiendo de qué error que ha hecho, es muy fácil para el compilador para empezar a malinterpretar lo que haga tiene bastante mal que comienza a errores del informe que tienen muy poco que ver con la realidad. Solo por ejemplo, considere un error tipográfico simple en el que tiene algo así en itn x;lugar de int x;. A menos que haya hecho algo más que itnsignifique algo, esto se informará como un error. Eso está bien en lo que respecta, pero ahora considere lo que sucede a continuación: el compilador analiza un montón de código que intenta usar x como variable. ¿Debería A) detenerse y dejar que arregle eso, o B) arrojar 2000 errores error: "x": undeclared identifiero algo en ese orden? Considere otra posibilidad:

int main()[

Este es otro error bastante obvio, obviamente debería ser un en {lugar de un [. El compilador puede decirle esa parte con bastante facilidad, pero ¿debería luego informar un error para algo como x=1;decir algo como error: statement only allowed inside a function?

Tenga en cuenta que estos son incluso problemas bastante triviales: es mucho más fácil encontrar problemas mucho peores (especialmente, como la mayoría de nosotros sabemos, cuando ingresa a las plantillas de C ++). La conclusión es que el escritor del compilador generalmente está atascado en tratar de comprometerse entre informar errores falsos (es decir, informar algo como un error, aunque esté bien) y no informar errores reales. Hay algunas reglas generales que la mayoría sigue para tratar de evitar ir demasiado mal en cualquier dirección, pero casi ninguna de ellas está cerca de ser perfecta.

Otro problema que mencionaste fue Java y @SupressWarning. Esto es bastante diferente de lo anterior: sería bastante trivial solucionarlo. La única razón por la que no se soluciona es que hacerlo no encaja con el "carácter" básico de Java, es decir, en su opinión, "eso no es un error, es una característica". Aunque generalmente es una broma, en este caso las personas involucradas están tan equivocadas que realmente creen que es verdad.

El problema que menciona en C y C ++ con rutas de código que no devuelven un valor no es realmente para permitir compiladores primitivos. Es para permitir décadas de código existente , algunos de los cuales nadie quiere arreglar, tocar o incluso leer. Es antiguo y feo, pero funciona, y nadie quiere nada más que seguir trabajando. Para bien o para mal, los comités de idiomas están bastante atascados con el mantenimiento de esa compatibilidad con versiones anteriores, por lo que continúan permitiendo cosas que a nadie realmente le gustan, pero algunas personas (al menos piensan que necesitan).

Jerry Coffin
fuente
3
Además de su punto sobre los errores tempranos que causan muchos otros, también existe el hecho de que los pases posteriores a menudo se crean para requerir que los pases anteriores se hayan completado con éxito. Por ejemplo, uno de los primeros pasos en el compilador de C # verifica para asegurarse de que no haya ciclos en el gráfico de herencia: no tiene A hereda de B que hereda de A. Si desea continuar y generar una lista de todos los errores después de eso, cada pasada posterior debería ser capaz de hacer frente a los ciclos, lo que lo hace significativamente más lento incluso en compilaciones "buenas".
Anon
@Luego. El compilador de Java hace esfuerzos mucho mejores para sobrevivir a los primeros pases, y no lo encuentro significativamente más lento. Para mí es algo molesto lo rápido que se cscrinde.
zneak
@zneak: Como dice Jerry, es un compromiso por parte de los desarrolladores de los compiladores. Escribir buenos diagnósticos de errores es en realidad un problema muy difícil (mire el clang para ver un ejemplo de cuán lejos realmente puede llevarlo). Vea aquí para una buena discusión de las fases y pases del compilador de C #.
Dean Harding