¿GOTO todavía se considera dañino? [cerrado]

282

Todos conocen las Cartas de Dijkstra al editor: vaya a la declaración considerada perjudicial (también aquí transcripción .html y aquí .pdf) y ha habido un impulso formidable desde ese momento para evitar la declaración goto siempre que sea posible. Si bien es posible usar goto para producir un código extenso e imposible de mantener, no obstante permanece en los lenguajes de programación modernos . Incluso la estructura avanzada de control de continuación en Scheme puede describirse como un goto sofisticado.

¿Qué circunstancias justifican el uso de goto? ¿Cuándo es mejor evitarlo?

Como una pregunta de seguimiento: C proporciona un par de funciones, setjmp y longjmp, que proporcionan la capacidad de ir no solo dentro del marco de pila actual sino dentro de cualquiera de los marcos de llamada. ¿Deberían considerarse estos tan peligrosos como el goto? ¿Más peligroso?


El propio Dijkstra lamentó ese título, del que no era responsable. Al final de EWD1308 (también aquí .pdf) escribió:

Finalmente una historia corta para el registro. En 1968, las Comunicaciones de la ACM publicaron un texto mío bajo el título " La declaración Goto considerada dañina ", que en años posteriores sería mencionada con mayor frecuencia, sin embargo, lamentablemente, a menudo por autores que no habían visto más de ella título, que se convirtió en una piedra angular de mi fama al convertirse en una plantilla: veríamos todo tipo de artículos bajo el título "X considerado dañino" para casi cualquier X, incluido uno titulado "Dijkstra considerado dañino". ¿Pero que había pasado? Había presentado un documento con el título " Un caso contra la declaración Goto", que, para acelerar su publicación, el editor había cambiado a una" carta al Editor ", ¡y en el proceso le había dado un nuevo título de su propia invención! El editor era Niklaus Wirth.

Un artículo clásico bien pensado sobre este tema, que debe coincidir con el de Dijkstra, es Programación estructurada con Ir a declaraciones , por Donald E. Knuth. Leer ambos ayuda a restablecer el contexto y una comprensión no dogmática del tema. En este documento, se informa la opinión de Dijkstra sobre este caso y es aún más fuerte:

Donald E. Knuth: Creo que al presentar tal punto de vista no estoy en desacuerdo con las ideas de Dijkstra, ya que recientemente escribió lo siguiente: "Por favor, no caigas en la trampa de creer que soy terriblemente dogmático sobre [el ir a la declaración]. ¡Tengo la incómoda sensación de que otros están haciendo de ella una religión, como si los problemas conceptuales de la programación pudieran resolverse con un solo truco, con una forma simple de disciplina de codificación! "

MaD70
fuente
1
El goto de C # no es el mismo que el goto del que hablaba Dijkstra, precisamente por las razones de las que habló Dijkstra. Ese goto es tan dañino como lo era en aquel entonces, pero también mucho menos necesario, porque los lenguajes modernos proporcionan estructuras de control alternativas. C # goto está extremadamente limitado.
jalf
25
Los gotos son buenos cuando agregan claridad. Si tiene un ciclo anidado largo, puede ser mejor salir de él que establecer variables "break" y romper hasta que salga.
simendsjo
28
Si tiene un bucle anidado en 4 profundidades (no es que sea algo bueno), romperlo requiere establecer valores temporales. Un goto aquí es mucho más claro para mí, y el IDE debería mostrar fácilmente dónde está el goto. Dicho esto, el uso de goto debería ser escaso y, en mi opinión, solo bajar para omitir código
simendsjo
77
Te sugiero que vayas a leer los nueve mil y un hilos etiquetados goto.
Matti Virkkunen
55
Hay una cosa claramente peor que usar goto: piratear herramientas de programación estructuradas para implementar a goto.

Respuestas:

184

Las siguientes declaraciones son generalizaciones; Si bien siempre es posible alegar una excepción, por lo general (en mi experiencia y humilde opinión) no vale la pena arriesgarse.

  1. El uso sin restricciones de las direcciones de memoria (GOTO o punteros sin formato) ofrece demasiadas oportunidades para cometer errores fácilmente evitables.
  2. Cuantas más formas haya de llegar a una "ubicación" particular en el código, menos seguro podrá estar sobre cuál es el estado del sistema en ese punto. (Vea abajo.)
  3. Programación estructurada En mi humilde opinión, se trata menos de "evitar GOTO" y más de hacer que la estructura del código coincida con la estructura de los datos. Por ejemplo, una estructura de datos repetitiva (por ejemplo, matriz, archivo secuencial, etc.) es procesada naturalmente por una unidad repetida de código. Tener estructuras integradas (por ejemplo, while, for, until, for-each, etc.) permite al programador evitar el tedio de repetir los mismos patrones de código cliché.
  4. Incluso si GOTO es un detalle de implementación de bajo nivel (¡no siempre es el caso!), Está por debajo del nivel que el programador debería estar pensando. ¿Cuántos programadores equilibran sus chequeras personales en binario sin formato? ¿Cuántos programadores se preocupan sobre qué sector del disco contiene un registro en particular, en lugar de simplemente proporcionar una clave para un motor de base de datos (y de cuántas maneras podrían salir mal las cosas si realmente escribiéramos todos nuestros programas en términos de sectores de disco físico)?

Notas a pie de página a lo anterior:

Con respecto al punto 2, considere el siguiente código:

a = b + 1
/* do something with a */

En el punto "hacer algo" en el código, podemos afirmar con alta confianza que aes mayor que b. (Sí, estoy ignorando la posibilidad de desbordamiento de enteros sin atrapar. No empañemos un ejemplo simple).

Por otro lado, si el código se hubiera leído de esta manera:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

La multiplicidad de formas de llegar a la etiqueta 10 significa que tenemos que trabajar mucho más para tener confianza en las relaciones entre ay ben ese punto. (De hecho, ¡en el caso general es indecidible!)

Con respecto al punto 4, toda la noción de "ir a algún lado" en el código es solo una metáfora. Nada está realmente "yendo" a ninguna parte dentro de la CPU, excepto electrones y fotones (por el calor residual). A veces renunciamos a una metáfora para otra, más útil, una. Recuerdo haber encontrado (¡hace unas décadas!) Un lenguaje donde

if (some condition) {
  action-1
} else {
  action-2
}

se implementó en una máquina virtual compilando action-1 y action-2 como rutinas sin parámetros fuera de línea, luego usando un código de operación de VM de dos argumentos que usaba el valor booleano de la condición para invocar uno u otro. El concepto era simplemente "elegir qué invocar ahora" en lugar de "ir aquí o ir allá". De nuevo, solo un cambio de metáfora.

