¿Es este un caso de uso decente para ir a C?

59

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 gotocomando, 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 gotoen C? ¿Hay una manera diferente / mejor de hacer esto?

Robz
fuente
3
Vea también esta pregunta (y mi respuesta ).
Keith Thompson
2
Más que el recubrimiento de azúcar, el compilador garantiza la limpieza y la verificación de errores, mientras que en C debe hacerlo manualmente, violando DRY y garantizando que en algún lugar fue demasiado vago para verificar el código de error o fue a la etiqueta incorrecta o algo así.
DeadMG
3
@Izkata: No, el punto es tener funciones (como destructores) que el compilador llama implícitamente cuando se produce una excepción.
DeadMG
66
@DeadMG: Usar otro idioma no siempre es una opción.
Benjamin Kloster
13
Creo que con el nombre apropiado de la etiqueta, el ejemplo puede parecer bastante decente:goto hell;
mosquito

Respuestas:

48

La gotodeclaració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 whilebucle, use un whilebucle , ¿de acuerdo? Los patrones de programación estructurados son definitivamente al menos potencialmente más claros de intención que un desorden de gotodeclaraciones.

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/ finallyestructura (y excepciones también). También carece de múltiples niveles breakde bucle. Estos son los tipos de cosas que gotose pueden usar para implementar. Es posible usar otros esquemas para hacer esto también: sabemos que C tiene un conjunto suficiente degotoprimitivas, 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 definitivamente goto , son muy baratas).

Por lo tanto, aunque no debe usarlo a gotomenos 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 sin gotohacerlo, pero hacerlo puede tener sus propios inconvenientes, como los problemas de mantenimiento. Un ejemplo del caso:

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

El código podría ser aún más corto, pero es suficiente para demostrar el punto.

Compañeros de Donal
fuente
44
+1: En C goto técnicamente nunca es "necesario": siempre hay una manera de hacerlo, se vuelve desordenado ..... Para un conjunto sólido de pautas para el uso de goto, mire MISRA C.
mattnz
1
¿Preferiría, try/catch/finallya gotopesar 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 usar try/catch/finally?
autista
65

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:

De: Robert Love
Asunto: Re: ¿alguna posibilidad de 2.6.0-test *?
Fecha: 12 de enero de 2003 17:58:06 -0500

El domingo, 12 de enero de 2003 a las 17:22, Rob Wilkens escribió:

Digo "por favor no use goto" y en su lugar tenga una función "cleanup_lock" y agregue eso antes de todas las declaraciones de devolución. No debería ser una carga. Sí, le está pidiendo al desarrollador que trabaje un poco más duro, pero el resultado final es un mejor código.

No, es asqueroso e hincha el núcleo . Incluye un montón de basura para N rutas de error, en lugar de tener el código de salida una vez al final. La huella del caché es clave y la acabas de matar.

Tampoco es más fácil de leer.

Como argumento final, no nos permite limpiar y desenrollar de manera limpia el estilo habitual de la pila , es decir

        do A
        if (error)
            goto out_a;
        do B
        if (error)
            goto out_b;
        do C
        if (error)
            goto out_c;
        goto out;
        out_c:
        undo C
        out_b:
        undo B:
        out_a:
        undo A
        out:
        return ret;

Ahora detén esto.

Robert Love

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.

Izkata
fuente
21
Tenga en cuenta que un kernel es diferente de un programa que no es del kernel con respecto a la prioridad en velocidad bruta versus legibilidad. En otras palabras, YA se han perfilado y descubrieron que necesitan optimizar su código para la velocidad con goto.
11
¡Usando el desbobinado de la pila para manejar la limpieza en caso de error sin presionar realmente la pila! Este es un uso impresionante de goto.
mike30
1
@ user1249, Basura, no puede perfilar todas las aplicaciones {pasadas, existentes, futuras} que usan un fragmento de código {biblioteca, núcleo}. Simplemente necesitas ser rápido.
Pacerier
1
Sin relación: estoy sorprendido de cómo las personas pueden usar listas de correo para hacer cualquier cosa, y mucho menos para proyectos tan masivos. Es tan ... primitivo. ¿Cómo entra la gente con la estación de bomberos de los mensajes?
Alexander
2
No estoy tan preocupado por la moderación. Si alguien es lo suficientemente blando como para ser rechazado por un imbécil en Internet, su proyecto probablemente esté mejor sin él. Estoy más preocupado por la impracticabilidad de mantenerse al día con el aluvión de mensajes entrantes, y cómo podría tener una discusión natural con tan pocas herramientas para realizar un seguimiento de las citas, por ejemplo.
Alexander
14

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 gotodurante 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:

