¿Existen casos de uso legítimos para "goto" en un lenguaje que admita bucles y funciones?
Durante mucho tiempo he tenido la impresión de que, goto
si es posible, nunca debería usarse.
Sin embargo, mientras examinaba libavcodec (que está escrito en C) el otro día, me sorprendió notar sus múltiples usos.
¿Alguna vez es ventajoso usarlo goto
en un lenguaje que admita bucles y funciones? Si es así, ¿por qué? Proporcione un ejemplo concreto que justifique claramente el uso de un archivo goto
.
Todos los que están en contra goto
citan, directa o indirectamente, el artículo GoTo Considered Harmful de Edsger Dijkstra para fundamentar su posición. Lástima que el artículo de Dijkstra prácticamente no tiene nada que ver con la forma en que goto
se usan las declaraciones hoy en día y, por lo tanto, lo que dice el artículo tiene poca o ninguna aplicabilidad en la escena de la programación moderna. El goto
meme menos ahora raya en una religión, hasta en sus escrituras dictadas desde lo alto, sus sumos sacerdotes y el rechazo (o algo peor) de los percibidos como herejes.
Pongamos en contexto el artículo de Dijkstra para arrojar un poco de luz sobre el tema.
Cuando Dijkstra escribió su artículo, los lenguajes populares de la época eran procedimentales no estructurados como BASIC, FORTRAN (los dialectos anteriores) y varios lenguajes ensambladores. Era bastante común que las personas que usaban lenguajes de nivel superior saltaran sobre su base de código en hilos de ejecución retorcidos y retorcidos que dieron lugar al término "código espagueti". Puedes ver esto saltando al clásico juego Trek escrito por Mike Mayfield y tratando de descubrir cómo funcionan las cosas. Tómate unos momentos para revisar eso.
ESTE es "el uso desenfrenado de la frase ir a" contra el cual Dijkstra criticaba en su artículo de 1968. ESTE es el entorno en el que vivió y que lo llevó a escribir ese artículo. La capacidad de saltar a cualquier lugar que desee en su código en cualquier punto que desee era lo que criticaba y exigía que se detuviera. Comparar eso con los poderes anémicos de goto
C u otros lenguajes más modernos es simplemente ridículo.
Ya puedo escuchar los cánticos elevados de los cultistas mientras se enfrentan al hereje. "Pero", corearán, "puedes hacer que el código sea muy difícil de leer en goto
C". ¿Oh sí? goto
También puedes hacer que el código sea muy difícil de leer . Como éste:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
No está goto
a la vista, por lo que debe ser fácil de leer, ¿verdad? O qué tal este:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k,
l)char* *l;{g= atoi(* ++l); for(k=
0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<=
g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1;
while(d <=g){ ++O;for (f=0;f< O&&d<=g
;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O
&&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e
;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++
b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1))
<<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c]
;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5
|c]);} putchar( '\n');}} /*Mike Laman*/
Allí tampoco goto
. Por tanto, debe ser legible.
¿Cuál es mi punto con estos ejemplos? No son las características del lenguaje las que hacen que el código sea ilegible e imposible de mantener. No es la sintaxis la que lo hace. Son los malos programadores los que causan esto. Y los malos programadores, como puede ver en el elemento anterior, pueden hacer que cualquier característica del lenguaje sea ilegible e inutilizable. Como los for
bucles de ahí arriba. (Puedes verlos, ¿verdad?)
Ahora bien, para ser justos, es más fácil abusar de algunas construcciones del lenguaje que de otras. Sin embargo, si eres un programador de C, observaría mucho más de cerca aproximadamente el 50% de los usos de #define
mucho antes de emprender una cruzada contra goto
.
Entonces, para aquellos que se han molestado en leer hasta aquí, hay varios puntos clave a tener en cuenta.
- El artículo de Dijkstra sobre
goto
declaraciones fue escrito para un entorno de programación dondegoto
era mucho más dañino que en la mayoría de los lenguajes modernos que no son ensambladores. - Desechar automáticamente todos los usos de
goto
debido a esto es tan racional como decir "Traté de divertirme una vez pero no me gustó, así que ahora estoy en contra". - Hay usos legítimos de las
goto
declaraciones modernas (anémicas) en el código que no pueden ser reemplazadas adecuadamente por otras construcciones. - Por supuesto, existen usos ilegítimos de las mismas declaraciones.
- También existen usos ilegítimos de las declaraciones de control modernas, como la " " abominación en la que se rompe
godo
un bucle siempre falso en lugar de un . Estos suelen ser peores que el uso sensato de .do
break
goto
goto
Hay algunas razones para usar la declaración "goto" que yo sepa (algunos ya han hablado de esto):
Salir limpiamente de una función
A menudo, en una función, puede asignar recursos y necesitar salir en varios lugares. Los programadores pueden simplificar su código colocando el código de limpieza de recursos al final de la función, y todos los "puntos de salida" de la función irán a la etiqueta de limpieza. De esta manera, no es necesario escribir código de limpieza en cada "punto de salida" de la función.
Salir de bucles anidados
Si está en un bucle anidado y necesita salir de todos los bucles, un goto puede hacer que esto sea mucho más limpio y simple que las declaraciones break y las comprobaciones if.
Mejoras de rendimiento de bajo nivel
Esto solo es válido en código de rendimiento crítico, pero las declaraciones goto se ejecutan muy rápidamente y pueden darle un impulso al moverse por una función. Sin embargo, esto es un arma de doble filo, porque un compilador normalmente no puede optimizar el código que contiene gotos.
Tenga en cuenta que en todos estos ejemplos, los gotos están restringidos al alcance de una única función.
Obedecer ciegamente las mejores prácticas no es una buena práctica. La idea de evitar goto
declaraciones como forma principal de control de flujo es evitar producir código espagueti ilegible. Si se usan con moderación y en los lugares correctos, a veces pueden ser la forma más sencilla y clara de expresar una idea. Walter Bright, creador del compilador Zortech C++ y del lenguaje de programación D, los utiliza con frecuencia, pero con prudencia. Incluso con las goto
declaraciones, su código sigue siendo perfectamente legible.
En pocas palabras: evitar goto
por evitar goto
no tiene sentido. Lo que realmente quieres evitar es producir código ilegible. Si su goto
código cargado es legible, entonces no tiene nada de malo.
Bueno, hay una cosa que siempre es peor que goto's
; uso extraño de otros operadores de flujo de programa para evitar un goto:
Ejemplos:
// 1
try{
...
throw NoErrorException;
...
} catch (const NoErrorException& noe){
// This is the worst
}
// 2
do {
...break;
...break;
} while (false);
// 3
for(int i = 0;...) {
bool restartOuter = false;
for (int j = 0;...) {
if (...)
restartOuter = true;
if (restartOuter) {
i = -1;
}
}
etc
etc
Dado que goto
dificulta el razonamiento sobre el flujo del programa 1 (también conocido como “código espagueti”), goto
generalmente solo se usa para compensar características faltantes: el uso de goto
puede ser aceptable, pero solo si el lenguaje no ofrece una variante más estructurada para obtener el mismo objetivo. Tomemos el ejemplo de la duda:
La regla que usamos con goto es que goto está bien para saltar hacia un único punto de limpieza de salida en una función.
Esto es cierto, pero solo si el lenguaje no permite el manejo estructurado de excepciones con código de limpieza (como RAII o finally
), que hace mejor el mismo trabajo (ya que está diseñado especialmente para hacerlo), o cuando hay una buena razón para no hacerlo. emplear un manejo estructurado de excepciones (pero nunca tendrá este caso excepto en un nivel muy bajo).
En la mayoría de los demás idiomas, el único uso aceptable goto
es salir de bucles anidados. E incluso allí, casi siempre es mejor levantar el bucle exterior con un método propio y utilizarlo return
en su lugar.
Aparte de eso, goto
es una señal de que no se ha pensado lo suficiente en ese fragmento de código en particular.
1 Los lenguajes modernos que admiten goto
implementan algunas restricciones (por ejemplo, goto
no pueden entrar o salir de funciones), pero el problema sigue siendo fundamentalmente el mismo.
Por cierto, lo mismo también se aplica a otras características del lenguaje, sobre todo a las excepciones. Y normalmente existen reglas estrictas para usar estas funciones solo cuando se indique, como la regla de no usar excepciones para controlar el flujo de programas no excepcionales.