Cuando el flujo de código es así:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
En general, he visto que esto funciona para evitar el flujo de código desordenado anterior:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
¿Cuáles son algunas formas mejores de evitar esta solución / pirateo para que se convierta en un código de nivel superior (nivel industrial)?
¡Cualquier sugerencia que esté lista para usar es bienvenida!
goto
, pero estoy seguro de que alguien me rebajará por sugerir eso, así que no estoy escribiendo una respuesta a ese efecto. Tener un LARGOdo ... while(0);
parece ser lo incorrecto.goto
, sea honesto y hágalo a la intemperie, no lo oculte usandobreak
ydo ... while
goto
esfuerzo de rehabilitación. :)Respuestas:
Se considera una práctica aceptable aislar estas decisiones en una función y usar
return
s en lugar debreak
s. Si bien todas estas verificaciones corresponden al mismo nivel de abstracción que la función, es un enfoque bastante lógico.Por ejemplo:
fuente
return
está más limpio porque cualquier lector es inmediatamente consciente de que funciona correctamente y de lo que hace. Congoto
usted debe mirar a su alrededor para ver para qué sirve y para asegurarse de que no se cometieron errores. El disfraz es el beneficio.forceinline
.Hay momentos en que usar
goto
es realmente la respuesta CORRECTA, al menos para aquellos que no se crían en la creencia religiosa de que "goto
nunca puede ser la respuesta, sin importar cuál sea la pregunta", y este es uno de esos casos.Este código está usando el truco
do { ... } while(0);
con el único propósito de disfrazarse de agoto
como abreak
. Si vas a usargoto
, entonces sé abierto al respecto. No tiene sentido hacer que el código sea MÁS DURO de leer.Una situación particular es cuando tienes mucho código con condiciones bastante complejas:
En realidad, puede aclarar que el código es correcto al no tener una lógica tan compleja.
Editar: Para aclarar, de ninguna manera propongo el uso de
goto
como una solución general. Pero hay casos en quegoto
es una mejor solución que otras soluciones.Imagine, por ejemplo, que estamos recopilando algunos datos, y las diferentes condiciones que se están probando son una especie de "este es el final de los datos que se recopilan", que depende de algún tipo de marcadores de "continuar / finalizar" que varían según el lugar Estás en el flujo de datos.
Ahora, cuando hayamos terminado, debemos guardar los datos en un archivo.
Y sí, a menudo hay otras soluciones que pueden proporcionar una solución razonable, pero no siempre.
fuente
goto
puede tener un lugar, perogoto cleanup
no lo tiene. La limpieza se realiza con RAII.goto
nunca es la respuesta correcta. Agradecería si hubiera un comentario ...goto
porque tienes que pensar en usarlo / para entender un programa que lo usa ... Los microprocesadores, por otro lado, están construidosjumps
yconditional jumps
... entonces el problema es con algunas personas, no con la lógica o alguna otra cosa.goto
. RAII no es la solución correcta si se esperan errores (no excepcionales), ya que eso sería un abuso de excepciones.Puede usar un patrón de continuación simple con una
bool
variable:Esta cadena de ejecución se detendrá tan pronto como
checkN
regrese afalse
. No se realizarán máscheck...()
llamadas debido al cortocircuito del&&
operador. Por otra parte, los compiladores de optimización son lo suficientemente inteligente como para reconocer que el establecimientogoOn
defalse
una calle de sentido único, e inserte el faltantegoto end
para usted. Como resultado, el rendimiento del código anterior sería idéntico al de undo
/while(0)
, solo sin un doloroso golpe a su legibilidad.fuente
if
condiciones parecen muy sospechosas.if
sin importar quégoOn
ocurriera, incluso si fallaba uno de los primeros (en lugar de saltar / romper) ... pero acabo de hacer una prueba y VS2012 al menos fue lo suficientemente inteligente como para cortocircuitar todo después del primer falso de todos modos. Usaré esto más a menudo. Nota: si se utilizagoOn &= checkN()
a continuación,checkN()
siempre funcionar incluso sigoOn
erafalse
al comienzo de laif
(es decir, no hacer eso).if
s.Intente extraer el código en una función separada (o quizás más de una). Luego regrese de la función si la verificación falla.
Si está demasiado unido al código circundante para hacer eso, y no puede encontrar una manera de reducir el acoplamiento, mire el código después de este bloque. Presumiblemente, limpia algunos recursos utilizados por la función. Intente administrar estos recursos utilizando un objeto RAII ; luego reemplaza cada poco fiable
break
conreturn
(othrow
, si eso es más apropiado) y deja que el destructor del objeto te limpie.Si el flujo del programa es (necesariamente) tan escabroso que realmente necesitas un
goto
, entonces úsalo en lugar de darle un disfraz extraño.Si tiene reglas de codificación que lo prohíben ciegamente
goto
y realmente no puede simplificar el flujo del programa, entonces probablemente tendrá que disfrazarlo con sudo
truco.fuente
goto
si esa es realmente una mejor opción.TLDR : RAII , código transaccional (solo establece resultados o devuelve cosas cuando ya está calculado) y excepciones.
Respuesta larga:
En C , la mejor práctica para este tipo de código es agregar una etiqueta EXIT / CLEANUP / other en el código, donde se realiza la limpieza de los recursos locales y se devuelve un código de error (si corresponde). Esta es la mejor práctica porque divide el código naturalmente en la inicialización, el cálculo, el compromiso y el retorno:
En C, en la mayoría de bases de código, el
if(error_ok != ...
ygoto
código está generalmente oculta detrás de unas macros convenientes (RET(computation_result)
,ENSURE_SUCCESS(computation_result, return_code)
, etc.).C ++ ofrece herramientas adicionales sobre C :
La funcionalidad del bloque de limpieza se puede implementar como RAII, lo que significa que ya no necesita el
cleanup
bloque completo y permite que el código del cliente agregue declaraciones de devolución anticipadas.Lanzas cuando no puedes continuar, transformando todo
if(error_ok != ...
en llamadas directas.Código C ++ equivalente:
Esta es la mejor práctica porque:
Es explícito (es decir, si bien el manejo de errores no es explícito, el flujo principal del algoritmo sí lo es)
Es sencillo escribir código de cliente
Es minima
Es simple
No tiene construcciones de código repetitivo
No usa macros
No usa
do { ... } while(0)
construcciones rarasEs reutilizable con un esfuerzo mínimo (es decir, si quiero copiar la llamada a
computation2();
una función diferente, no tengo que asegurarme de agregar undo { ... } while(0)
código nuevo, ni#define
una macro de contenedor Goto y una etiqueta de limpieza, ni Algo más).fuente
shared_ptr
con un eliminador personalizado puede hacer muchas cosas. Aún más fácil con lambdas en C ++ 11.namespace xyz { typedef shared_ptr<some_handle> shared_handle; shared_handle make_shared_handle(a, b, c); };
en este caso (con lamake_handle
configuración del tipo de borrador correcto en la construcción), el nombre del tipo ya no sugiere que sea un puntero .Estoy agregando una respuesta en aras de la integridad. Varias otras respuestas señalaron que el bloque de condición grande podría dividirse en una función separada. Pero como también se señaló varias veces, este enfoque separa el código condicional del contexto original. Esta es una razón por la que se agregaron lambdas al lenguaje en C ++ 11. Otros sugirieron el uso de lambdas, pero no se proporcionó una muestra explícita. He puesto uno en esta respuesta. Lo que me sorprende es que se siente muy similar al
do { } while(0)
enfoque en muchos sentidos, y tal vez eso significa que sigue siendo ungoto
disfraz ...fuente
Ciertamente, no la respuesta, sino una respuesta (en aras de la integridad)
En vez de :
Podrías escribir:
Esto sigue siendo un goto disfrazado, pero al menos ya no es un bucle. Lo que significa que no tendrá que verificar con mucho cuidado que no haya algunos que continúen ocultos en algún lugar del bloque.
La construcción también es lo suficientemente simple como para que pueda esperar que el compilador la optimice.
Como lo sugiere @jamesdlin, incluso puedes ocultar eso detrás de una macro como
Y úsalo como
Esto es posible porque la sintaxis del lenguaje C espera una declaración después de un cambio, no un bloque entre corchetes y puede poner una etiqueta de caso antes de esa declaración. Hasta ahora no veía el punto de permitir eso, pero en este caso particular es útil ocultar el interruptor detrás de una buena macro.
fuente
define BLOCK switch (0) case 0:
y usarlo comoBLOCK { ... break; }
.Recomendaría un enfoque similar a la respuesta de Mats menos lo innecesario
goto
. Solo ponga la lógica condicional en la función. Cualquier código que siempre se ejecute debe ir antes o después de invocar la función en la persona que llama:fuente
func
, necesita factorizar otra función (según su patrón). Si todas estas funciones aisladas necesitan los mismos datos, simplemente terminará copiando los mismos argumentos de pila una y otra vez, o decidirá asignar sus argumentos en el montón y pasar un puntero, sin aprovechar la característica más básica del lenguaje ( argumentos de función). En resumen, no creo que esta solución se adapte al peor de los casos en el que se verifican todas las condiciones después de adquirir nuevos recursos que deben limpiarse. Sin embargo, tenga en cuenta el comentario de Nawaz sobre lambdas.func()
y permitir que su destructor se encargue de liberar recursos? Si algo fuera defunc()
necesita acceso al mismo recurso, debe declararse en el montón antes de llamarfunc()
por un administrador de recursos adecuado.El flujo de código en sí mismo ya es un olor a código que sucede mucho en la función. Si no hay una solución directa para eso (la función es una función de verificación general), entonces usar RAII para que pueda regresar en lugar de saltar a la sección final de la función podría ser mejor.
fuente
Si no necesita introducir variables locales durante la ejecución, a menudo puede aplanar esto:
fuente
Similar a la respuesta de dasblinkenlight, pero evita la asignación dentro de la
if
que podría ser "reparada" por un revisor de código:...
Utilizo este patrón cuando los resultados de un paso deben verificarse antes del siguiente paso, lo que difiere de una situación en la que todas las comprobaciones podrían realizarse por adelantado con un
if( check1() && check2()...
patrón de tipo grande .fuente
Use excepciones. Su código se verá mucho más limpio (y se crearon excepciones exactamente para manejar errores en el flujo de ejecución de un programa). Para limpiar recursos (descriptores de archivos, conexiones de bases de datos, etc.), lea el artículo ¿Por qué C ++ no proporciona una construcción "finalmente"? .
fuente
check()
condición falla, y eso es, sin duda, SU suposición, no hay contexto en la muestra. Asumiendo que el programa no puede continuar, usar excepciones es el camino a seguir.Para mi
do{...}while(0)
esta bien. Si no desea ver eldo{...}while(0)
, puede definir palabras clave alternativas para ellos.Ejemplo:
SomeUtilities.hpp:
SomeSourceFile.cpp:
Creo que el compilador eliminará la
while(0)
condición innecesariado{...}while(0)
en versión binaria y convertirá los saltos en salto incondicional. Puede verificar su versión en lenguaje ensamblador para estar seguro.El uso
goto
también produce un código más limpio y es sencillo con la lógica de condición y luego salto. Puedes hacer lo siguiente:Tenga en cuenta que la etiqueta se coloca después del cierre
}
. Este es el evitar un posible problema en elgoto
que accidentalmente se coloca un código en el medio porque no vio la etiqueta. Ahora es comodo{...}while(0)
sin código de condición.Para hacer este código más limpio y más comprensible, puede hacer esto:
SomeUtilities.hpp:
SomeSourceFile.cpp:
Con esto, puede hacer bloques anidados y especificar dónde desea salir / saltar.
El código de espagueti no es
goto
culpa del programador. Todavía puede producir código de espagueti sin usargoto
.fuente
goto
sobre extender la sintaxis del lenguaje usando el preprocesador un millón de veces.do{...}while(0)
se prefiere lo normal . esta respuesta es solo una sugerencia y un juego en macro / goto / label para romper bloques específicos.Este es un problema bien conocido y bien resuelto desde una perspectiva de programación funcional: quizás la mónada.
En respuesta al comentario que recibí a continuación, he editado mi introducción aquí: puede encontrar todos los detalles sobre la implementación de mónadas C ++ en varios lugares que le permitirán lograr lo que sugiere Rotsor. Toma un tiempo asimilar mónadas, así que en su lugar voy a sugerir aquí un mecanismo rápido tipo mónada de "pobre hombre" para el que no necesita saber nada más que boost :: opcional.
Configure sus pasos de cálculo de la siguiente manera:
Obviamente, cada paso computacional puede hacer algo como regresar
boost::none
si el opcional que se le dio está vacío. Así por ejemplo:Luego encadenarlos juntos:
Lo bueno de esto es que puede escribir pruebas unitarias claramente definidas para cada paso computacional. Además, la invocación se lee como un inglés simple (como suele ser el caso con el estilo funcional).
Si no le importa la inmutabilidad y es más conveniente devolver el mismo objeto cada vez que pueda encontrar alguna variación usando shared_ptr o similar.
fuente
optional<EnabledContext> enabled(Context); optional<EnergisedContext> energised(EnabledContext);
lugar y usar la operación de composición monádica ('bind') en lugar de la aplicación de función.¿Qué hay de mover las declaraciones if a una función adicional que produce un resultado numérico o enum?
fuente
Algo como esto tal vez
o use excepciones
Usando excepciones también puede pasar datos.
fuente
ever
será muy infeliz ...No estoy particularmente en la forma de usar
break
oreturn
en tal caso. Dado que normalmente cuando nos enfrentamos a una situación así, generalmente es un método relativamente largo.Si tenemos múltiples puntos de salida, puede causar dificultades cuando queremos saber qué causará que se ejecute cierta lógica: normalmente seguimos subiendo bloques que encierran esa pieza de lógica, y los criterios de esos bloques que nos rodean nos dicen el situación:
Por ejemplo,
Al observar los bloques que encierran, es fácil descubrir que
myLogic()
solo suceden cuandoconditionA and conditionB and conditionC
es cierto.Se vuelve mucho menos visible cuando hay retornos tempranos:
Ya no podemos navegar hacia arriba
myLogic()
, mirando el bloque adjunto para descubrir la condición.Hay diferentes soluciones que usé. Aqui esta uno de ellos:
(Por supuesto, es bienvenido usar la misma variable para reemplazar todo
isA isB isC
).Tal enfoque al menos le dará al lector de código, que
myLogic()
se ejecuta cuandoisB && conditionC
. Se le da al lector una pista de que necesita buscar más a fondo lo que hará que B sea cierto.fuente
¿Qué hay sobre eso?
fuente
return condition;
, de lo contrario, creo que esto se puede mantener bien.Otro patrón útil si necesita diferentes pasos de limpieza dependiendo de dónde esté la falla:
fuente
No soy un programador de C ++ , así que no escribiré ningún código aquí, pero hasta ahora nadie ha mencionado una solución orientada a objetos. Así que aquí está mi suposición al respecto:
Tener una interfaz genérica que proporcione un método para evaluar una sola condición. Ahora puede usar una lista de implementaciones de esas condiciones en su objeto que contiene el método en cuestión. Usted itera sobre la lista y evalúa cada condición, posiblemente rompiendo temprano si falla una.
Lo bueno es que dicho diseño se adhiere muy bien al principio abierto / cerrado , porque puede agregar fácilmente nuevas condiciones durante la inicialización del objeto que contiene el método en cuestión. Incluso puede agregar un segundo método a la interfaz con el método para la evaluación de la condición que devuelve una descripción de la condición. Esto se puede utilizar para sistemas de autodocumentación.
La desventaja, sin embargo, es que hay un poco más de sobrecarga involucrada debido al uso de más objetos y la iteración sobre la lista.
fuente
Así es como lo hago.
fuente
Primero, un breve ejemplo para mostrar por qué
goto
no es una buena solución para C ++:Intente compilar esto en un archivo de objeto y vea qué sucede. Luego prueba el equivalente
do
+break
+while(0)
.Eso fue un aparte. El punto principal sigue.
Esos pequeños fragmentos de código a menudo requieren algún tipo de limpieza en caso de que falle toda la función. Esas limpiezas por lo general quieren que sucedan en el orden opuesto de los trozos mismos, ya que "desenrolla" el cálculo parcialmente terminado.
Una opción para obtener esta semántica es RAII ; ver la respuesta de @ utnapistim. C ++ garantiza que los destructores automáticos se ejecutan en el orden opuesto a los constructores, lo que naturalmente proporciona un "desenrollado".
Pero eso requiere muchas clases de RAII. A veces, una opción más simple es usar la pila:
...y así. Esto es fácil de auditar, ya que coloca el código "deshacer" al lado del código "hacer". La auditoría fácil es buena. También hace que el control de flujo sea muy claro. También es un patrón útil para C.
Puede requerir que las
calc
funciones tomen muchos argumentos, pero eso generalmente no es un problema si sus clases / estructuras tienen buena cohesión. (Es decir, las cosas que pertenecen juntas viven en un solo objeto, por lo que estas funciones pueden tomar punteros o referencias a un pequeño número de objetos y aún hacer mucho trabajo útil).fuente
Si el código tiene un bloque largo de sentencias if..else if..else, puede intentar reescribir todo el bloque con la ayuda de
Functors
ofunction pointers
. Puede que no sea la solución correcta siempre, pero a menudo lo es.http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html
fuente
Me sorprende la cantidad de respuestas diferentes que se presentan aquí. Pero, finalmente, en el código que tengo que cambiar (es decir, eliminar este
do-while(0)
truco o algo), hice algo diferente de cualquiera de las respuestas que se mencionan aquí y estoy confundido por qué nadie pensó esto. Esto es lo que hice:Código inicial:
Ahora:
¡Entonces, lo que se ha hecho aquí es que las cosas de acabado se han aislado en una función y las cosas se han vuelto tan simples y limpias de repente!
Pensé que valía la pena mencionar esta solución, así que proporcione aquí.
fuente
Consolidarlo en una sola
if
declaración:Este es el patrón utilizado en lenguajes como Java donde se eliminó la palabra clave goto.
fuente
true
. La evaluación de cortocircuito garantizaría que nunca ejecute cosas intermedias para las cuales no pasaron todas las pruebas de requisitos previos.(/*do other stuff*/, /*next condition*/)
, incluso podría formatearlo bien. Simplemente no esperes que a la gente le guste. Pero, honestamente, esto sólo sirve para demostrar que fue un error que Java fase quegoto
la declaración ...Si usa el mismo controlador de errores para todos los errores, y cada paso devuelve un valor bool que indica éxito:
(Similar a la respuesta de tyzoid, pero las condiciones son las acciones, y el && evita que ocurran acciones adicionales después de la primera falla).
fuente
¿Por qué no se respondió el método de marcado? Se utiliza desde hace siglos.
fuente