2012-04-07 11 views
7

Estoy escribiendo un programa en C que necesita un buen manejo de errores. El código le gusta así:Enfoque de gestión de errores de limpieza en C

If(doWork("A")<0){ 
    return -1; 
} 
If(doWork("B")<0){ 
    undoWork("A"); 
    return -1; 
} 
If(doWork("C")<0){ 
    undoWork("A"); 
    undoWork("B"); 
    return -1; 
} 
return 0; 

Este código funciona, pero se ve muy complicado, sobre todo tengo una larga lista de doWork(X) llamar. ¿Hay un enfoque mejor y más limpio para manejar el error en este caso?

+1

posible duplicado de [Cómo evitar larga cadena de la libre (o elimina) después de cada comprobación de errores en C?] (Http://stackoverflow.com/questions/3339946/how-to-avoid-long- chain-of-frees-or-deletes-after-every-error-check-in-c) – karlphillip

+1

Hay muchas buenas respuestas, pero lo mejor que puedo ofrecer es un comentario que las acompañe: cualquiera que sea el enfoque que tome, usted ' Siempre minimizaré la fealdad como esta y maximizaré la solidez, minimizando el número de pasos que tienen un caso de falla. Por ejemplo, precomputar la cantidad de memoria que necesitará y realizar un solo 'malloc' antes de comenzar, en lugar de asignar cantidades pequeñas en cada paso, puede hacer que muchas tareas con lógica compleja de salida de error sean simples. –

Respuesta

7

Algunas personas, especialmente los programadores-principiante a intermedio, tienen una reacción muy peculiar de ver goto en el código de producción, pero el idioma habitual para secuencial de adquisición de recursos y su liberación inteligente en caso de error es el siguiente:

if(doWork("A") < 0) 
    goto errA; 

if(doWork("B") < 0) 
    goto errB; 

if(doWork("C") < 0) 
    goto errC; 

/* success! */ 
return 0; 

/* Error handling/releasing resources section */ 
errC: 
    undoWork("B"); 
errB: 
    undoWork("A"); 
errA: 

return -1; 

Verá muchos ejemplos en el código del sistema, por ejemplo en el kernel de Linux.

0

Probablemente no. Los lenguajes más nuevos, como C++ y C#, favorecen las excepciones para ayudar a mejorar situaciones como esta.

Quizás podría tener una tabla que de alguna manera indicara qué tareas ha realizado y deshaga esas. Pero realmente creo que eso haría que tu código sea más complejo y no menos.

También tenga en cuenta que, si bien hay algunas sensaciones bastante fuertes sobre el uso de goto, de hecho hay momentos en que eso puede simplificar estructuras como esta.

+0

Incluso los lenguajes más nuevos como golang tienen construcciones incluso mejores como 'defer'. – Flavius

0

si es posible almacenar todo lo que tiene que llamar doWork en una matriz, entonces podría acortar el código significativamente algo así como.

int i = 0; 
int len = MAX_NUM; //set to the value of calls 
int error = 0; 

for(i = 0; i < len; i++) { 
    if(doWork(a[i]) < 0) { 
     error = 1; 
     break; 
    } 
} 

if(error) { 
    for(int j = 0; j < i; i++) { 
     undoWork(a[j]); 
    } 
    return -1; 
} 
1

Siendo la misma tarea doWork, es probable que pueda definir una lista enlazada o vector de jobs y pasar que como parámetro para doWork, añadir la información correspondiente a esta lista dentro de la función, y sólo llamar undoWork vez:

If(doWork("A", &jobs)<0){ 
    return -1; 
} 
If(doWork("B", &jobs)<0){ 
    undoWork(jobs); 
    return -1; 
} 
If(doWork("C", &jobs)<0){ 
    undoWork(jobs); 
    return -1; 
} 
return 0; 

De esta manera, su lógica no se volverá demasiado complicada, sin importar la combinación de trabajos para deshacer.

La ventaja, en comparación con la solución de @ twain249, es que la función decide si un trabajo se agrega a la lista o no, por lo que tiene un buen aislamiento, modularidad.

Por supuesto, puede combinar alguna forma de una estructura de datos interable con esto, a reducir aún más la cantidad de código repetitivo:

for(i=0; i < jobdata.size; i++) { 
    If(doWork(jobdata[i], &jobs)<0){ 
     undowork(jobs); 
     return -1; 
    } 
} 

Como se puede notar, diseño de la estructura de datos juega un papel importante en diseño de algoritmo, generalmente mucho más importante de lo que normalmente se piensa.

Podría haber miles de trabajos, el código seguirá siendo de cuatro líneas.

0

Si no tiene una lista súper larga, puede abordarlo de esta manera.

if (dowork("A") >=0) { 
if (dowork("B") >=0) { 
if (dowork("C") >=0) { 
if (dowork("D") >=0) return 0; 
undowork("C"); } 
undowork("B"); } 
undowork("A"); } 
return -1; 
+0

No entiendo por qué la gente estaba abajo votando esto. Cuando se compila, será exactamente lo mismo que hacer la técnica de goto/etiqueta. – EdH

0

También hay otro enfoque ampliamente utilizado basado en un bucle de un solo pase que es claro y no requiere goto.Sin embargo, implica que las funciones Deshacer manejan correctamente tanto el trabajo realizado como el que no.

do 
{ 
    if(doWork("A")<0) 
    break; 

    if(doWork("B")<0) 
    break; 

    if(doWork("C")<0) 
    break; 

    return 0; 
} 
while(0); 

undoWork("A"); 
undoWork("B"); 
undoWork("C"); 
return -1; 
Cuestiones relacionadas