Error some_func (void)
{
  Error error;
  type_t* resource = malloc(...);

  error = some_other_func (resource);

  free (resource);

  /* error handling can be placed here, or it can be returned to the caller */

  return error;
}


Error some_other_func (type_t* resource)  // inline if needed
{
  error = function_that_could_fail_1();
  if(error)
  {
    return error;
  }

  /* ... */

  error = function_that_could_fail_2();
  if(error)
  {
    return error;
  }

  /* ... */

  return ok;
}

Ventajas de este diseño:

  • La función que realiza el trabajo real no necesita ocuparse de tareas que son irrelevantes para su algoritmo, como la asignación de datos.
  • El código se verá menos extraño para los programadores de C, ya que tienen miedo de ir a las etiquetas.
  • Puede centralizar el manejo de errores y la desasignación en el mismo lugar, fuera de la función que realiza el algoritmo. No tiene sentido que una función maneje sus propios resultados.
Doc Brown
fuente
11

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.

Ninguna posibilidad
fuente
9

En Java lo harías así:

makeCalls:  {
    error = function_that_could_fail_1();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_2();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_3();
    if (error) {
        break makeCalls;
    }
    ...
    return 0;  // No error code.
}
// deal with error if it exists, clean up
// return error code

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 etiqueta breaky en todas partes utilizo a goto.)

RalphChapin
fuente
3
Aw, esa es una estructura de control ordenada.
Bryan Boettcher
44
Esto es realmente interesante Normalmente pensaría en usar la estructura try / catch / finally para esto en Java (lanzando excepciones en lugar de romper).
Robz
55
Eso es realmente ilegible (al menos para mí). Si está presente, las excepciones son mucho mejores.
m3th0dman
1
@ m3th0dman Estoy de acuerdo con usted en este ejemplo particular (manejo de errores). Pero hay otros casos (no excepcionales) en los que este modismo podría ser útil.
Konrad Rudolph el
1
Las excepciones son caras, necesitan generar un error, stacktrace y mucha más basura. Este salto de etiqueta proporciona una salida limpia de un bucle de comprobación. a menos que uno no se preocupe por la memoria y la velocidad, entonces, para todo lo que me importa, use una excepción.
Tschallacka
8

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:

error = function_that_could_fail_1();
error = error || function_that_could_fail_2();
error = error || function_that_could_fail_3();
if(error)
{
     // do cleanup
}

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.

Doc Brown
fuente
1
El problema con esto es que errorel valor podría no tener sentido con todos los OR.
James
@James: edité mi respuesta debido a tu comentario
Doc Brown
1
Esto no es suficiente. Si ocurrió un error durante la primera función, no quiero ejecutar la segunda o tercera función.
Robz
2
Si con evaluación de mano corta quiere decir evaluación de cortocircuito , esto no es exactamente lo que se hace aquí debido al uso de OR bit a bit en lugar de OR lógico.
Chris dice que reinstala a Mónica el
1
@ChristianRau: gracias, edité mi respuesta en consecuencia
Doc Brown
6

La guía de estilo de Linux proporciona razones específicas para usar gotolas que están en línea con su ejemplo:

https://www.kernel.org/doc/Documentation/process/coding-style.rst

La razón para usar gotos es:

  • las declaraciones incondicionales son más fáciles de entender y seguir
  • la anidación se reduce
  • errores al no actualizar los puntos de salida individuales cuando se evitan las modificaciones
  • guarda el trabajo del compilador para optimizar el código redundante de distancia;)

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:

tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        free(var1);
        free(var2);
        ...
        free(varN);
        return 1;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            free(var1);
            free(var2);
            ...
            free(varN);
            return 1;
        } else {
            free(var1);
            free(var2);
            ...
            free(varN);
            return 0;
        }
    }
    token = strtok(NULL,delim);
}

free(var1);
free(var2);
...
free(varN);
return 1;

Ahora para mí, el siguiente código es mucho mejor y más fácil de mantener si necesita agregar un varNplus1:

int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        retval = 1;
        goto out_free;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            retval = 1;
            goto out_free;
        } else {
            retval = 0;
            goto out_free;
        }
    }
    token = strtok(NULL,delim);
}

out_free:
free(var1);
free(var2);
...
free(varN);
return retval;

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

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 == NULL ){
        retval = 0;
        goto out;
    }

    if( isValid(var1) ){
         retval = some_function(var1);
         goto out_free;
    }

    if( isGood(var1) ){
         retval = 0;
         goto out_free;
    }

out_free:
    free(var1);
out:
    return retval;
}

Si consideramos el equivalente sin gotos:

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 != NULL ){

       if( isValid(var1) ){
            retval = some_function(var1);
       } else {
          if( isGood(var1) ){
               retval = 0;
          }
       }
       free(var1);

    } else {
       retval = 0;
    }

    return retval;
}