joel.neely
fuente
2
Un buen punto En los idiomas de nivel superior, ir a goto ni siquiera significa nada (considere saltar entre métodos en Java). Una función de Haskell puede consistir en una sola expresión; ¡intenta saltar de eso con un goto!
Caracol mecánico
2
Postscript funciona como su ejemplo del punto 4.
luser droog
Smalltalk funciona de manera similar al ejemplo del punto 4, si por "de manera similar" quiere decir "nada como lo hacen los lenguajes de procedimiento". : P No hay control de flujo en el lenguaje; Todas las decisiones se manejan a través del polimorfismo ( truey falseson de diferentes tipos), y cada rama de un if / else es básicamente una lambda.
cHao
Estos son puntos válidos, pero al final solo reiteran cuán malo puede ser el goto "si se usa incorrectamente". Break, continue, exit, return, gosub, settimeout, global, include, etc. son todas técnicas modernas que requieren un seguimiento mental del flujo de las cosas y pueden ser mal utilizadas para crear código de espagueti y saltar para crear incertidumbre de estados variables. Para ser justos, aunque nunca he visto un mal uso de goto de primera mano, solo lo he visto una o dos veces. Eso habla de la afirmación de que siempre hay mejores cosas para usar.
Beejor
gotoen un lenguaje de programación moderno (Go) stackoverflow.com/a/11065563/3309046 .
nishanths
245

Cómic GOTO de XKCD

Un compañero de trabajo mío dijo que la única razón para usar un GOTO es si te has programado tanto en una esquina que es la única salida. En otras palabras, un diseño adecuado con anticipación y no necesitará usar un GOTO más adelante.

Pensé que este cómic ilustra que bellamente "podría reestructurar el flujo del programa, o usar un pequeño 'GOTO' en su lugar". Un GOTO es una salida débil cuando tienes un diseño débil. Los velociraptores se aprovechan de los débiles .

Jim McKeeth
fuente
41
GOTO puede "saltar" de un punto arbitrario a otro punto arbitrario. ¡Velociraptor saltó hasta aquí de la nada!
rpattabi
29
No me parece gracioso el chiste porque todo el mundo sabe que también debes vincularlo antes de poder ejecutarlo.
Kinjal Dixit
31
Su compañero de trabajo está equivocado y obviamente no leyó el periódico de Knuth.
Jim Balter
27
No he visto un final de código ofuscado y contorsionado a lo largo de los años para evitar un goto. @jimmcKeeth, la caricatura xkcd anterior no establece que goto sea débil. Se está burlando de la histeria en torno al uso.
44
Te das cuenta de que el código fuente de la biblioteca Delphi contiene declaraciones goto. Y estos son usos apropiados de goto.
David Heffernan
131

A veces es válido usar GOTO como alternativa al manejo de excepciones dentro de una sola función:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

El código COM parece caer en este patrón con bastante frecuencia.

Rob Walker
fuente
29
Estoy de acuerdo, hay casos de uso legítimos donde, goto puede simplificar el código y hacerlo más legible / mantenible, pero parece haber algún tipo de goto-fobia flotando ...
Pop Catalin
48
@Bob: es difícil mover el código err_cleanup a una subrutina si está limpiando variables locales.
Niki
44
En realidad, lo usé en COM / VB6 solo porque no tenía otra alternativa, no porque fuera una alternativa. Qué feliz estoy hoy en día con try / catch / finally.
Rui Craveiro
10
@ user4891 La forma idiomática de C ++ no es probar {} catch () {cleanup; }, sino más bien, RAII, donde los recursos que deben limpiarse se realizan en destructores. Cada constructor / destructor gestiona exactamente un recurso.
David Stone
55
Hay dos formas de escribir esto en C sin ir a; y ambos son mucho más cortos. O bien: if (f ()) if (g ()) if (h ()) devuelve el éxito; limpiar(); falla de retorno; o: si (f () && g () && h ()) devuelve el éxito; limpiar(); falla de retorno;
LHP
124

Solo recuerdo haber usado un goto una vez. Tenía una serie de cinco bucles contados anidados y necesitaba poder salir de toda la estructura desde el interior temprano en función de ciertas condiciones:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Podría haber declarado fácilmente una variable de corte booleana y usarla como parte del condicional para cada ciclo, pero en este caso decidí que un GOTO era tan práctico y tan legible.

Ningún velociraptor me atacó.

shsteimer
fuente
83
"Refactorizarlo en una función y reemplazar goto con return :)", y la diferencia es? ¿Cuál es realmente la diferencia? no es volver a ir también? Los retornos también frenan el flujo estructurado de lo que hace goto, y en este caso lo hacen de la misma manera (incluso si se puede usar goto para cosas más malas)
Pop Catalin
38
Anidar muchos bucles suele ser un código que huele todo. A menos que esté haciendo, como, una multiplicación de matriz de 5 dimensiones, es difícil imaginar una situación en la que algunos de los bucles internos no puedan extraerse de manera útil en funciones más pequeñas. Como todas las reglas generales, supongo que hay algunas excepciones.
Doug McClean
55
Reemplazarlo con una devolución solo funciona si está utilizando un idioma que admite devoluciones.
Loren Pechtel el
31
@leppie: La generación que se rebeló gotoy nos dio una programación estructurada también rechazó los primeros retornos, por la misma razón. Todo se reduce a qué tan legible es el código, qué tan claramente expresa la intención del programador. Crear una función con el único propósito de evitar el uso de una palabra clave difamada resulta en una mala cohesión: la cura es peor que la enfermedad.
Barro
21
@ButtleButkus: Francamente, eso es igual de malo, si no peor. Al menos con a goto, se puede especificar explícitamente el objetivo. Con break 5;, (1) tengo que contar los cierres de bucle para encontrar el destino; y (2) si la estructura del bucle alguna vez cambia, bien puede requerir cambiar ese número para mantener el destino correcto. Si voy a evitar goto, entonces la ganancia debería estar en no tener que rastrear manualmente cosas como esas.
cHao
93

Ya tuvimos esta discusión y mantengo mi punto de vista .

Además, estoy harto de personas que describen estructuras de lenguaje de nivel superior como " gotodisfrazadas" porque claramente no han entendido nada . Por ejemplo:

Incluso la estructura avanzada de control de continuación en Scheme puede describirse como un goto sofisticado.

Eso es una completa tontería. Cada estructura de control puede implementarse en términos de, gotopero esta observación es completamente trivial e inútil. gotono se considera dañino debido a sus efectos positivos, sino a sus consecuencias negativas, que han sido eliminadas por la programación estructurada.

