Realmente dudo en preguntar esto, porque no quiero "solicitar debates, argumentos, encuestas o discusiones extendidas", pero soy nuevo en C y quiero obtener más información sobre los patrones comunes utilizados en el lenguaje.
Recientemente escuché cierto desagrado por el goto
comando, pero también recientemente encontré un caso de uso decente para él.
Código como este:
error = function_that_could_fail_1();
if (!error) {
error = function_that_could_fail_2();
if (!error) {
error = function_that_could_fail_3();
...to the n-th tab level!
} else {
// deal with error, clean up, and return error code
}
} else {
// deal with error, clean up, and return error code
}
Si la parte de limpieza es muy similar, podría escribirse un poco más bonita (¿mi opinión?) Así:
error = function_that_could_fail_1();
if(error) {
goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code
¿Es este un caso de uso común o aceptable de goto
en C? ¿Hay una manera diferente / mejor de hacer esto?
c
design-patterns
goto
Robz
fuente
fuente
goto hell;
Respuestas:
La
goto
declaración (y sus etiquetas correspondientes) son una primitiva de control de flujo (junto con la ejecución condicional de una declaración). Con eso quiero decir que están allí para permitirle construir redes de control de flujo de programas. Puede pensar en ellos como modelos de las flechas entre los nodos de un diagrama de flujo.Algunos de estos pueden optimizarse de inmediato, donde hay un flujo lineal directo (solo usa una secuencia de declaraciones básicas). Otros patrones se reemplazan mejor con construcciones de programación estructurada donde están disponibles; si parece un
while
bucle, use unwhile
bucle , ¿de acuerdo? Los patrones de programación estructurados son definitivamente al menos potencialmente más claros de intención que un desorden degoto
declaraciones.Sin embargo, C no incluye todas las posibles construcciones de programación estructurada. (No está claro para mí que todos los relevantes hayan sido descubiertos todavía; la tasa de descubrimiento es lenta ahora, pero dudaría en decir que se han encontrado todos). De los que sabemos, C definitivamente carece del
try
/catch
/finally
estructura (y excepciones también). También carece de múltiples nivelesbreak
de bucle. Estos son los tipos de cosas quegoto
se pueden usar para implementar. Es posible usar otros esquemas para hacer esto también: sabemos que C tiene un conjunto suficiente degoto
primitivas, pero a menudo implican la creación de variables de bandera y condiciones de protección o bucle mucho más complejas; aumentar el enredo del análisis de control con el análisis de datos hace que el programa sea más difícil de entender en general. También hace que sea más difícil para el compilador optimizar y que la CPU se ejecute rápidamente (la mayoría de las construcciones de control de flujo, y definitivamentegoto
, son muy baratas).Por lo tanto, aunque no debe usarlo a
goto
menos que sea necesario, debe tener en cuenta que existe y que puede ser necesario, y si lo necesita, no debe sentirse tan mal. Un ejemplo de un caso en el que se necesita es la desasignación de recursos cuando una función llamada devuelve una condición de error. (Es decir,try
/finally
.) Es posible escribir eso singoto
hacerlo, pero hacerlo puede tener sus propios inconvenientes, como los problemas de mantenimiento. Un ejemplo del caso:El código podría ser aún más corto, pero es suficiente para demostrar el punto.
fuente
try/catch/finally
agoto
pesar de la forma similar, aún más generalizada (ya que puede extenderse a través de múltiples funciones / módulos) de código de espagueti que es posible usartry/catch/finally
?Si.
Se usa, por ejemplo, en el kernel de Linux. Aquí hay un correo electrónico del final de un hilo de hace casi una década , en negrita:
Dicho esto, se requiere mucha disciplina para evitar crear código de espagueti una vez que se acostumbra a usar goto, por lo que, a menos que esté escribiendo algo que requiera velocidad y poca huella de memoria (como un núcleo o un sistema incrustado), realmente debería piénselo antes de escribir el primer goto.
fuente
En mi opinión, el código que publicó es un ejemplo de un uso válido de
goto
, porque solo salta hacia abajo y solo lo usa como un controlador de excepciones primitivo.Sin embargo , debido al antiguo debate de goto, los programadores han estado evitando
goto
durante unos 40 años y, por lo tanto, no están acostumbrados a leer código con goto. Esta es una razón válida para evitar el goto: simplemente no es el estándar.Hubiera reescrito el código como algo más fácil de leer por los programadores de C:
Ventajas de este diseño:
fuente
Un famoso artículo que describe un caso de uso válido fue la Programación Estructurada con Declaración GOTO de Donald E. Knuth (Universidad de Stanford). El documento apareció en los días en que el uso de GOTO era considerado un pecado por algunos y cuando el movimiento para la Programación Estructurada estaba en su apogeo. Es posible que desee echar un vistazo a GoTo Considerado nocivo.
fuente
En Java lo harías así:
Yo uso esto mucho. Por mucho que no me guste
goto
, en la mayoría de los otros lenguajes de estilo C utilizo su código; No hay otra buena manera de hacerlo. (Saltar de bucles anidados es un caso similar; en Java utilizo una etiquetabreak
y en todas partes utilizo agoto
.)fuente
Creo que es un caso de uso decente, pero en caso de que "error" no sea más que un valor booleano, hay una forma diferente de lograr lo que desea:
Esto hace uso de la evaluación de cortocircuito de operadores booleanos. Si esto "mejor", depende de su gusto personal y de cómo está acostumbrado a ese idioma.
fuente
error
el valor podría no tener sentido con todos los OR.La guía de estilo de Linux proporciona razones específicas para usar
goto
las que están en línea con su ejemplo:https://www.kernel.org/doc/Documentation/process/coding-style.rst
Descargo de responsabilidad Se supone que no debo compartir mi trabajo. Los ejemplos aquí son un poco artificiales, así que tengan paciencia, tengan paciencia conmigo.
Esto es bueno para la gestión de la memoria. Recientemente trabajé en código que tenía memoria asignada dinámicamente (por ejemplo, un
char *
devuelto por una función). Una función que analiza una ruta y determina si la ruta es válida analizando los tokens de la ruta:Ahora para mí, el siguiente código es mucho mejor y más fácil de mantener si necesita agregar un
varNplus1
:Ahora el código tenía todo tipo de otros problemas, a saber, que N estaba en algún lugar por encima de 10, y la función tenía más de 450 líneas, con 10 niveles de anidamiento en algunos lugares.
Pero le ofrecí a mi supervisor que lo refactorizara, lo cual hice y ahora es un montón de funciones que son todas cortas, y todas tienen el estilo Linux
Si consideramos el equivalente sin
goto
s:Para mí, en el primer caso, es obvio para mí que si la primera función regresa
NULL
, nos vamos de aquí y volvemos0
. En el segundo caso, tengo que desplazarme hacia abajo para ver si el if contiene toda la función. De acuerdo, el primero me indica esto estilísticamente (el nombre "out
") y el segundo lo hace sintácticamente. El primero es aún más obvio.Además, prefiero tener
free()
declaraciones al final de una función. Esto se debe en parte a que, en mi experiencia, lasfree()
declaraciones en el medio de las funciones huelen mal y me indican que debería crear una subrutina. En este caso, creévar1
en mi función y no pudefree()
hacerlo en una subrutina, pero es por eso que elgoto out_free
estilo de goto out es tan práctico.Creo que los programadores necesitan ser educados creyendo que
goto
son malos. Luego, cuando sean lo suficientemente maduros, deberían explorar el código fuente de Linux y leer la guía de estilo de Linux.Debo agregar que uso este estilo de manera muy consistente, cada función tiene
retval
unaout_free
etiqueta int , una etiqueta y una etiqueta de salida. Debido a la consistencia estilística, se mejora la legibilidad.Bonus: se rompe y continúa
Digamos que tienes un ciclo while
Hay otras cosas mal con este código, pero una cosa es la declaración de continuación. Me gustaría reescribir todo el asunto, pero me encargaron modificarlo de una manera pequeña. Me habría llevado días refactorizarlo de una manera que me satisficiera, pero el cambio real fue aproximadamente medio día de trabajo. El problema es que incluso si nosotros '
continue
' todavía necesitamos liberarnosvar1
yvar2
. Tuve que agregar unvar3
, y me hizo querer vomitar tener que reflejar las declaraciones free ().Era un pasante relativamente nuevo en ese momento, pero había estado mirando el código fuente de Linux por diversión hace un tiempo, así que le pregunté a mi supervisor si podía usar una declaración goto. Él dijo que sí, e hice esto:
Creo que las continuas están bien en el mejor de los casos, pero para mí son como un goto con una etiqueta invisible. Lo mismo ocurre con los descansos. Todavía preferiría continuar o interrumpir a menos que, como fue el caso aquí, te obligue a reflejar modificaciones en varios lugares.
Y también debo agregar que este uso
goto next;
y lanext:
etiqueta no me satisfacen. Son simplemente mejores que reflejar losfree()
'sy lascount++
declaraciones.goto
Casi siempre están equivocados, pero uno debe saber cuándo son buenos para usar.Una cosa que no discutí es el manejo de errores que ha sido cubierto por otras respuestas.
Actuación
Se puede ver la implementación de strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
Corríjame si me equivoco, pero creo que la
cont:
etiqueta y lagoto cont;
declaración están ahí para el rendimiento (seguramente no hacen que el código sea más legible). Podrían ser reemplazados con código legible haciendoomitir delimitadores. Pero para ser lo más rápido posible y evitar llamadas a funciones innecesarias, lo hacen de esta manera.
Leí el periódico de Dijkstra y lo encuentro bastante esotérico.
google "declaración de dijkstra goto considerada dañina" porque no tengo suficiente reputación para publicar más de 2 enlaces.
Lo he visto citado como una razón para no usar goto's y leerlo no ha cambiado nada en lo que respecta a mis usos de goto's.
Anexo :
Se me ocurrió una regla clara mientras pensaba en todo esto sobre continuos y descansos.
No siempre es posible debido a problemas de alcance, pero he descubierto que hacer esto hace que sea mucho más fácil razonar sobre mi código. Me di cuenta de que cada vez que un ciclo while se interrumpía o continuaba, me daba un mal presentimiento.
fuente
Personalmente lo refactorizaría más así:
Sin embargo, eso estaría más motivado por evitar el anidamiento profundo que evitar el goto (en mi opinión, un problema peor con el primer ejemplo de código) y, por supuesto, dependería de que CleanupAfterError esté fuera de alcance (en este caso, los "params" podrían puede ser una estructura que contiene memoria asignada que necesita liberar, un ARCHIVO * que necesita cerrar o lo que sea).
Una de las principales ventajas que veo con este enfoque es que es más fácil y más limpio ubicar un hipotético paso adicional futuro entre, por ejemplo, FTCF2 y FTCF3 (o eliminar un paso actual existente), por lo que se presta mejor para la mantenibilidad (y la persona que hereda mi código que no quiere lincharme!) - a un lado, la versión anidada carece de eso.
fuente
Eche un vistazo a las pautas de codificación C de MISRA (Motor Industry Software Reliability Association) que permiten ir a criterios estrictos (que cumple su ejemplo)
Donde trabajo, se escribiría el mismo código, sin necesidad de ir a Goto. Evitar debates religiosos innecesarios sobre ellos es una gran ventaja en cualquier casa de software.
o para "goto in drag" - algo aún más dudoso que goto, pero evita el "No goto Ever !!!" campamento) "Seguramente debe estar bien, no usa Goto" ....
Si las funciones tienen el mismo tipo de parámetro, colóquelas en una tabla y use un bucle:
fuente
if(flag)
copia tome la rama "if", y que cada declaración correspondiente en la otra copia tome el " más". Las acciones que establecen y borran la bandera son realmente "gotos" que saltan entre esas dos versiones del código. Hay momentos en que el uso de banderas es más limpio que cualquier otra alternativa, pero agregar una bandera para salvar ungoto
objetivo no es una buena compensación.También uso
goto
si lado/while/continue/break
piratería alternativa sería menos legible.goto
s tienen la ventaja de que sus objetivos tienen un nombre y leengoto something;
. Esto puede ser más legible quebreak
ocontinue
si realmente no está deteniendo o continuando algo.fuente
do ... while(0)
u otra construcción que no sea un bucle real sino un intento descabellado para evitar el uso de agoto
.fuente
break
funciona exactamente igualgoto
, aunque sin estigma.Siempre habrá campamentos que dicen que una forma es aceptable y otra que no. Las empresas para las que he trabajado han desaprobado o desaconsejado el uso de goto. Personalmente, no puedo pensar en ningún momento en que haya usado uno, pero eso no significa que sean malos , solo otra forma de hacer las cosas.
En C, normalmente hago lo siguiente:
Para el procesamiento, usando su ejemplo goto, haría esto:
error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }
No hay anidamiento, y dentro de las cláusulas if, puede hacer cualquier informe de error, si el paso generó un error. Por lo tanto, no tiene que ser "peor" que un método con gotos.
Todavía tengo que encontrarme con un caso en el que alguien tiene problemas que no se pueden hacer con otro método y es tan legible / comprensible y esa es la clave, en mi humilde opinión.
fuente