¿Hay buenas razones por las que es una mejor práctica tener una sola declaración de retorno en una función?
¿O está bien regresar de una función tan pronto como sea lógicamente correcto hacerlo, lo que significa que puede haber muchas declaraciones de retorno en la función?
language-agnostic
coding-style
Tim Post
fuente
fuente
Respuestas:
A menudo tengo varias declaraciones al comienzo de un método para regresar para situaciones "fáciles". Por ejemplo, esto:
... puede hacerse más legible (en mi humilde opinión) de esta manera:
Entonces sí, creo que está bien tener múltiples "puntos de salida" de una función / método.
fuente
DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Nadie ha mencionado o citado Code Complete así que lo haré.
17.1 regreso
Minimice la cantidad de devoluciones en cada rutina . Es más difícil entender una rutina si, al leerla en la parte inferior, no eres consciente de la posibilidad de que regrese en algún lugar arriba.
Use un retorno cuando mejore la legibilidad . En ciertas rutinas, una vez que conoce la respuesta, desea devolverla a la rutina de llamada inmediatamente. Si la rutina se define de tal manera que no requiere ninguna limpieza, no volver inmediatamente significa que debe escribir más código.
fuente
Diría que sería increíblemente imprudente decidir arbitrariamente contra múltiples puntos de salida, ya que he encontrado que la técnica es útil en la práctica una y otra vez , de hecho, a menudo he refactorizado el código existente en múltiples puntos de salida para mayor claridad. Podemos comparar los dos enfoques así:
Compare esto con el código donde se permiten múltiples puntos de salida : -
Creo que esto último es considerablemente más claro. Hasta donde puedo decir, la crítica de múltiples puntos de salida es un punto de vista bastante arcaico en estos días.
fuente
Actualmente estoy trabajando en una base de código donde dos de las personas que trabajan en ella se suscriben ciegamente a la teoría del "punto único de salida" y puedo decirles que, por experiencia, es una práctica horrible horrible. Hace que el código sea extremadamente difícil de mantener y le mostraré por qué.
Con la teoría del "punto único de salida", inevitablemente terminas con un código que se ve así:
Esto no solo hace que el código sea muy difícil de seguir, sino que ahora diga que debe volver y agregar una operación entre 1 y 2. Debe sangrar casi toda la función de maldición, y buena suerte asegurándose de que todo sus condiciones if / else y llaves se combinan correctamente.
Este método hace que el mantenimiento del código sea extremadamente difícil y propenso a errores.
fuente
La programación estructurada dice que solo debe tener una declaración de retorno por función. Esto es para limitar la complejidad. Muchas personas, como Martin Fowler, argumentan que es más simple escribir funciones con múltiples declaraciones de retorno. Presenta este argumento en el clásico libro de refactorización que escribió. Esto funciona bien si sigues sus otros consejos y escribes pequeñas funciones. Estoy de acuerdo con este punto de vista y solo los puristas estrictos de programación estructurada se adhieren a declaraciones de retorno individuales por función.
fuente
GOTO
para mover el flujo de control incluso cuando existía la función. Nunca dice "nunca usarGOTO
".Como Kent Beck señala cuando discute cláusulas de guardia en Patrones de implementación que hacen que una rutina tenga un único punto de entrada y salida ...
Encuentro que una función escrita con cláusulas de guarda es mucho más fácil de seguir que una larga serie de
if then else
declaraciones anidadas .fuente
En una función que no tiene efectos secundarios, no hay una buena razón para tener más de un retorno y debe escribirlos en un estilo funcional. En un método con efectos secundarios, las cosas son más secuenciales (indexadas en el tiempo), por lo que debe escribir en un estilo imperativo, utilizando la instrucción return como comando para detener la ejecución.
En otras palabras, cuando sea posible, favorezca este estilo.
Más allá de esto
Si te encuentras escribiendo varias capas de condiciones anidadas, probablemente hay una forma de refactorizar eso, usando la lista de predicados, por ejemplo. Si encuentra que sus ifs y elses están muy separados sintácticamente, es posible que desee dividirlo en funciones más pequeñas. Un bloque condicional que abarca más que una pantalla llena de texto es difícil de leer.
No hay una regla estricta y rápida que se aplique a todos los idiomas. Algo así como tener una sola declaración de devolución no hará que su código sea bueno. Pero un buen código tenderá a permitirle escribir sus funciones de esa manera.
fuente
Lo he visto en los estándares de codificación para C ++ que eran una suspensión de C, como si no tuviera RAII u otra administración automática de memoria, entonces tiene que limpiar para cada devolución, lo que significa cortar y pegar de la limpieza o un goto (lógicamente lo mismo que 'finalmente' en los lenguajes administrados), los cuales se consideran de mala forma. Si sus prácticas son utilizar punteros inteligentes y colecciones en C ++ u otro sistema de memoria automático, entonces no hay una razón sólida para ello, y se trata de la legibilidad y más de una llamada de juicio.
fuente
auto_ptr
, puede usar punteros simples en paralelo. Aunque sería extraño escribir código 'optimizado' con un compilador no optimizador en primer lugar.try
...finally
en Java) y necesita hacer un mantenimiento de recursos, puede hacerlo con un solo volver al final de un método. Antes de hacer esto, debería considerar seriamente refactorizar el código para deshacerse de la situación.Me inclino por la idea de que las declaraciones de retorno en el medio de la función son malas. Puede usar los retornos para construir algunas cláusulas de protección en la parte superior de la función y, por supuesto, decirle al compilador qué devolver al final de la función sin problemas, pero los retornos en el medio de la función pueden ser fáciles de omitir y pueden hace que la función sea más difícil de interpretar.
fuente
Si , hay:
La pregunta a menudo se plantea como una falsa dicotomía entre múltiples retornos o declaraciones if profundamente anidadas. Casi siempre hay una tercera solución que es muy lineal (sin anidamiento profundo) con un solo punto de salida.
Actualización : Aparentemente, las pautas de MISRA también promueven la salida única .
Para ser claros, no digo que siempre sea incorrecto tener retornos múltiples. Pero dadas soluciones equivalentes, hay muchas buenas razones para preferir la que tiene un solo retorno.
fuente
Contract.Ensures
con múltiples puntos de devolución.goto
para llegar a un código de limpieza común, entonces probablemente haya simplificado la función para que haya un únicoreturn
al final del código de limpieza. Entonces podría decir que resolvió el problemagoto
, pero yo diría que lo resolvió simplificando a uno soloreturn
.Tener un único punto de salida proporciona una ventaja en la depuración, ya que le permite establecer un único punto de interrupción al final de una función para ver qué valor realmente se devolverá.
fuente
En general trato de tener solo un único punto de salida de una función. Hay veces, sin embargo, que hacerlo termina creando un cuerpo de función más complejo de lo necesario, en cuyo caso es mejor tener múltiples puntos de salida. Realmente tiene que ser un "juicio" basado en la complejidad resultante, pero el objetivo debe ser la menor cantidad de puntos de salida posible sin sacrificar la complejidad y la comprensibilidad.
fuente
No, porque ya no vivimos en la década de 1970 . Si su función es lo suficientemente larga como para que los retornos múltiples sean un problema, es demasiado larga.
(Aparte del hecho de que cualquier función de varias líneas en un idioma con excepciones tendrá múltiples puntos de salida de todos modos).
fuente
Mi preferencia sería una salida única a menos que realmente complique las cosas. He descubierto que, en algunos casos, múltiples puntos existentes pueden enmascarar otros problemas de diseño más significativos:
Al ver este código, inmediatamente preguntaría:
Dependiendo de las respuestas a estas preguntas, podría ser que
En los dos casos anteriores, el código probablemente puede ser modificado con una afirmación para garantizar que 'foo' nunca sea nulo y que las personas que llaman relevantes cambien.
Hay otras dos razones (creo que específicas para el código C ++) donde existen múltiples pueden tener un efecto negativo efecto . Son el tamaño del código y las optimizaciones del compilador.
Un objeto C ++ que no sea POD en alcance a la salida de una función tendrá su destructor llamado. Cuando hay varias declaraciones de devolución, puede darse el caso de que haya diferentes objetos en el alcance y, por lo tanto, la lista de destructores para llamar será diferente. Por lo tanto, el compilador necesita generar código para cada declaración de retorno:
Si el tamaño del código es un problema, entonces puede ser algo que valga la pena evitar.
El otro problema se relaciona con la "Optimización del valor de retorno con nombre" (también conocido como Copy Elision, ISO C ++ '03 12.8 / 15). C ++ permite que una implementación omita llamar al constructor de copias si puede:
Simplemente tomando el código como está, el objeto 'a1' se construye en 'foo' y luego se llamará a su construcción de copia para construir 'a2'. Sin embargo, copiar elisión permite al compilador construir 'a1' en el mismo lugar en la pila que 'a2'. Por lo tanto, no es necesario "copiar" el objeto cuando la función vuelve.
Múltiples puntos de salida complican el trabajo del compilador al tratar de detectar esto, y al menos para una versión relativamente reciente de VC ++, la optimización no tuvo lugar donde el cuerpo de la función tenía múltiples retornos. Consulte Optimización del valor de retorno con nombre en Visual C ++ 2005 para obtener más detalles.
fuente
throw new ArgumentNullException()
en C # en este caso), realmente me gustaron sus otras consideraciones, todas son válidas para mí y podrían ser críticas en algunos contextos de nicho.foo
se está probando no tiene nada que ver con el tema, que es si hacerif (foo == NULL) return; dowork;
oif (foo != NULL) { dowork; }
Tener un único punto de salida reduce la Complejidad Ciclomática y, por lo tanto, en teoría , reduce la probabilidad de que introduzcas errores en tu código cuando lo cambies. Sin embargo, la práctica tiende a sugerir que se necesita un enfoque más pragmático. Por lo tanto, tiendo a apuntar a tener un único punto de salida, pero permitir que mi código tenga varios si eso es más legible.
fuente
Me obligo a usar solo una
return
declaración, ya que en cierto sentido generará olor a código. Dejame explicar:(Las condiciones son arbitrarias ...)
Cuantas más condiciones, cuanto más grande sea la función, más difícil será leerla. Entonces, si está en sintonía con el olor del código, se dará cuenta y querrá refactorizar el código. Dos posibles soluciones son:
Devoluciones múltiples
Funciones separadas
Por supuesto, es más largo y un poco desordenado, pero en el proceso de refactorizar la función de esta manera, hemos
fuente
Diría que debe tener tantos como sea necesario, o cualquiera que haga que el código sea más limpio (como las cláusulas de protección ).
Personalmente, nunca he escuchado / visto ninguna "mejor práctica" que diga que debe tener una sola declaración de devolución.
En su mayor parte, tiendo a salir de una función lo antes posible en función de una ruta lógica (las cláusulas de protección son un excelente ejemplo de esto).
fuente
Creo que las devoluciones múltiples suelen ser buenas (en el código que escribo en C #). El estilo de retorno único es un remanente de C. Pero probablemente no esté codificando en C.
No hay una ley que requiera solo un punto de salida para un método en todos los lenguajes de programación . Algunas personas insisten en la superioridad de este estilo, y a veces lo elevan a una "regla" o "ley", pero esta creencia no está respaldada por ninguna evidencia o investigación.
Más de un estilo de retorno puede ser un mal hábito en el código C, donde los recursos deben ser desasignados explícitamente, pero los lenguajes como Java, C #, Python o JavaScript que tienen construcciones como la recolección de basura automática y los
try..finally
bloques (yusing
bloques en C # ), y este argumento no se aplica: en estos lenguajes, es muy poco frecuente que se necesite la desasignación de recursos manual centralizada.Hay casos en que un solo retorno es más legible, y casos en que no lo es. Vea si reduce el número de líneas de código, aclara la lógica o reduce el número de llaves y sangrías o variables temporales.
Por lo tanto, use tantos retornos como se adapte a sus sensibilidades artísticas, porque es un problema de diseño y legibilidad, no técnico.
He hablado sobre esto con mayor detalle en mi blog .
fuente
Hay cosas buenas que decir sobre tener un único punto de salida, así como hay cosas malas que decir sobre la inevitable programación de "flecha" que resulta.
Si uso múltiples puntos de salida durante la validación de entrada o la asignación de recursos, trato de poner todas las 'salidas de error' de manera muy visible en la parte superior de la función.
Tanto el artículo de Spartan Programming de "SSDSLPedia" como el artículo de punto de salida de función única de "Wiki Pattern Repository's Wiki" tienen algunos argumentos perspicaces al respecto. Además, por supuesto, hay que considerar esta publicación.
Si realmente desea un único punto de salida (en cualquier lenguaje no habilitado para excepciones), por ejemplo, para liberar recursos en un solo lugar, encuentro que la aplicación cuidadosa de goto es buena; vea, por ejemplo, este ejemplo bastante artificial (comprimido para guardar el espacio en pantalla):
Personalmente, en general, no me gusta la programación de flechas más de lo que no me gustan los múltiples puntos de salida, aunque ambos son útiles cuando se aplican correctamente. Lo mejor, por supuesto, es estructurar su programa para que no lo requiera. Desglosar su función en varios fragmentos generalmente ayuda :)
Aunque al hacerlo, encuentro que de todos modos termino con múltiples puntos de salida como en este ejemplo, donde alguna función más grande se ha dividido en varias funciones más pequeñas:
Dependiendo del proyecto o las pautas de codificación, la mayoría del código de la placa de caldera podría reemplazarse por macros. Como nota al margen, desglosarlo de esta manera hace que las funciones g0, g1, g2 sean muy fáciles de probar individualmente.
Obviamente, en un lenguaje habilitado para excepciones y OO, no usaría sentencias if como esa (o en absoluto, si pudiera salirse con la suya con poco esfuerzo), y el código sería mucho más simple. Y no flecha. Y la mayoría de los retornos no finales probablemente serían excepciones.
En breve;
fuente
Conoces el dicho: la belleza está en los ojos del espectador .
Algunas personas juran por NetBeans y algunas por IntelliJ IDEA , algunas por Python y algunas por PHP .
En algunas tiendas, podría perder su trabajo si insiste en hacer esto:
La pregunta es sobre visibilidad y mantenibilidad.
Soy adicto al uso de álgebra booleana para reducir y simplificar la lógica y el uso de máquinas de estado. Sin embargo, había colegas anteriores que creían que mi uso de "técnicas matemáticas" en la codificación no era adecuado, porque no sería visible y mantenible. Y eso sería una mala práctica. Lo siento, las técnicas que utilizo son muy visibles y mantenibles para mí, porque cuando vuelva al código seis meses después, entendería el código claramente en lugar de ver un desorden de espagueti proverbial.
Hola amigo (como solía decir un cliente anterior) haz lo que quieras siempre y cuando sepas cómo solucionarlo cuando necesito que lo arregles.
Recuerdo que hace 20 años, un colega mío fue despedido por emplear lo que hoy se llamaría una estrategia de desarrollo ágil . Tenía un plan incremental meticuloso. Pero su gerente le gritaba: "¡No puedes liberar gradualmente funciones para los usuarios! Debes seguir con la cascada ". Su respuesta al gerente fue que el desarrollo incremental sería más preciso para las necesidades del cliente. Él creía en el desarrollo para las necesidades de los clientes, pero el gerente creía en la codificación de "requisitos del cliente".
Con frecuencia somos culpables de romper los límites de normalización de datos, MVP y MVC . Estamos en línea en lugar de construir una función. Tomamos atajos.
Personalmente, creo que PHP es una mala práctica, pero qué sé. Todos los argumentos teóricos se reducen a tratar de cumplir un conjunto de reglas.
Todas las demás reglas se desvanecen en el fondo. Y, por supuesto, esta regla nunca se desvanece:
fuente
Me inclino hacia el uso de cláusulas de guardia para regresar temprano y, de lo contrario, salir al final de un método. La regla de entrada y salida única tiene un significado histórico y fue particularmente útil cuando se trataba de código heredado que corría a 10 páginas A4 para un único método C ++ con múltiples retornos (y muchos defectos). Más recientemente, una buena práctica aceptada es mantener los métodos pequeños, lo que hace que las salidas múltiples sean menos una impedancia para la comprensión. En el siguiente ejemplo de Kronoz copiado desde arriba, la pregunta es ¿qué ocurre en // Resto del código ... ?:
Me doy cuenta de que el ejemplo es algo ingenioso, pero me vería tentado a refactorizar el bucle foreach en una declaración LINQ que luego podría considerarse una cláusula de guardia. Una vez más, en un ejemplo artificial la intención del código no es aparente y someFunction () puede tener algún otro efecto secundario o el resultado puede ser utilizado en la // Resto del código ... .
Dando la siguiente función refactorizada:
fuente
null
lugar de lanzar una excepción que indica que el argumento no es aceptado?Una buena razón por la que puedo pensar es para el mantenimiento del código: tiene un único punto de salida. Si desea cambiar el formato del resultado, ..., es mucho más sencillo de implementar. Además, para la depuración, puede pegar un punto de interrupción allí :)
Dicho esto, una vez tuve que trabajar en una biblioteca donde los estándares de codificación imponían 'una declaración de retorno por función', y me pareció bastante difícil. Escribo muchos códigos de cómputos numéricos, y a menudo hay 'casos especiales', por lo que el código terminó siendo bastante difícil de seguir ...
fuente
Múltiples puntos de salida están bien para funciones lo suficientemente pequeñas, es decir, una función que se puede ver en la longitud de una pantalla en su totalidad. Si una función larga también incluye múltiples puntos de salida, es una señal de que la función se puede cortar aún más.
Dicho esto, evito las funciones de salida múltiple a menos que sea absolutamente necesario . He sentido el dolor de los errores que se deben a un retorno perdido en una línea oscura en funciones más complejas.
fuente
He trabajado con terribles estándares de codificación que forzaron una única ruta de salida en ti y el resultado casi siempre es un espagueti desestructurado si la función es cualquier cosa menos trivial: terminas con muchos descansos y continúa que solo se interponen en el camino.
fuente
if
declaración en frente de cada llamada al método que devuelve éxito o no :(El único punto de salida, todo lo demás igual, hace que el código sea mucho más legible. Pero hay una trampa: construcción popular
es falso, "res =" no es mucho mejor que "return". Tiene una sola declaración de retorno, pero múltiples puntos donde la función realmente termina.
Si tiene una función con múltiples retornos (o "res =" s), a menudo es una buena idea dividirla en varias funciones más pequeñas con un único punto de salida.
fuente
Mi política habitual es tener solo una declaración de devolución al final de una función, a menos que la complejidad del código se reduzca considerablemente al agregar más. De hecho, soy un fanático de Eiffel, que aplica la única regla de retorno al no tener una declaración de retorno (solo hay una variable de 'resultado' creada automáticamente para colocar su resultado).
Ciertamente, hay casos en los que el código puede hacerse más claro con múltiples retornos que la versión obvia sin ellos. Se podría argumentar que se necesita más retrabajo si tiene una función que es demasiado compleja para ser comprensible sin múltiples declaraciones de retorno, pero a veces es bueno ser pragmático sobre tales cosas.
fuente
Si termina con más de unas pocas devoluciones, puede haber algo mal con su código. De lo contrario, estaría de acuerdo en que a veces es bueno poder regresar de múltiples lugares en una subrutina, especialmente cuando hace que el código sea más limpio.
Perl 6: mal ejemplo
estaría mejor escrito así
Perl 6: buen ejemplo
Tenga en cuenta que esto es solo un ejemplo rápido
fuente
Voto por Single return al final como guía. Esto ayuda a un manejo común de limpieza de código ... Por ejemplo, eche un vistazo al siguiente código ...
fuente
Esta es probablemente una perspectiva inusual, pero creo que cualquiera que crea que se deben favorecer las declaraciones de devolución múltiples nunca ha tenido que usar un depurador en un microprocesador que solo admite 4 puntos de interrupción de hardware. ;-)
Si bien los problemas del "código de flecha" son completamente correctos, un problema que parece desaparecer cuando se utilizan múltiples declaraciones de retorno es la situación en la que está utilizando un depurador. No tiene una posición general adecuada para poner un punto de interrupción para garantizar que verá la salida y, por lo tanto, la condición de retorno.
fuente
Cuantas más declaraciones de devolución tenga en una función, mayor será la complejidad en ese método. Si se pregunta si tiene demasiadas declaraciones de retorno, es posible que desee preguntarse si tiene demasiadas líneas de código en esa función.
Pero no, no hay nada de malo en una / varias declaraciones de devolución. En algunos lenguajes, es una mejor práctica (C ++) que en otros (C).
fuente