Del mismo modo, decir "GOTO es una herramienta, y como todas las herramientas, se puede usar y abusar" está completamente fuera de lugar. Ningún trabajador de la construcción moderna usaría una roca y afirmaría que "es una herramienta". Las rocas han sido reemplazadas por martillos. gotoha sido reemplazado por estructuras de control. Si el trabajador de la construcción quedara varado en la naturaleza sin un martillo, por supuesto que usaría una roca en su lugar. Si un programador tiene que usar un lenguaje de programación inferior que no tiene la función X, bueno, por supuesto, puede que tenga que usarlo goto. Pero si lo usa en otro lugar en lugar de la característica de lenguaje apropiada, claramente no ha entendido el idioma correctamente y lo usa incorrectamente. Es realmente tan simple como eso.

Konrad Rudolph
fuente
82
Por supuesto, el uso adecuado de una roca no es como un martillo. Uno de sus usos adecuados es una piedra de pulir, o para afilar otras herramientas. Incluso la roca humilde, cuando se usa correctamente, es una buena herramienta. Solo tienes que encontrar el uso adecuado. Lo mismo va para goto.
Kibbee el
10
Entonces, ¿cuál es el uso adecuado de Goto? Para cada caso imaginable hay otra herramienta más adecuada. E incluso su piedra de moler en realidad es reemplazada por herramientas de alta tecnología hoy en día, incluso si todavía están hechas de roca. Hay una gran diferencia entre una materia prima y una herramienta.
Konrad Rudolph el
8
@jalf: Goto sin duda lo hace existir en C #. Ver stackoverflow.com/questions/359436/…
Jon Skeet
51
Estoy consternado porque mucha gente aprueba esta publicación. Su publicación solo parecía efectiva porque nunca se molestó en cuestionar qué lógica estaba realmente realizando, por lo que no notó su falacia. Permítanme parafrasear toda su publicación: "Hay una herramienta superior para un goto en cada situación, por lo que nunca se deben usar gotos". Esta es una lógica bicondicional, y como tal, toda tu publicación esencialmente plantea la pregunta "¿Cómo sabes que hay una herramienta superior para un goto en cada situación?"
Codificación con estilo
10
@Coding: No, te perdiste por completo la esencia de la publicación. Fue una respuesta más que un argumento aislado y completo. Simplemente señalé la falacia en el argumento principal "para" goto. Tienes razón en la medida en que no ofrezco un argumento en contra gotoper se, no tenía la intención de hacerlo, por lo que no hay dudas.
Konrad Rudolph
91

Goto es extremadamente bajo en mi lista de cosas para incluir en un programa solo por el simple hecho de hacerlo. Eso no significa que sea inaceptable.

Goto puede ser bueno para las máquinas de estado. Una declaración de cambio en un bucle es (en orden de importancia típica): (a) en realidad no representativa del flujo de control, (b) feo, (c) potencialmente ineficiente dependiendo del lenguaje y el compilador. Entonces terminas escribiendo una función por estado y haciendo cosas como "return NEXT_STATE;" que incluso parecen goto.

Por supuesto, es difícil codificar máquinas de estado de una manera que las haga fáciles de entender. Sin embargo, ninguna de esas dificultades tiene que ver con el uso de goto, y ninguna de ellas puede reducirse mediante el uso de estructuras de control alternativas. A menos que su lenguaje tenga una construcción de 'máquina de estado'. La mía no.

En esas raras ocasiones en que su algoritmo es realmente más comprensible en términos de una ruta a través de una secuencia de nodos (estados) conectados por un conjunto limitado de transiciones permisibles (gotos), en lugar de por un flujo de control más específico (bucles, condicionales, etc.) ), entonces eso debería ser explícito en el código. Y deberías dibujar un bonito diagrama.

setjmp / longjmp puede ser bueno para implementar excepciones o comportamientos similares a las excepciones. Si bien no se elogian universalmente, las excepciones generalmente se consideran una estructura de control "válida".

setjmp / longjmp son 'más peligrosos' que goto en el sentido de que son más difíciles de usar correctamente, no importa mucho.

Nunca ha habido, ni habrá, un lenguaje en el que sea menos difícil escribir código incorrecto. - Donald Knuth.

Sacar Goto de C no facilitaría escribir un buen código en C. De hecho, preferiría perder el punto de que se supone que C es capaz de actuar como un lenguaje ensamblador glorificado.

Luego serán "punteros considerados dañinos", luego "tipear pato considerado dañino". Entonces, ¿quién quedará para defenderte cuando vengan a quitar tu construcción de programación insegura? Eh?

Steve Jessop
fuente
14
Personalmente, este es el comentario al que le habría dado el cheque. Una cosa que me gustaría señalar a los lectores es que el término esotérico "máquinas de estado" incluye cosas cotidianas como los analizadores léxicos. Echa un vistazo a la salida de lex somtime. Lleno de gotos.
TED
2
Puede usar una instrucción switch dentro de un bucle (o controlador de eventos) para hacer máquinas de estado perfectamente. He hecho muchas máquinas de estado sin tener que usar un jmp o goto.
Scott Whitlock
11
+1 Las flechas en las máquinas de estado se asignan a 'ir a' más de cerca que a cualquier otra estructura de control. Claro, puede usar un interruptor dentro de un bucle, al igual que puede usar un montón de gotos en lugar de un tiempo para otros problemas, pero generalmente es una idea; cuál es el punto central de esta discusión.
Edmund
2
¿Puedo citarte en ese último párrafo?
Chris Lutz el
2
Y, aquí en 2013, ya hemos alcanzado (y superado) la fase de "punteros considerados dañinos".
Isiah Meadows
68

En Linux: Usando goto In Kernel Code en Kernel Trap, hay una discusión con Linus Torvalds y un "chico nuevo" sobre el uso de GOTOs en el código de Linux. Hay algunos puntos muy buenos allí y Linus se vistió con esa arrogancia habitual :)

Algunos pasajes:

Linus: "No, la gente de CS te lavó el cerebro y pensó que Niklaus Wirth realmente sabía de lo que estaba hablando. No lo sabía. No tiene ni una pista".

-

Linus: "Creo que los goto están bien, y a menudo son más legibles que grandes cantidades de sangría".

-

Linus: "Por supuesto, en lenguajes estúpidos como Pascal, donde las etiquetas no pueden ser descriptivas, los goto pueden ser malos".