Para mí, en el primer caso, es obvio para mí que si la primera función regresa NULL, nos vamos de aquí y volvemos 0. 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, las free()declaraciones en el medio de las funciones huelen mal y me indican que debería crear una subrutina. En este caso, creé var1en mi función y no pude free()hacerlo en una subrutina, pero es por eso que el goto out_freeestilo de goto out es tan práctico.

Creo que los programadores necesitan ser educados creyendo que gotoson 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 retvaluna out_freeetiqueta 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

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);

    if( functionC(var1, var2){
         count++
         continue;
    }

    ...
    a bunch of statements
    ...

    count++;
    free(var1);
    free(var2);
}

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 liberarnos var1y var2. Tuve que agregar un var3, 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:

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);
    var3 = newFunction(line,count);

    if( functionC(var1, var2){
         goto next;
    }

    ...
    a bunch of statements
    ...
next:
    count++;
    free(var1);
    free(var2);
}

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 la next:etiqueta no me satisfacen. Son simplemente mejores que reflejar los free()'sy las count++declaraciones.

gotoCasi 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

#include <stddef.h>
#include <string.h>

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}

Corríjame si me equivoco, pero creo que la cont:etiqueta y la goto 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 haciendo

while( isDelim(*s++,delim));

omitir 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.

  • Si en un ciclo while, tiene una continuación, entonces el cuerpo del ciclo while debe ser una función y la continuación debe ser una declaración de retorno.
  • Si en un ciclo while, tiene una instrucción break, entonces el ciclo while en sí mismo debería ser una función y el break debería convertirse en una declaración return.
  • Si tiene ambos, entonces algo podría estar mal.

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.

Philippe Carphin
fuente
2
+1 pero ¿puedo estar en desacuerdo en un punto? "Creo que los programadores necesitan ser educados creyendo que los goto son malos". Tal vez sí, pero aprendí a programar en BASIC, con números de línea y GOTO, sin un editor de texto, en 1975. Conocí la programación estructurada diez años después, después de lo cual me llevó un mes dejar de usar GOTO por mi cuenta, sin cualquier presión para parar. Hoy, uso GOTO varias veces al año por varias razones, pero no aparece mucho. No haber sido educado para creer que GOTO es malo no me ha hecho ningún daño que yo sepa, e incluso podría haber hecho algo bueno. Así soy yo.
thb
1
Creo que tienes razón en eso. Se me ocurrió la idea de que los GOTO no debían usarse y, por pura casualidad, estaba navegando por el código fuente de Linux en un momento en que estaba trabajando en el código que tenía estas funciones con múltiples puntos de salida con memoria libre. De lo contrario, nunca habría sabido sobre estas técnicas.
Philippe Carphin
1
@thb Además, una historia divertida, le pedí permiso a mi supervisor en ese momento, como pasante, para usar GOTO's y me aseguré de explicarle que los iba a usar de una manera específica, como se usa en el Kernel de Linux y dijo "Ok, eso tiene sentido, y además, no sabía que podías usar GOTO's en C".
Philippe Carphin
1
@thb No sé si es bueno ir a bucles (en lugar de romper los bucles) como este . Bueno, es un mal ejemplo, pero me parece que la clasificación rápida con declaraciones goto (ejemplo 7a) en la Programación estructurada de Knuth con Ir a declaraciones no es muy comprensible.
Yai0Phah
@ Yai0Phah Explicaré mi punto, ¡pero mi explicación no disminuye su buen ejemplo 7a! Yo apruebo el ejemplo. Aún así, a los estudiantes de segundo año mandones les gusta dar conferencias sobre el goto. Es difícil encontrar un uso práctico de goto desde 1985 que cause problemas significativos, mientras que uno puede encontrar objetos inofensivos que facilitan el trabajo del programador. De todos modos, rara vez aparece Goto en la programación moderna, que, cuando surja, mi consejo es que, si quieres usarlo, entonces probablemente deberías usarlo. Goto está bien. El principal problema con goto es que algunos creen que despreciar el goto los hace parecer inteligentes.
THB
5

Personalmente lo refactorizaría más así:

int DoLotsOfStuffThatCouldFail (paramstruct *params)
{
    int errcode = EC_NOERROR;

    if ((errcode = FunctionThatCouldFail1 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail2 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail3 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail4 (params)) != EC_NOERROR) return errcode;

    return EC_NOERROR;
}

void DoStuff (paramstruct *params)
{
    int errcode = EC_NOERROR;

    InitStuffThatMayNeedToBeCleaned (params);

    if ((errcode = DoLotsOfStuffThatCouldFail (params)) != EC_NOERROR)
    {
         CleanupAfterError (params, errcode);
    }
}

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.

