Entiendo que salvo por romper bucles anidados en bucles; la goto
declaración es evadida y vilipendiada como un estilo de programación propenso a errores, que nunca se utilizará.
Texto alternativo : "Neal Stephenson piensa que es lindo nombrar sus etiquetas 'dengo'".
Vea el cómic original en: http://xkcd.com/292/
Porque aprendí esto temprano; Realmente no tengo ninguna idea o experiencia sobre a qué tipo de errores goto
realmente conduce. Entonces, ¿de qué estamos hablando aquí?
- ¿Inestabilidad?
- ¿Código no mantenible o ilegible?
- ¿Vulnerabilidades de seguridad?
- ¿Algo más por completo?
¿A qué tipo de errores conducen realmente las declaraciones "goto"? ¿Hay algún ejemplo históricamente significativo?
Respuestas:
Aquí hay una manera de verlo que aún no he visto en las otras respuestas.
Se trata de alcance. Uno de los pilares principales de las buenas prácticas de programación es mantener su alcance ajustado. Necesita un alcance limitado porque carece de la capacidad mental para supervisar y comprender más que unos pocos pasos lógicos. Entonces crea pequeños bloques (cada uno de los cuales se convierte en "una cosa") y luego construye con ellos para crear bloques más grandes que se convierten en una sola cosa, y así sucesivamente. Esto mantiene las cosas manejables y comprensibles.
Un goto efectivamente infla el alcance de su lógica a todo el programa. Esto seguramente derrotará a su cerebro para cualquiera de los programas más pequeños que abarcan solo unas pocas líneas.
Por lo tanto, ya no podrá saber si cometió un error porque hay demasiado para controlar y controlar su pobre cabecita. Este es el verdadero problema, los errores son solo un resultado probable.
fuente
No es que
goto
sea malo en sí mismo. (Después de todo, cada instrucción de salto en una computadora es un goto.) El problema es que existe un estilo humano de programación que precede a la programación estructurada, lo que podría llamarse programación de "diagrama de flujo".En la programación de diagramas de flujo (que la gente de mi generación aprendió y se usó para el programa Apollo moon), hace un diagrama con bloques para ejecuciones de declaraciones y diamantes para decisiones, y puede conectarlos con líneas que van por todo el lugar. (El llamado "código de espagueti").
El problema con el código de espagueti es que usted, el programador, podría "saber" que era correcto, pero ¿cómo podría probarlo usted mismo o cualquier otra persona? De hecho, podría tener un mal comportamiento potencial, y su conocimiento de que siempre es correcto podría estar equivocado.
Junto vino la programación estructurada con bloques de inicio-fin, para, while, if-else, y así sucesivamente. Estos tenían la ventaja de que aún podía hacer cualquier cosa en ellos, pero si fuera cuidadoso, podría estar seguro de que su código era correcto.
Por supuesto, la gente aún puede escribir código de espagueti incluso sin él
goto
. El método común es escribir unwhile(...) switch( iState ){...
, donde diferentes casos estableceniState
valores diferentes. De hecho, en C o C ++ se podría escribir una macro para hacer eso y nombrarlaGOTO
, por lo que decir que no está usandogoto
es una distinción sin una diferencia.Como ejemplo de cómo la prueba de código puede evitar restricciones
goto
, hace mucho tiempo me topé con una estructura de control útil para cambiar dinámicamente las interfaces de usuario. Lo llamé ejecución diferencial . Es totalmente universal de Turing, pero su prueba de corrección depende de la programación estructurada pura - singoto
,return
,continue
,break
, o excepciones.fuente
¿Por qué es peligroso ir a Goto?
goto
no causa inestabilidad por sí mismo. A pesar de unos 100.000goto
s, el kernel de Linux sigue siendo un modelo de estabilidad.goto
por sí solo no debe causar vulnerabilidades de seguridad. Sin embargo, en algunos idiomas, mezclarlo contry
/catch
bloques de administración de excepciones podría generar vulnerabilidades como se explica en esta recomendación del CERT . Los compiladores de C ++ convencionales señalan y evitan tales errores, pero desafortunadamente, los compiladores más antiguos o más exóticos no lo hacen.goto
causa código ilegible e imposible de mantener. Esto también se llama código de espagueti , porque, como en un plato de espagueti, es muy difícil seguir el flujo de control cuando hay demasiados gotos.Incluso si logras evitar el código de espagueti y si usas solo algunos gotos, todavía facilitan errores como la fuga de recursos:
goto
declaración, rompes ese flujo directo y rompes las expectativas. Por ejemplo, es posible que no note que todavía tiene que liberar recursos.goto
en diferentes lugares pueden enviarte a un solo objetivo de goto. Por lo tanto, no es obvio saber con certeza el estado en el que se encuentra al llegar a este lugar. El riesgo de hacer suposiciones erróneas / infundadas es, por lo tanto, bastante grande.Información adicional y cotizaciones:
E.Dijkstra escribió un primer ensayo sobre el tema ya en 1968: " Ir a la declaración considerada perjudicial "
Brian.W.Kernighan & Dennis.M.Ritchie escribieron en el lenguaje de programación C:
James Gosling y Henry McGilton escribieron en su libro blanco sobre el entorno del lenguaje Java de 1995 :
Bjarne Stroustrup define goto en su glosario en estos términos atractivos:
¿Cuándo se puede usar goto?
Como K&R, no soy dogmático sobre los gotos. Admito que hay situaciones en las que goto podría facilitar la vida.
Por lo general, en C, goto permite la salida de bucle multinivel, o el manejo de errores que requiere alcanzar un punto de salida apropiado que libera / desbloquea todos los recursos asignados hasta ahora (es decir, la asignación múltiple en secuencia significa múltiples etiquetas). Este artículo cuantifica los diferentes usos del goto en el kernel de Linux.
Personalmente prefiero evitarlo y en 10 años de C, usé un máximo de 10 gotos. Prefiero usar
if
s anidados , que creo que son más legibles. Cuando esto llevaría a un anidamiento demasiado profundo, optaría por descomponer mi función en partes más pequeñas o usar un indicador booleano en cascada. Los compiladores de optimización actuales son lo suficientemente inteligentes como para generar casi el mismo código que el mismo códigogoto
.El uso de goto depende en gran medida del idioma:
En C ++, el uso adecuado de RAII hace que el compilador destruya automáticamente los objetos que quedan fuera del alcance, de modo que los recursos / bloqueo se limpiarán de todos modos, y ya no será necesario volver a usarlos.
En Java no hay necesidad de Goto (ver cita de autor de Java anterior y este excelente respuesta de desbordamiento de pila ): el recolector de basura que limpia el desorden,
break
,continue
, ytry
/catch
manejo de excepciones cubre todo el caso en el quegoto
puedan ser de utilidad, pero en un mejor y más seguro conducta. La popularidad de Java demuestra que la declaración goto se puede evitar en un lenguaje moderno.Zoom sobre la famosa vulnerabilidad SSL goto fail
Descargo de responsabilidad importante: en vista de la feroz discusión en los comentarios, quiero aclarar que no pretendo que la declaración goto sea la única causa de este error. No pretendo que sin ir a Goto no habría ningún error. Solo quiero mostrar que un goto puede estar involucrado en un error grave.
No sé cuántos errores graves están relacionados
goto
en la historia de la programación: los detalles a menudo no se comunican. Sin embargo, hubo un famoso error SSL de Apple que debilitó la seguridad de iOS. La declaración que condujo a este error fue unagoto
declaración incorrecta .Algunos argumentan que la causa raíz del error no fue la declaración goto en sí misma, sino una copia / pegar incorrecta, una sangría engañosa, falta de llaves alrededor del bloque condicional, o tal vez los hábitos de trabajo del desarrollador. Tampoco puedo confirmar ninguno de ellos: todos estos argumentos son hipótesis e interpretación probables. Nadie lo sabe realmente. ( mientras tanto, la hipótesis de una fusión que salió mal como alguien sugirió en los comentarios parece ser un muy buen candidato en vista de algunas otras inconsistencias de sangría en la misma función ).
El único hecho objetivo es que un duplicado
goto
condujo a salir prematuramente de la función. Mirando el código, la única otra declaración única que podría haber causado el mismo efecto habría sido una devolución.El error está en función
SSLEncodeSignedServerKeyExchange()
en este archivo :De hecho, las llaves alrededor del bloque condicional podrían haber evitado el error:
habría provocado un error de sintaxis en la compilación (y, por lo tanto, una corrección) o un goto inofensivo redundante. Por cierto, GCC 6 podría detectar estos errores gracias a su advertencia opcional para detectar sangría inconsistente.
Pero en primer lugar, todos estos problemas podrían haberse evitado con un código más estructurado. Entonces goto es al menos indirectamente una causa de este error. Hay al menos dos formas diferentes que podrían haberlo evitado:
Enfoque 1: si la cláusula
if
es anidadaEn lugar de probar muchas condiciones de error secuencialmente, y cada vez que se envía a una
fail
etiqueta en caso de problema, uno podría haber optado por ejecutar las operaciones criptográficas en unaif
declaración que lo haría solo si no hubiera una precondición incorrecta:Enfoque 2: use un acumulador de errores
Este enfoque se basa en el hecho de que casi todas las declaraciones aquí llaman a alguna función para establecer un
err
código de error y ejecutan el resto del código solo sierr
fue 0 (es decir, la función se ejecutó sin error). Una buena alternativa segura y legible es:Aquí, no hay un solo goto: no hay riesgo de saltar rápidamente al punto de salida de falla. Y visualmente sería fácil detectar una línea desalineada o olvidada
ok &&
.Esta construcción es más compacta. Se basa en el hecho de que en C, la segunda parte de un lógico y (
&&
) se evalúa solo si la primera parte es verdadera. De hecho, el ensamblador generado por un compilador optimizador es casi equivalente al código original con gotos: el optimizador detecta muy bien la cadena de condiciones y genera código, que en el primer valor de retorno no nulo salta al final ( prueba en línea ).Incluso podría prever una comprobación de coherencia al final de la función que podría identificar durante la fase de prueba las discrepancias entre el indicador ok y el código de error.
Errores tales como un
==0
reemplazo inadvertido por un!=0
error de conector lógico se detectarían fácilmente durante la fase de depuración.Como dije: no pretendo que las construcciones alternativas hubieran evitado cualquier error. Solo quiero decir que podrían haber hecho que el error sea más difícil de producir.
fuente
goto
dentro de unaif
declaración sin llaves. Pero si está sugiriendo que evitargoto
hará que su código sea inmune a los efectos de líneas duplicadas al azar, tengo malas noticias para usted.if
declaración? Tiempos divertidos. :)El famoso artículo de Dijkstra fue escrito en un momento en que algunos lenguajes de programación eran realmente capaces de crear subrutinas con múltiples puntos de entrada y salida. En otras palabras, podría saltar literalmente al centro de una función y saltar en cualquier lugar dentro de la función, sin llamar a esa función o regresar de la manera convencional. Eso sigue siendo cierto para el lenguaje ensamblador. Nadie argumenta que dicho enfoque es superior al método estructurado de escribir software que ahora utilizamos.
En la mayoría de los lenguajes de programación modernos, las funciones se definen muy específicamente con un punto de entrada y un punto de salida. El punto de entrada es donde especifica los parámetros de la función y lo llama, y el punto de salida es donde devuelve el valor resultante y continúa la ejecución en la instrucción que sigue a la llamada a la función original.
Dentro de esa función, deberías poder hacer lo que quieras, dentro de lo razonable. Si poner un goto o dos en la función lo hace más claro o mejora su velocidad, ¿por qué no? El objetivo de una función es secuestrar un poco de funcionalidad claramente definida, para que ya no tenga que pensar en cómo funciona internamente. Una vez que está escrito, solo lo usa.
Y sí, puede tener múltiples declaraciones de retorno dentro de una función; siempre hay un lugar en una función adecuada desde el que regresa (el reverso de la función, básicamente). Eso no es lo mismo que saltar de una función con un goto antes de que tenga la oportunidad de regresar adecuadamente.
Entonces no se trata realmente de usar goto's. Se trata de evitar su abuso. Todos están de acuerdo en que puedes hacer un programa terriblemente complicado usando gotos, pero también puedes hacer lo mismo abusando de las funciones (es mucho más fácil abusar de los gotos).
Por lo que vale, desde que me gradué de los programas BASIC de estilo de número de línea a la programación estructurada usando Pascal y lenguajes de llaves, nunca he tenido que usar un goto. La única vez que he tenido la tentación de usar uno es hacer una salida temprana de un bucle anidado (en idiomas que no admiten la salida temprana de varios niveles de los bucles), pero generalmente puedo encontrar otra forma más limpia.
fuente
Solía usar
goto
declaraciones cuando escribía programas BASIC cuando era niño como una forma simple de obtener bucles for y while (Commodore 64 BASIC no tenía bucles while, y era demasiado inmaduro para aprender la sintaxis y el uso adecuados de los bucles for ) Mi código era frecuentemente trivial, pero cualquier error de bucle podría atribuirse inmediatamente a mi uso de goto.Ahora uso principalmente Python, un lenguaje de programación de alto nivel que ha determinado que no necesita goto.
Cuando Edsger Dijkstra declaró "Goto considerado dañino" en 1968, no dio un puñado de ejemplos en los que los errores relacionados pudieran atribuirse al goto, sino que declaró que
goto
era innecesario para los idiomas de nivel superior y que debería evitarse a favor de lo que hoy consideramos el flujo de control normal: bucles y condicionales. Sus palabras:Probablemente tenía montañas de ejemplos de errores cada vez que depuraba el código
goto
. Pero su documento era una declaración de posición generalizada respaldada por pruebas quegoto
eran innecesarias para los idiomas de nivel superior. El error generalizado es que es posible que no tenga la capacidad de analizar estáticamente el código en cuestión.El código es mucho más difícil de analizar estáticamente con
goto
declaraciones, especialmente si retrocede en su flujo de control (que solía hacer) o en alguna sección de código no relacionada. El código puede y fue escrito de esta manera. Se realizó para optimizar en gran medida los recursos informáticos muy escasos y, por lo tanto, las arquitecturas de las máquinas.Un ejemplo apócrifo
Hubo un programa de blackjack que un responsable de mantenimiento consideró que estaba optimizado con elegancia pero que también era " imposible" para él debido a la naturaleza del código. Fue programado en código de máquina que depende en gran medida de los gotos, así que creo que esta historia es bastante relevante. Este es el mejor ejemplo canónico que se me ocurre.
Un contraejemplo
Sin embargo, la fuente C de CPython (la implementación de Python más común y de referencia) usa
goto
declaraciones con gran efecto. Se utilizan para evitar el flujo de control irrelevante dentro de las funciones para llegar al final de las funciones, haciendo que las funciones sean más eficientes sin perder legibilidad. Esto respeta uno de los ideales de la programación estructurada : tener un único punto de salida para las funciones.Para el registro, considero que este contraejemplo es bastante fácil de analizar estáticamente.
fuente
Cuando el wigwam era un arte arquitectónico actual, sus constructores sin duda podían darle consejos prácticos sobre la construcción de wigwams, cómo dejar escapar el humo, etc. Afortunadamente, los constructores de hoy probablemente pueden olvidar la mayoría de esos consejos.
Cuando la diligencia era un arte de transporte actual, sus conductores sin duda podrían darle consejos prácticos sobre caballos de diligencias, cómo defenderse de los bandoleros, etc. Afortunadamente, los automovilistas de hoy también pueden olvidar la mayoría de esos consejos.
Cuando las tarjetas perforadas eran arte de programación actual, sus practicantes también podrían brindarle consejos prácticos sobre la organización de las tarjetas, cómo numerar las declaraciones, etc. No estoy seguro de que ese consejo sea muy relevante hoy.
¿Eres lo suficientemente mayor como para saber la frase "numerar declaraciones" ? No necesita saberlo, pero si no lo hace, entonces no está familiarizado con el contexto histórico en el que la advertencia contra
goto
era principalmente relevante.La advertencia en contra
goto
no es muy relevante hoy. Con entrenamiento básico en bucles while / for y llamadas a funciones, ni siquiera pensará en emitir ungoto
muy a menudo. Cuando lo pienses, probablemente tengas una razón, así que adelante.Pero, ¿
goto
no se puede abusar de la declaración?Respuesta: claro, se puede abusar de él, pero su abuso es un problema bastante pequeño en la ingeniería de software en comparación con errores mucho, mucho más comunes, como el uso de una variable donde servirá una constante, o como la programación de cortar y pegar (de lo contrario conocido como negligencia para refactorizar). Dudo que estés en peligro. A menos que esté usando
longjmp
o transfiriendo el control a un código lejano, si piensa usar ungoto
o simplemente desea probarlo por diversión, continúe. Estarás bien.Puede notar la falta de historias de terror recientes en las que
goto
interpreta al villano. La mayoría o todas estas historias parecen tener 30 o 40 años. Te paras en tierra firme si consideras esas historias como obsoletas.fuente
Para agregar una cosa a las otras respuestas excelentes, con las
goto
declaraciones puede ser difícil decir exactamente cómo llegaste a un lugar determinado en el programa. Puede saber que se produjo una excepción en alguna línea específica, pero si haygoto
códigos en el código, no hay forma de saber qué sentencias ejecutadas para que esta excepción provoque un estado sin buscar en todo el programa. No hay pila de llamadas ni flujo visual. Es posible que haya una declaración a 1000 líneas de distancia que lo puso en un mal estado ejecutadogoto
a en la línea que generó una excepción.fuente
On Error Goto errh
, puede usarResume
para volver a intentarlo,Resume Next
omitir la línea ofensiva yErl
saber qué línea era (si usa números de línea).Resume
volverá a ejecutar esa función desde el principio yResume Next
omitirá todo en la función que aún no lo ha hecho.On Error Goto 0
y depurar manualmente.Resume
está destinado a ser utilizado después de verificarErr.Number
y hacer los ajustes necesarios.goto
solo funciona con líneas etiquetadas, que parecen haber sido reemplazadas por bucles etiquetados .Goto es más difícil de razonar para los humanos que otras formas de control de flujo.
Programar el código correcto es difícil. Escribir programas correctos es difícil, determinar si los programas son correctos es difícil, probar programas correctos es difícil.
Obtener código para hacer vagamente lo que quieres es fácil en comparación con todo lo demás sobre programación.
Goto resuelve algunos programas con la obtención de código para hacer lo que quieras. No ayuda a facilitar la verificación de la corrección, mientras que sus alternativas a menudo lo hacen.
Hay estilos de programación donde goto es la solución adecuada. Incluso hay estilos de programación donde su gemelo malvado, proveniente de, es la solución adecuada. En ambos casos, se debe tener mucho cuidado para asegurarse de que lo esté utilizando en un patrón comprensible, y mucha comprobación manual de que no está haciendo algo difícil para garantizar su corrección.
Como ejemplo, hay una característica de algunos idiomas llamados corutinas. Las corutinas son hilos sin hilos; estado de ejecución sin un hilo para ejecutarlo. Puede pedirles que se ejecuten, y pueden ejecutar parte de sí mismos y luego suspenderse, devolviendo el control de flujo.
Las corutinas "superficiales" en lenguajes sin soporte de corutina (como C ++ pre-C ++ 20 y C) son posibles utilizando una mezcla de gotos y gestión de estado manual. Las rutinas "profundas" se pueden hacer usando las funciones
setjmp
ylongjmp
de C.Hay casos en los que las corutinas son tan útiles que vale la pena escribirlas manualmente y con cuidado.
En el caso de C ++, se les encuentra lo suficientemente útiles como para ampliar el lenguaje para admitirlos. La
goto
gestión de estado manual y de estado se oculta detrás de una capa de abstracción de costo cero, lo que permite a los programadores escribirla sin la dificultad de tener que demostrar que su desorden de goto,void**
s, construcción manual / destrucción de estado, etc. es correcta.El goto se oculta detrás de una abstracción de nivel superior, como
while
ofor
oif
oswitch
. Esas abstracciones de nivel superior son más fáciles de comprobar y verificar.Si faltaban algunos de ellos en el idioma (como en algunos idiomas modernos faltan corutinas), cojear junto con un patrón que no se ajusta al problema o usar goto se convierten en sus alternativas.
Hacer que una computadora haga vagamente lo que quieres que haga en casos comunes es fácil . Escribir código confiablemente robusto es difícil . Goto ayuda al primero mucho más de lo que ayuda al segundo; por lo tanto, "se considera dañino", ya que es una señal de código superficialmente "funcional" con errores profundos y difíciles de rastrear. Con el esfuerzo suficiente, aún puede hacer que el código con "goto" sea confiablemente robusto, por lo que mantener la regla de absoluto es incorrecto; pero como regla general, es buena.
fuente
longjmp
solo es legal en C para desenrollar la pila a unasetjmp
función principal. (Como romper varios niveles de anidamiento, pero para llamadas a funciones en lugar de bucles).longjjmp
a un contexto desetjmp
hecho en una función que desde entonces ha regresado no es legal (y sospecho que no funcionará en la práctica en la mayoría de los casos). No estoy familiarizado con las co-rutinas, pero por su descripción de una co-rutina como similar a un hilo de espacio de usuario, creo que necesitaría su propia pila, así como un contexto de registro guardado, ylongjmp
solo le da registro -ahorro, no una pila separada.Eche un vistazo a este código, de http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html que en realidad era parte de una gran simulación de Dilema del prisionero. Si ha visto código FORTRAN o BASIC antiguo, se dará cuenta de que no es tan inusual.
Hay muchos problemas aquí que van mucho más allá de la declaración GOTO aquí; Sinceramente, creo que la declaración GOTO fue un chivo expiatorio. Pero el flujo de control no está absolutamente claro aquí, y el código se mezcla de manera que no queda muy claro lo que está sucediendo. Incluso sin agregar comentarios o usar mejores nombres de variables, cambiar esto a una estructura de bloque sin GOTO facilitaría mucho la lectura y el seguimiento.
fuente
Uno de los principios de la programación mantenible es la encapsulación . El punto es que usted interactúa con un módulo / rutina / subrutina / componente / objeto utilizando una interfaz definida, y solo esa interfaz, y los resultados serán predecibles (suponiendo que la unidad se haya probado efectivamente).
Dentro de una sola unidad de código, se aplica el mismo principio. Si aplica constantemente la programación estructurada o los principios de programación orientada a objetos, no:
Algunos de los síntomas más comunes de estos errores de procesamiento incluyen pérdidas de memoria, acumulación de memoria, desbordamientos de puntero, bloqueos, registros de datos incompletos, excepciones de agregar / cambiar / eliminar registros, fallas de página de memoria, etc.
Las manifestaciones observables por el usuario de estos problemas incluyen bloqueo de la interfaz de usuario, disminución gradual del rendimiento, registros de datos incompletos, incapacidad para iniciar o completar transacciones, datos corruptos, cortes de red, cortes de energía, fallas de sistemas integrados (por la pérdida de control de misiles a la pérdida de la capacidad de seguimiento y control en los sistemas de control de tráfico aéreo), fallas en el temporizador, y así sucesivamente. El catálogo es muy extenso.
fuente
goto
ni una sola vez. :)goto es peligroso porque generalmente se usa donde no es necesario. Usar cualquier cosa que no sea necesaria es peligroso, pero especialmente ir . Si googleas, encontrarás muchos errores causados por goto, esto no es una razón per se para no usarlo (los errores siempre ocurren cuando usas funciones de lenguaje porque eso es intrínseco en la programación), pero algunos de ellos están claramente muy relacionados para ir al uso.
Razones para usar / no usar goto :
Si necesita un bucle, debe usar
while
ofor
.Si necesita hacer un salto condicional, use
if/then/else
Si necesita procedimiento, llame a una función / método.
Si necesita salir de una función, solo
return
.Puedo contar con mis dedos lugares donde he visto
goto
usar y usar correctamente.En libKTX hay una función que tiene el siguiente código
Ahora en este lugar
goto
es útil porque el lenguaje es C:Este caso de uso es útil porque en C no tenemos clases, por lo que la forma más sencilla de limpiar es usar a
goto
.Si teníamos el mismo código en C ++ ya no hay necesidad de ir a Goto:
¿A qué tipo de errores puede conducir? Cualquier cosa. Bucles infinitos, orden de ejecución incorrecto, tornillo de pila.
Un caso documentado es una vulnerabilidad en SSL que permitió ataques Man in the middle causados por el uso incorrecto de goto: aquí está el artículo
Eso es un error tipográfico, pero pasó desapercibido por un tiempo, si el código se estructuraba de otra manera, se probaba correctamente, ese error no podría haber sido posible.
fuente