Marcio Aguiar
fuente
9
Ese es un buen punto, ¿cómo? Están discutiendo su uso en un lenguaje que no tiene nada más. Cuando está programando en ensamblaje, todas las ramas y saltos son de goto. Y C es, y fue, un "lenguaje ensamblador portátil". Además, los pasajes que cita no dicen nada acerca de por qué piensa que goto es bueno.
jalf
55
Guau. Eso es decepcionante de leer. Uno pensaría que un gran tipo de sistema operativo como Linus Torvalds sabría mejor que decir eso. Pascal (pascal de la vieja escuela, no la versión moderna de Object) fue en lo que se escribió Mac OS Classic durante el período de 68k, y fue el sistema operativo más avanzado de su tiempo.
Mason Wheeler
66
@mason Classic Mac OS tenía algunas bibliotecas de Pascal (eventualmente, el tiempo de ejecución de Pascal ocupaba demasiada memoria en los primeros Macs), pero la mayoría del código central estaba escrito en Assembler, particularmente los gráficos y las rutinas de UI.
Jim Dovey
30
Linus solo argumenta (explícitamente, como Rik van Riel en esa discusión) a favor de manejar el estado de salida, y lo hace sobre la base de la complejidad que traerían las construcciones alternativas de C si se usaran en su lugar.
Charles Stewart el
21
En mi humilde opinión, Linus tiene razón en este tema. Su punto es que el código del kernel, escrito en C, que necesita implementar algo similar al manejo de excepciones, se escribe de manera más clara y simple usando un goto. El idioma goto cleanup_and_exites uno de los pocos "buenos" usos de la izquierda Goto ahora que tenemos for, whiley ifpara administrar nuestro flujo de control. Ver también: programmers.stackexchange.com/a/154980
steveha
50

En C, gotosolo funciona dentro del alcance de la función actual, que tiende a localizar posibles errores. setjmpy longjmpson mucho más peligrosos, no son locales, complicados y dependen de la implementación. Sin embargo, en la práctica, son demasiado oscuros y poco comunes como para causar muchos problemas.

Creo que el peligro de gotoen C es muy exagerado. Recuerde que los gotoargumentos originales tuvieron lugar en la época de idiomas como el BASIC pasado de moda, donde los principiantes escribían código de espagueti como este:

3420 IF A > 2 THEN GOTO 1430

Aquí Linus describe un uso apropiado de goto: http://www.kernel.org/doc/Documentation/CodingStyle (capítulo 7).

smh
fuente
77
Cuando BASIC estuvo disponible por primera vez, no había ninguna alternativa a GOTO nnnn y GOSUB mmmm como formas de saltar. Las construcciones estructuradas se agregaron más tarde.
Jonathan Leffler
77
Te estás perdiendo el punto ... incluso entonces no tenías que escribir espagueti ... tus GOTO siempre se podían usar de manera disciplinada
JoelFan
También vale la pena señalar que el comportamiento de setjmp/ longjmpsolo se especificó cuando se usaron como un medio para saltar a un lugar dentro de un alcance desde otros lugares dentro de ese mismo alcance. Una vez que el control abandona el ámbito donde setjmpse ejecuta, cualquier intento de uso longjmpen la estructura creada por setjmpdará como resultado un comportamiento indefinido.
supercat
9
Algunas versiones de BASIC te dejarían hacerlo GOTO A * 40 + B * 200 + 30. No es difícil ver cómo esto fue muy útil y muy peligroso.
Jon Hanna
1
@Hjulle calcularía la expresión y luego iría a la línea de código con ese número (los números de línea explícitos eran un requisito de la mayoría de los dialectos anteriores). ZX Spectrum Basic fue uno que aceptaría eso
Jon Hanna,
48

Hoy en día, es difícil ver el gran problema acerca de la GOTOdeclaración porque la gente de "programación estructurada" en su mayoría ganó el debate y los lenguajes de hoy tienen estructuras de flujo de control suficientes para evitar GOTO.

Cuente el número de gotos en un programa moderno de C. Ahora agregue el número de break, continuey returndeclaraciones. Por otra parte, añadir el número de veces que utilice if, else, while, switcho case. Eso es aproximadamente cuántos GOTOs habría tenido su programa si estuviera escribiendo en FORTRAN o BASIC en 1968 cuando Dijkstra escribió su carta.

Los lenguajes de programación en ese momento carecían de flujo de control. Por ejemplo, en el Dartmouth BASIC original:

  • IFdeclaraciones no tenían ELSE. Si querías uno, tenías que escribir:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Incluso si su IFestado de cuenta no necesitaba un ELSE, todavía estaba limitado a una sola línea, que generalmente consistía en un GOTO.

  • No hubo DO...LOOPdeclaración. Para los que no son FORbucles, tenía que finalizar el bucle de forma explícita GOTOo IF...GOTOvolver al principio.

  • Allí no estaba SELECT CASE. Tuviste que usar ON...GOTO.

Por lo tanto, que terminó con una gran cantidad de GOTOs en su programa. Y no podría depender de la restricción de GOTOs dentro de una sola subrutina (porque GOSUB...RETURNera un concepto tan débil de subrutinas), por lo que estos GOTOs podrían ir a cualquier parte . Obviamente, esto hizo que el control fluyera difícil de seguir.

De aquí es de donde GOTOvino el anti- movimiento.

dan04
fuente
Otra cosa a tener en cuenta es que la forma preferida de escribir código si uno tuviera algún código en un bucle que tendría que ejecutarse raramente sería 420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// 3000 [code for rare condition]// 3230 goto 430. Escribir código de esa manera evita cualquier rama o salto tomado en el caso común de la línea principal, pero hace que las cosas sean difíciles de seguir. La evitación de ramificaciones en el código de ensamblaje puede ser peor, si algunas ramificaciones están limitadas a, por ejemplo, +/- 128 bytes y, a veces, no tienen pares complementarios (por ejemplo, "cjne" existe pero no "cje").
supercat
Una vez escribí un código para el 8x51 que tenía una interrupción que se ejecutaba una vez cada 128 ciclos. Cada ciclo adicional gastado en el caso común de ese ISR reduciría la velocidad de ejecución del código de la línea principal en más de un 1% (creo que 90 de 128 generalmente estaban disponibles para la línea principal), y cualquier instrucción de ramificación tomaría dos ciclos ( tanto en casos de ramificación como de caída). El código tenía dos comparaciones, una que generalmente informaba igual; el otro, no igual. En ambos casos, el código de casos raros estaba a más de 128 bytes de distancia. Entonces ...
supercat
cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // first_comp_springboard: ajmp first_comp_target// second_comp_fallthrough: .... No es un patrón de codificación muy agradable, pero cuando los ciclos individuales cuentan, uno hace esas cosas. Por supuesto, en la década de 1960, tales niveles de optimización eran más importantes que hoy, especialmente porque las CPU modernas a menudo requieren optimizaciones extrañas, y los sistemas de compilación justo a tiempo pueden aplicar código de optimización para CPU que no existían cuando El código en cuestión fue escrito.
supercat
34

Ir a puede proporcionar una especie de sustituto para el manejo de excepciones "reales" en ciertos casos. Considerar:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

Obviamente, este código se simplificó para ocupar menos espacio, así que no te obsesiones con los detalles. Pero considere una alternativa que he visto demasiadas veces en el código de producción por los codificadores yendo a longitudes absurdas para evitar usar goto:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