Maximus Minimus
fuente
1
No dije esto en mi pregunta, pero es posible que los FTCF NO tengan los mismos parámetros, lo que hace que este patrón sea un poco más complicado. Gracias sin embargo.
Robz
3

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.

error = function_that_could_fail_1();
if(!error) {
  error = function_that_could_fail_2();
}
if(!error) {
  error = function_that_could_fail_3();
} 
if(!error) {
...
if (error) {
  cleanup:
} 

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" ....

do {
  if (error = function_that_could_fail_1() ){
    break 
  }
  if (error = function_that_could_fail_2() ){
    break 
  }
  ....... 
} while (0) 
cleanup();
.... 

Si las funciones tienen el mismo tipo de parámetro, colóquelas en una tabla y use un bucle:

Mattnz
fuente
2
Las pautas actuales de MISRA-C: 2004 no permiten ir a ninguna forma (ver la regla 14.4). Tenga en cuenta que el comité MISRA siempre ha estado confundido acerca de esto, no saben qué pie pisar. Primero, prohibieron incondicionalmente el uso de goto, continúan, etc. Pero en el borrador para el próximo MISRA 2011 quieren permitirlos nuevamente. Como nota al margen, tenga en cuenta que MISRA prohíbe la asignación dentro de declaraciones if, por muy buenas razones, ya que es mucho más peligroso que cualquier uso de goto.
1
Desde una perspectiva analítica, agregar un indicador a un programa es equivalente a duplicar todo el código del código donde el indicador está dentro del alcance, hacer que cada 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 un gotoobjetivo no es una buena compensación.
supercat
1

También uso gotosi la do/while/continue/breakpiratería alternativa sería menos legible.

gotos tienen la ventaja de que sus objetivos tienen un nombre y leen goto something;. Esto puede ser más legible que breako continuesi realmente no está deteniendo o continuando algo.

aib
fuente
44
En cualquier lugar dentro de una do ... while(0)u otra construcción que no sea un bucle real sino un intento descabellado para evitar el uso de a goto.
aib
1
Ah, gracias, no conocía esta marca particular de "¿Por qué demonios alguien haría eso?" construye todavía.
Benjamin Kloster
2
Por lo general, la piratería do / while / continue / break solo se vuelve ilegible cuando el módulo que lo contiene es demasiado largo en primer lugar.
John R. Strohm
2
No puedo encontrar nada en esto como justificación para usar goto. Romper y continuar tiene una consecuencia obvia. ¿Ir a dónde? ¿Dónde está la etiqueta? Romper y pasar a decir exactamente dónde está el siguiente paso y sus alrededores.
Plataforma
1
La etiqueta, por supuesto, debe ser visible desde dentro del bucle. Estoy de acuerdo con la parte del mensaje de @John R. Strohm. Y su punto, traducido a piratería informática de bucles, se convierte en "¿Salir de qué? ¡Esto no es un bucle!". En cualquier caso, esto se está convirtiendo en lo que el OP temía, por lo que estoy abandonando la discusión.
aib
-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:
aib
fuente
Si solo hay un bucle, breakfunciona exactamente igual goto, aunque sin estigma.
9000
66
-1: Primero, xey están FUERA DE ALCANCE en encontrado :, por lo que esto no te ayuda en nada. En segundo lugar, con el código escrito, el hecho de que haya llegado a encontrado: no significa que haya encontrado lo que estaba buscando.
John R. Strohm
Es porque este es el ejemplo más pequeño que se me ocurre para el caso de romper un número múltiple de bucles. Por favor, siéntase libre de editarlo para una mejor etiqueta o un cheque hecho.
aib
1
Pero también tenga en cuenta que las funciones C no están necesariamente libres de efectos secundarios.
aib
1
@ JohnR.Strohm Eso no tiene mucho sentido ... La etiqueta 'encontrado' se usa para romper el ciclo, no para verificar las variables. Si quisiera verificar las variables, podría hacer algo como esto: for (int y = 0; y <height; ++ y) {for (int x = 0; x <width; ++ x) {if (find (find ( x, y)) {doSomeThingWith (x, y); Goto encontrado; }}} encontrado:
YoYoYonnY
-1

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:

  • Pruebe las condiciones que podrían impedir el procesamiento (entradas incorrectas, etc.) y el "retorno"
  • Realice todos los pasos que requieren la asignación de recursos (por ejemplo, mallocs)
  • Realice el procesamiento, donde múltiples pasos verifican el éxito
  • Libere cualquier recurso, si se asigna correctamente
  • Devolver cualquier resultado

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.

pcm
fuente