Ahora funcionalmente este código hace exactamente lo mismo. De hecho, el código generado por el compilador es casi idéntico. Sin embargo, en el afán del programador por apaciguar a Nogoto (el temido dios de la reprimenda académica), este programador ha roto por completo el idioma subyacente que whilerepresenta el bucle e hizo un número real sobre la legibilidad del código. Esto no es mejor

Entonces, la moraleja de la historia es que si te encuentras recurriendo a algo realmente estúpido para evitar usar goto, entonces no lo hagas.

tylerl
fuente
1
Aunque tiendo a estar de acuerdo con lo que está diciendo aquí, el hecho de que las breakdeclaraciones estén dentro de una estructura de control deja en claro exactamente lo que hacen. Con el gotoejemplo, la persona que lee el código tiene que revisar el código para encontrar la etiqueta, que en teoría podría estar antes de la estructura de control. No tengo suficiente experiencia con el estilo antiguo C para juzgar que uno es definitivamente mejor que el otro, pero hay compensaciones en ambos sentidos.
Vivian River
55
@DanielAllenLangdon: El hecho de que los breaks estén dentro de un bucle deja en claro exactamente que salen del bucle . Eso no es "exactamente lo que hacen", ya que en realidad, ¡no hay un bucle en absoluto! Nada de eso tiene posibilidades de repetirse, pero eso no está claro hasta el final. El hecho de que tenga un "bucle" que nunca se ejecuta más de una vez, significa que las estructuras de control están siendo abusadas. Con el gotoejemplo, el programador puede decir goto error_handler;. Es más explícito, y aún menos difícil de seguir. (Ctrl + F, "error_handler:" para encontrar el objetivo. Intenta hacerlo con "}".)
cHao
1
Una vez vi un código similar a su segundo ejemplo en un sistema de control de tráfico aéreo , porque 'goto no está en nuestro léxico'.
QED
30

Donald E. Knuth respondió a esta pregunta en el libro "Literate Programming", 1992 CSLI. En P. 17 hay un ensayo " Programación estructurada con declaraciones Goto " (PDF). Creo que el artículo podría haber sido publicado en otros libros también.

El artículo describe la sugerencia de Dijkstra y describe las circunstancias en que esto es válido. Pero también da una serie de contraejemplos (problemas y algoritmos) que no pueden reproducirse fácilmente utilizando solo bucles estructurados.

El artículo contiene una descripción completa del problema, la historia, los ejemplos y los contraejemplos.

Bruno Ranschaert
fuente
25

Goto lo consideró útil.

Comencé a programar en 1975. Para los programadores de la década de 1970, las palabras "goto considerado dañino" decían más o menos que valía la pena probar nuevos lenguajes de programación con estructuras de control modernas. Probamos los nuevos idiomas. Nos convertimos rápidamente. Nunca volvimos.

Nunca volvimos, pero si eres más joven, nunca has estado allí en primer lugar.

Ahora, un fondo en lenguajes de programación antiguos puede no ser muy útil, excepto como un indicador de la edad del programador. No obstante, los programadores más jóvenes carecen de estos antecedentes, por lo que ya no entienden el mensaje que el lema "goto considerado dañino" transmitió a su público objetivo en el momento en que se introdujo.

Los lemas que uno no entiende no son muy esclarecedores. Probablemente sea mejor olvidar tales lemas. Tales consignas no ayudan.

Sin embargo, este eslogan particular, "Goto considerado dañino", ha adquirido una vida propia de los no muertos.

¿No se puede abusar de goto? Respuesta: claro, pero ¿y qué? Prácticamente todos los elementos de programación pueden ser abusados. Los humildes, boolpor ejemplo, son abusados ​​con más frecuencia de lo que algunos de nosotros quisiéramos creer.

Por el contrario, no recuerdo haber conocido una sola instancia real de abuso de goto desde 1990.

El mayor problema con goto probablemente no sea técnico, sino social. Los programadores que no saben mucho a veces parecen sentir que despreciar el goto los hace sonar inteligentes. Puede que tenga que satisfacer a tales programadores de vez en cuando. Así es la vida.

Lo peor de goto hoy es que no se usa lo suficiente.

thb
fuente
24

Atraído por Jay Ballou agregando una respuesta, agregaré £ 0.02. Si Bruno Ranschaert aún no lo hubiera hecho, habría mencionado el artículo de Knuth "Programación estructurada con declaraciones GOTO".

Una cosa que no he visto discutido es el tipo de código que, aunque no es exactamente común, se enseñó en los libros de texto de Fortran. Cosas como el rango extendido de un bucle DO y subrutinas de código abierto (recuerde, esto sería Fortran II, Fortran IV o Fortran 66, no Fortran 77 o 90). Existe al menos una posibilidad de que los detalles sintácticos sean inexactos, pero los conceptos deben ser lo suficientemente precisos. Los fragmentos en cada caso están dentro de una sola función.

Tenga en cuenta que el excelente pero anticuado (y agotado) libro ' The Elements of Programming Style, 2nd Edn ' de Kernighan & Plauger incluye algunos ejemplos reales de abuso de GOTO de los libros de texto de programación de su época (finales de los 70). Sin embargo, el material a continuación no es de ese libro.

Rango extendido para un ciclo DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Una razón para tales tonterías era la buena tarjeta perforada pasada de moda. Puede notar que las etiquetas (¡muy fuera de secuencia porque ese era el estilo canónico!) Están en la columna 1 (en realidad, tenían que estar en las columnas 1-5) y el código está en las columnas 7-72 ​​(la columna 6 fue la continuación columna de marcador). Las columnas 73-80 recibirían un número de secuencia, y había máquinas que clasificaban los mazos de cartas perforadas en orden de número de secuencia. Si tuviera su programa en tarjetas secuenciadas y necesitara agregar algunas tarjetas (líneas) en el medio de un bucle, tendría que volver a ejecutar todo después de esas líneas adicionales. Sin embargo, si reemplaza una tarjeta con el material GOTO, puede evitar volver a secuenciar todas las tarjetas: simplemente coloca las nuevas tarjetas al final de la rutina con nuevos números de secuencia. Considere que es el primer intento de 'computación verde'

Ah, también podrías notar que estoy engañando y no gritando: Fortran IV se escribió en mayúsculas normalmente.

Subrutina de código abierto

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

El GOTO entre las etiquetas 76 y 54 es una versión de goto computarizado. Si la variable i tiene el valor 1, pase a la primera etiqueta de la lista (123); si tiene el valor 2, pase al segundo, y así sucesivamente. El fragmento del 76 al goto calculado es la subrutina de código abierto. Era un fragmento de código ejecutado como una subrutina, pero escrito en el cuerpo de una función. (Fortran también tenía funciones de declaración, que eran funciones integradas que se ajustaban en una sola línea).

Había construcciones peores que el goto calculado: se podían asignar etiquetas a las variables y luego usar un goto asignado. El goto asignado en Google me dice que fue eliminado de Fortran 95. Marque uno para la revolución de programación estructurada que podría decirse que comenzó en público con la carta o artículo "GOTO Considered Damful" de Dijkstra.

Sin algún conocimiento del tipo de cosas que se hicieron en Fortran (y en otros idiomas, la mayoría de las cuales han quedado en el camino), es difícil para nosotros los recién llegados entender el alcance del problema con el que Dijkstra estaba lidiando. Diablos, no comencé a programar hasta diez años después de que se publicó esa carta (pero tuve la desgracia de programar en Fortran IV por un tiempo).

Jonathan Leffler
fuente
2
Si desea ver un ejemplo de algún código usando goto'en la naturaleza', la pregunta Se busca: Algoritmo de ordenación Bose-Hibbard de trabajo muestra algún código (Algol 60) publicado en 1963. El diseño original no se compara con los estándares de codificación modernos. El código aclarado (sangrado) sigue siendo bastante inescrutable. Las gotodeclaraciones allí hacen que sea (muy) difícil entender lo que está haciendo el algoritmo.
Jonathan Leffler
1
Siendo demasiado joven para haber experimentado algo parecido a las tarjetas perforadas, fue esclarecedor leer sobre el problema de volver a perforar. +1
Qix - MONICA FUE MALTRATADA el
20

No hay cosas que GOTO considere dañinas .

GOTO es una herramienta, y como todas las herramientas, se puede usar y abusar de ella .

Sin embargo, hay muchas herramientas en el mundo de la programación que tienden a ser abusadas más que a ser utilizadas , y GOTO es una de ellas. la declaración WITH de Delphi es otra.

Personalmente, no uso ninguno de los códigos típicos , pero he tenido el uso extraño de GOTO y WITH que estaban garantizados, y una solución alternativa habría contenido más código.

La mejor solución sería que el compilador le advirtiera que la palabra clave estaba contaminada , y tendría que rellenar un par de directivas pragma alrededor de la declaración para deshacerse de las advertencias.

Es como decirles a tus hijos que no corran con tijeras . Las tijeras no son malas, pero su uso no es la mejor manera de mantener su salud.

Lasse V. Karlsen
fuente
El problema con GOTO es que rompe importantes invariantes que damos por sentado con los lenguajes de programación modernos. Para tomar un ejemplo, si llamo a una función, asumimos que cuando la función se complete, devolverá el control a la persona que llama, ya sea normalmente o mediante un desbobinado de pila excepcional. Si esa función usa mal GOTO, entonces, por supuesto, esto ya no es cierto. Esto hace que sea muy difícil razonar sobre nuestro código. No es suficiente evitar cuidadosamente el mal uso de GOTO. El problema aún ocurre si GOTO es mal utilizado por nuestras dependencias ...
Jonathan Hartley
... Entonces, para saber si podemos razonar sobre nuestras llamadas a funciones, necesitamos examinar cada línea del código fuente de cada una de nuestras dependencias transitivas, verificando que no hagan un mal uso de GOTO. Entonces, solo la existencia de GOTO en el lenguaje ha roto nuestra capacidad de razonar con confianza sobre nuestro propio código, incluso si lo usamos perfectamente (o nunca lo usamos). Por esta razón, GOTO no es solo una herramienta para ser utilizada con cuidado. Está roto sistémicamente y su existencia en un idioma se considera unilateralmente perjudicial.
Jonathan Hartley el
Incluso si la gotopalabra clave se borró de C #, el concepto de "saltar aquí" todavía existe en IL. Se puede construir fácilmente un bucle infinito sin la palabra clave goto. Si esta falta de garantía de que el código llamado volverá, en su opinión, significa "incapaz de razonar sobre el código", entonces diría que nunca tuvimos esa capacidad.
Lasse V. Karlsen
Ah, has encontrado la fuente de nuestra falta de comunicación. El gotoin C # no es un verdadero "goto" en el sentido original de la palabra. Es una versión mucho más débil que solo permite saltar dentro de una función. El sentido en el que estoy usando "goto" permite saltar a cualquier parte del proceso. Entonces, aunque C # tiene una palabra clave "goto", podría decirse que nunca ha tenido un goto real. Sí, hay un goto real disponible a nivel IL, de la misma manera que cuando un lenguaje se compila en ensamblador. Pero el programador está protegido de eso, no puede usarlo en circunstancias normales, por lo que no cuenta.
Jonathan Hartley
Por cierto, la opinión que describo aquí no es originalmente mía, pero creo que es el núcleo del artículo original de Dijkstra de 1967, titulado por su editor "Goto considerado dañino", que se ha convertido en un meme tan citado durante 50 años. años precisamente porque era tan revolucionario, perspicaz y universalmente aceptado.
Jonathan Hartley
17

Nunca lo fue, siempre que pudieras pensar por ti mismo.

stesch
fuente
17

Desde que comencé a hacer algunas cosas en el kernel de Linux, los gotos no me molestan tanto como antes. Al principio me horroricé al ver que ellos (chicos del kernel) añadían gotos a mi código. Desde entonces me he acostumbrado al uso de gotos, en algunos contextos limitados, y ahora ocasionalmente los usaré yo mismo. Por lo general, es un goto que salta al final de una función para realizar algún tipo de limpieza y rescate, en lugar de duplicar esa misma limpieza y rescate en varios lugares de la función. Y, por lo general, no es algo lo suficientemente grande como para pasar a otra función; por ejemplo, liberar algunas variables localmente (k) mal asignadas es un caso típico.

He escrito código que usó setjmp / longjmp solo una vez. Estaba en un programa secuenciador de batería MIDI. La reproducción se realizó en un proceso separado de toda interacción del usuario, y el proceso de reproducción utilizó memoria compartida con el proceso de la interfaz de usuario para obtener la información limitada que necesitaba para realizar la reproducción. Cuando el usuario quería detener la reproducción, el proceso de reproducción simplemente hacía un "salto al principio" para volver a comenzar, en lugar de un complicado desenrollamiento de donde se estaba ejecutando cuando el usuario quería que se detuviera. Funcionó muy bien, fue simple y nunca tuve ningún problema o error relacionado con eso en esa instancia.

setjmp / longjmp tienen su lugar, pero ese lugar es uno que probablemente no visitará, pero de vez en cuando.

Editar: acabo de mirar el código. En realidad fue siglongjmp () lo que usé, no longjmp (no es que sea un gran problema, pero había olvidado que siglongjmp incluso existía).

smcameron
fuente
15

Si está escribiendo una VM en C, resulta que el uso de gotos computados (gcc) es el siguiente:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

funciona mucho más rápido que el interruptor convencional dentro de un bucle.

Vermont.
fuente
El único problema es que no es estándar, ¿verdad?
Calmarius
&&op_incciertamente no compila, porque (la izquierda) &espera un valor de l, pero (la derecha) &produce un valor de r.
fredoverflow
77
@FredO: Es un operador especial de GCC. Sin embargo, rechazaría este código en todas las circunstancias, excepto en las más difíciles, porque seguramente no puedo entender lo que está sucediendo.
Cachorro
Si bien este es un ejemplo (optimización máxima para la velocidad) que justifica el uso de goto's y el código críptico, aún debe comentarse mucho para explicar la necesidad de optimización, aproximadamente cómo funciona y la mejor línea por línea comentarios que se pueden hacer. Entonces, todavía falla, pero porque deja a los programadores de mantenimiento en la oscuridad. Pero me gusta el ejemplo de VM. Gracias.
MicroserviciosOnDDD
14

Porque gotopuede usarse para confundir metaprogramación

Gotoes una expresión de control de alto nivel y de bajo nivel , y como resultado simplemente no tiene un patrón de diseño apropiado para la mayoría de los problemas.

Es de bajo nivel en el sentido de que un Goto es una operación primitiva que implementa algo superior al igual que whileo foreacho algo así.

Es de alto nivel en el sentido de que, cuando se usa de ciertas maneras, toma un código que se ejecuta en una secuencia clara, de manera ininterrumpida, excepto los bucles estructurados, y lo transforma en piezas de lógica que son, con suficiente gotos, un agarre -Bolsa de lógica reensamblada dinámicamente.

Por lo tanto, hay una prosaica y un mal lado a goto.

El lado prosaico es que un goto que apunta hacia arriba puede implementar un bucle perfectamente razonable y un goto que apunta hacia abajo puede hacer un breako perfectamente razonable return. Por supuesto, un real while, breako returnsería mucho más legible, ya que el pobre humano no tendría que simular el efecto del gotopara obtener el panorama general. Entonces, una mala idea en general.

El lado malvado implica una rutina que no usa goto durante un tiempo, se rompe o regresa, sino que se usa para lo que se llama lógica de espagueti . En este caso, el desarrollador goto-happy está construyendo piezas de código a partir de un laberinto de goto's, y la única forma de entenderlo es simulando mentalmente en su conjunto, una tarea terriblemente agotadora cuando hay muchos goto's. Quiero decir, imagina el problema de evaluar el código donde el elseno es precisamente un inverso del if, donde los ifs anidados podrían permitir algunas cosas que fueron rechazadas por el exterior if, etc., etc.

Finalmente, para cubrir realmente el tema, debemos tener en cuenta que, esencialmente, todos los primeros idiomas, excepto Algol, inicialmente solo hicieron declaraciones únicas sujetas a sus versiones de if-then-else. Entonces, la única forma de hacer un bloque condicional era gotorodeándolo usando un condicional inverso. Loco, lo sé, pero he leído algunas especificaciones antiguas. Recuerde que las primeras computadoras fueron programadas en código de máquina binario, así que supongo que cualquier tipo de HLL era un salvavidas; Supongo que no fueron demasiado quisquillosos sobre exactamente qué características de HLL obtuvieron.

Habiendo dicho todo lo que solía incluir uno gotoen cada programa, escribí "solo para molestar a los puristas" .

DigitalRoss
fuente
44
¡+1 por molestar a los puristas! :-)
Lumi
10

Negar el uso de la declaración GOTO a los programadores es como decirle a un carpintero que no use un martillo ya que podría dañar la pared mientras está clavando un clavo. Un programador real sabe cómo y cuándo usar un GOTO. He seguido algunos de estos llamados 'Programas Estructurados'. He visto ese código horrible solo para evitar usar un GOTO, para poder dispararle al programador. Ok, en defensa del otro lado, también he visto un código de espagueti real y, una vez más, esos programadores también deberían recibir un disparo.

Aquí hay solo un pequeño ejemplo de código que he encontrado.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------O----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10
CurtTampa
fuente
3
HACER CRT '¿Es esto correcto? (S / N): ': ENTRADA HORNO HASTA YORN =' Y 'O YORN =' N '; etc.
joel.neely
55
De hecho, pero lo más importante, un programador real sabe cuándo no usar un goto- y sabe por qué . Evitar una construcción de lenguaje tabú porque $ programacion_guru lo dijo, esa es la definición misma de la programación de culto de carga.
Piskvor salió del edificio el
1
Es una buena analogía. Para clavar un clavo sin dañar el sustrato, no elimine el martillo. Por el contrario, utiliza una herramienta simple conocida como punzón. Este es un pasador metálico con un extremo cónico que tiene una muesca hueca en su punta, para acoplarse de manera segura con la cabeza del clavo (estas herramientas vienen en diferentes tamaños para diferentes clavos). El otro extremo romo del punzón se golpea con un martillo.
Kaz
9

"En este enlace http://kerneltrap.org/node/553/2131 "

Irónicamente, la eliminación del goto introdujo un error: se omitió la llamada spinlock.

Jay Ballou
fuente
+1 por 'eliminar el goto introdujo un error'
QED
7

El documento original debe considerarse como "GOTO incondicional considerado perjudicial". En particular, abogaba por una forma de programación basada en construcciones condicionales ( if) e iterativas ( while), en lugar de las pruebas y saltos comunes al código inicial. gotosigue siendo útil en algunos idiomas o circunstancias, donde no existe una estructura de control adecuada.

John Millikin
fuente
6

Casi el único lugar en el que estoy de acuerdo con que Goto podría usarse es cuando necesita lidiar con errores, y cada punto particular en que se produce un error requiere un manejo especial.

Por ejemplo, si está tomando recursos y usando semáforos o mutexes, debe tomarlos en orden y siempre debe liberarlos de la manera opuesta.

Algunos códigos requieren un patrón muy extraño de capturar estos recursos, y no puede simplemente escribir una estructura de control fácil de mantener y comprender para manejar correctamente tanto la captura como la liberación de estos recursos para evitar un punto muerto.

Siempre es posible hacerlo bien sin goto, pero en este caso y en algunos otros, Goto es en realidad la mejor solución principalmente para la legibilidad y la facilidad de mantenimiento.

-Adán

Adam Davis
fuente
5

Un uso moderno de GOTO es el compilador de C # para crear máquinas de estado para enumeradores definidos por el rendimiento.

GOTO es algo que deberían usar los compiladores y no los programadores.

Brian Leahy
fuente
55
¿Quién exactamente crees que crea los compiladores?
tloach
10
Compiladores, por supuesto!
Seiti
3
Creo que quiere decir "GOTO es algo que solo debe ser usado por el código emitido por un compilador".
Simon Buchan
Este es un caso en contra goto. Donde podríamos usar gotoen una máquina de estado codificada a mano para implementar un enumerador, podemos usar yielden su lugar.
Jon Hanna
encienda muchas cadenas (para evitar la compilación a if-else) con compilaciones de mayúsculas y minúsculas para cambiar con la instrucción goto.
M.kazem Akhgary
5

Hasta que C y C ++ (entre otros culpables) tengan roturas roturas y continúen, goto continuará teniendo un papel.

DrPizza
fuente
Entonces rotulado o continuar sería diferente de ir a ¿cómo?
Matthew Whited
3
No permiten saltos totalmente arbitrarios en el flujo de control.
DrPizza
5

Si GOTO fuera malvado, los compiladores serían malvados porque generan JMP. Si saltar a un bloque de código, especialmente después de un puntero, fuera inherentemente malo, la instrucción RETurn sería malvada. Más bien, el mal está en el potencial de abuso.

A veces he tenido que escribir aplicaciones que tenían que hacer un seguimiento de una serie de objetos en los que cada objeto tenía que seguir una secuencia intrincada de estados en respuesta a los eventos, pero todo era definitivamente de un solo hilo. Una secuencia típica de estados, si se representa en pseudocódigo sería:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Estoy seguro de que esto no es nuevo, pero la forma en que lo manejé en C (++) fue definir algunas macros:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Luego (suponiendo que el estado es inicialmente 0) la máquina de estados estructurados anterior se convierte en el código estructurado:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

Con una variación de esto, puede haber CALL y RETURN, por lo que algunas máquinas de estado pueden actuar como subrutinas de otras máquinas de estado.

¿Es inusual? Si. ¿Toma algo de aprendizaje por parte del mantenedor? Si. ¿Vale la pena ese aprendizaje? Creo que sí. ¿Se podría hacer sin GOTOs que saltan en bloques? No

Mike Dunlavey
fuente
1
Evitar una característica del lenguaje por miedo es una señal de daño cerebral. Esto también es más nift que el infierno.
Vector Gorgoth
4

Lo evito ya que un compañero de trabajo / gerente, sin duda, cuestionará su uso, ya sea en una revisión de código o cuando se encuentren con él. Si bien creo que tiene usos (el caso de manejo de errores, por ejemplo), se encontrará con algún otro desarrollador que tendrá algún tipo de problema.

Que no vale la pena.

Aardvark
fuente
Lo bueno de Probar ... Capturar bloques en C # es que se encargan de limpiar la pila y otros recursos asignados (llamados desenrollar la pila) a medida que la excepción aparece en un controlador de excepciones. Esto hace que Try ... Catch sea mucho mejor que Goto, así que si tienes Try ... Catch, úsalo.
Scott Whitlock
Tengo una mejor solución: envuelva el fragmento de código del que desea extraer en un 'do {...} while (0);' lazo. De esa manera, puede saltar de la misma manera que un goto sin la sobrecarga del ciclo try / catch (no sé sobre C #, pero en C ++ try es de bajo costo y catch es de alto costo, por lo que parece excesivo para lanzar una excepción donde un simple salto sería suficiente).
Jim Dovey
10
Jim, el problema con eso es que no es más que una forma estúpidamente indirecta de obtener un goto.
Codificación con estilo
4

De hecho, me vi obligado a usar un goto, porque literalmente no podía pensar en una mejor (más rápida) forma de escribir este código:

Tenía un objeto complejo y necesitaba hacer alguna operación sobre él. Si el objeto estaba en un estado, entonces podría hacer una versión rápida de la operación, de lo contrario tenía que hacer una versión lenta de la operación. La cuestión era que, en algunos casos, en medio de la operación lenta, era posible darse cuenta de que esto podría haberse hecho con la operación rápida.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

Esto estaba en una pieza de código de interfaz de usuario en tiempo real, por lo que honestamente creo que un GOTO estaba justificado aquí.

Hugo

Rocketmagnet
fuente
La forma no GOTO sería utilizar una función fast_calculations, que incurre en una sobrecarga. Probablemente no se nota en la mayoría de las circunstancias, pero como dijiste, esto era crítico para la velocidad.
Kyle Cronin
1
Bueno, eso no es sorprendente. Todo código que tenga pautas de rendimiento absolutamente insanas siempre romperá prácticamente todas las mejores prácticas. Las mejores prácticas son para alcanzar y mantener, no para exprimir 10 milisegundos más o ahorrar 5 bytes más ram.
Jonathon
1
@JonathonWisnoski, los usos legítimos de goto también eliminan cantidades locas de código spagetti entrometiéndose en los nidos de variables de una rata para realizar un seguimiento de hacia dónde vamos.
vonbrand
4

Casi todas las situaciones en las que se puede usar un goto, puedes hacer lo mismo con otras construcciones. Goto es usado por el compilador de todos modos.

Yo personalmente nunca lo uso explícitamente, nunca lo necesito.

Adam Houldsworth
fuente
4

Una cosa que no he visto en ninguna de las respuestas aquí es que una solución 'goto' suele ser más eficiente que una de las soluciones de programación estructurada mencionadas a menudo.

Considere el caso de muchos bucles anidados, donde usar 'goto' en lugar de un montón de if(breakVariable)secciones es obviamente más eficiente. La solución "Poner tus loops en una función y usar return" a menudo es totalmente irracional. En el caso probable de que los bucles estén usando variables locales, ahora tiene que pasarlos todos a través de los parámetros de la función, potencialmente manejando un montón de dolores de cabeza adicionales que surgen de eso.

Ahora considere el caso de limpieza, que he usado con bastante frecuencia, y es tan común que presumiblemente fue responsable de la estructura try {} catch {} que no está disponible en muchos idiomas. El número de comprobaciones y variables adicionales que se requieren para lograr lo mismo son mucho peores que las una o dos instrucciones para dar el salto, y nuevamente, la solución de función adicional no es una solución en absoluto. No puedes decirme que es más manejable o más legible.

Ahora el espacio de código, el uso de la pila y el tiempo de ejecución pueden no ser lo suficientemente importantes en muchas situaciones para muchos programadores, pero cuando se encuentra en un entorno incrustado con solo 2 KB de espacio de código para trabajar, 50 bytes de instrucciones adicionales para evitar uno claramente definido 'goto' es simplemente ridículo, y esta no es una situación tan rara como creen muchos programadores de alto nivel.

La afirmación de que 'goto es dañino' fue muy útil para avanzar hacia una programación estructurada, incluso si siempre fue una generalización excesiva. En este punto, todos lo hemos escuchado lo suficiente como para tener cuidado de usarlo (como deberíamos). Cuando obviamente es la herramienta adecuada para el trabajo, no necesitamos tenerle miedo.

gkimsey
fuente
3

Puede usarlo para romper un bucle profundamente anidado, pero la mayoría de las veces se puede refactorizar su código para que sea más limpio sin bucles profundamente anidados.

Brian R. Bondy
fuente