Invierta la declaración "if" para reducir el anidamiento

272

Cuando ejecuté ReSharper en mi código, por ejemplo:

    if (some condition)
    {
        Some code...            
    }

ReSharper me dio la advertencia anterior (Invierta la declaración "if" para reducir el anidamiento) y sugirió la siguiente corrección:

   if (!some condition) return;
   Some code...

Me gustaría entender por qué eso es mejor. Siempre pensé que usar "return" en medio de un método problemático, algo así como "goto".

Lea Cohen
fuente
1
Creo que la comprobación de excepciones y la devolución al principio están bien, pero cambiaría la condición para que esté buscando la excepción directamente en lugar de no algo (es decir, si (alguna condición) devuelve).
bruceatk
36
No, no hará nada por el rendimiento.
Seth Carnegie
3
Me sentiría tentado a lanzar una excepción ArgumentException si se pasaran datos incorrectos a mi método.
asawyer
1
@asawyer Sí, aquí hay una discusión lateral completa acerca de las funciones que son demasiado indulgentes con las entradas sin sentido, en lugar de usar un fallo de aserción. Escribir código sólido me abrió los ojos a esto. En cuyo caso, esto sería algo así ASSERT( exampleParam > 0 ).
Greg Hendershott
44
Las afirmaciones son para estado interno, no para parámetros. Comenzaría validando los parámetros, afirmando que su estado interno es correcto y luego realizando la operación. En una versión de lanzamiento, puede omitir las aserciones o asignarlas al cierre de un componente.
Simon Richter

Respuestas:

296

Un retorno en el medio del método no es necesariamente malo. Sería mejor regresar de inmediato si aclara la intención del código. Por ejemplo:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

En este caso, si _isDeades cierto, podemos salir inmediatamente del método. Podría ser mejor estructurarlo de esta manera:

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

Elegí este código del catálogo de refactorización . Esta refactorización específica se llama: Reemplazar condicional anidado con cláusulas de guardia.

jop
fuente
13
Este es un buen ejemplo! El código refactorizado se lee más como una declaración de caso.
Otros en
16
Probablemente solo sea una cuestión de gustos: sugiero cambiar el segundo y tercer "si" a "otro si" para aumentar aún más la legibilidad. Si uno pasa por alto la declaración de "retorno", aún estaría claro que el siguiente caso solo se verifica si el anterior falló, es decir, que el orden de las verificaciones es importante.
miedo el
2
En un ejemplo así de simple, la identificación está de acuerdo en que la segunda forma es mejor, pero solo porque es TAN obvio lo que está sucediendo.
Andrew Bullock, el
Ahora, ¿cómo tomamos ese bonito código escrito y evitamos que Visual Studio lo rompa y ponga los retornos en líneas separadas? Realmente me molesta que reformatee el código de esa manera. Hace este código muy legible feo.
Mark T
@ Mark T Hay configuraciones en Visual Studio para evitar que rompa ese código.
Aaron Smith
333

No solo es estética , sino que también reduce el nivel máximo de anidación dentro del método. Esto generalmente se considera como una ventaja porque hace que los métodos sean más fáciles de entender (y, de hecho, muchas herramientas de análisis estático proporcionan una medida de esto como uno de los indicadores de la calidad del código).

Por otro lado, también hace que su método tenga múltiples puntos de salida, algo que otro grupo de personas cree que es un no-no.

Personalmente, estoy de acuerdo con ReSharper y el primer grupo (en un lenguaje que tiene excepciones, me parece absurdo discutir "puntos de salida múltiples"; casi cualquier cosa puede arrojar, por lo que hay numerosos puntos de salida potenciales en todos los métodos).

En cuanto al rendimiento : ambas versiones deben ser equivalentes (si no en el nivel de IL, entonces ciertamente después de que el jitter haya terminado con el código) en todos los idiomas. Teóricamente, esto depende del compilador, pero prácticamente cualquier compilador ampliamente utilizado en la actualidad es capaz de manejar casos mucho más avanzados de optimización de código que este.

Jon
fuente
41
puntos de salida individuales? Quien lo necesita
sq33G
3
@ sq33G: Esa pregunta sobre SESE (y las respuestas, por supuesto) son fantásticas. Gracias por el enlace!
Jon
Sigo escuchando en las respuestas que hay algunas personas que abogan por puntos de salida únicos, pero nunca vi a alguien realmente abogar por eso, especialmente en lenguajes como C #.
Thomas Bonini
1
@AndreasBonini: La ausencia de prueba no es prueba de ausencia. :-)
Jon
Sí, por supuesto, me parece extraño que todos sientan la necesidad de decir que a algunas personas les gusta otro enfoque si esas personas no sienten la necesidad de decirlo ellos mismos =)
Thomas Bonini,
102

Este es un argumento un poco religioso, pero estoy de acuerdo con ReSharper en que debería preferir menos anidamiento. Creo que esto supera los aspectos negativos de tener múltiples rutas de retorno desde una función.

La razón clave para tener menos anidamiento es mejorar la legibilidad y la facilidad de mantenimiento del código . Recuerde que muchos otros desarrolladores necesitarán leer su código en el futuro, y el código con menos sangría es generalmente mucho más fácil de leer.

Las condiciones previas son un gran ejemplo de dónde está bien regresar temprano al comienzo de la función. ¿Por qué la legibilidad del resto de la función debería verse afectada por la presencia de una verificación de precondición?

En cuanto a los aspectos negativos de regresar varias veces de un método, los depuradores son bastante potentes ahora, y es muy fácil averiguar exactamente dónde y cuándo regresa una función en particular.

Tener múltiples retornos en una función no va a afectar el trabajo del programador de mantenimiento.

Mala legibilidad del código.

LeopardoPiel
fuente
2
Los retornos múltiples en una función afectan al programador de mantenimiento. Al depurar una función, buscando el valor de retorno falso, el mantenedor debe poner puntos de interrupción en todos los lugares que pueden regresar.
EvilTeach
10
Puse un punto de quiebre en la llave de apertura. Si avanza por la función, no solo podrá ver las comprobaciones que se realizan en orden, sino que también quedará muy claro qué comprobación se realizó en esa función.
John Dunagan
3
De acuerdo con John aquí ... No veo el problema en solo pasar por toda la función.
Nailer
55
@Nailer Muy tarde, estoy especialmente de acuerdo, porque si la función es demasiado grande para pasar, ¡de todos modos debería separarse en varias funciones!
Aidiakapi
1
De acuerdo con John también. Tal vez es una vieja escuela pensada para poner el punto de quiebre en la parte inferior, pero si tienes curiosidad por saber qué función está regresando, entonces estarás pasando por dicha función para ver por qué está devolviendo lo que está regresando de todos modos. Si solo quiere ver lo que devuelve, póngalo en el último soporte.
user441521
70

Como otros han mencionado, no debería haber un impacto en el rendimiento, pero hay otras consideraciones. Aparte de esas inquietudes válidas, esto también puede abrirlo en algunas circunstancias. Supongamos que se trata de un doublelugar:

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

Contrasta eso con la inversión aparentemente equivalente:

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

Entonces, en ciertas circunstancias, lo que parece estar correctamente invertido ifpodría no serlo.

Michael McGowan
fuente
44
También debe tenerse en cuenta que la solución rápida de resharper invierte exampleParam> 0 en exampleParam <0 no exampleParam <= 0, lo que me ha sorprendido.
nickd
1
Y esta es la razón por la que ese cheque debe ser adelantado y devuelto también dado que el desarrollador cree que un nan debería resultar en un rescate.
user441521
51

La idea de regresar solo al final de una función surgió de los días anteriores a que los idiomas admitieran excepciones. Permitió que los programas confiaran en poder poner el código de limpieza al final de un método, y luego estar seguro de que se llamaría y algún otro programador no ocultaría un retorno en el método que hizo que se omitiera el código de limpieza . El código de limpieza omitido podría provocar una pérdida de memoria o recursos.

Sin embargo, en un lenguaje que admite excepciones, no ofrece tales garantías. En un lenguaje que admite excepciones, la ejecución de cualquier declaración o expresión puede causar un flujo de control que hace que el método finalice. Esto significa que la limpieza debe hacerse mediante el uso de finalmente o mediante palabras clave.

De todos modos, digo que creo que muchas personas citan la directriz de 'solo retorno al final de un método' sin entender por qué fue algo bueno hacer, y que reducir la anidación para mejorar la legibilidad es probablemente un mejor objetivo.

Scott Langham
fuente
66
Acabas de descubrir por qué las excepciones son UglyAndEvil [tm] ... ;-) Las excepciones son disfraces de disfraces caros y elegantes.
EricSchaefer el
16
@Eric debe haber estado expuesto a un código excepcionalmente malo. Es bastante obvio cuando se usan incorrectamente, y generalmente le permiten escribir código de mayor calidad.
Robert Paulson
¡Esta es la respuesta que realmente estaba buscando! Aquí hay excelentes respuestas, pero la mayoría de ellas se centran en ejemplos, no en la razón real por la que nació esta recomendación.
Gustavo Mori
Esta es la mejor respuesta, ya que explica por qué todo este argumento surgió en primer lugar.
Buttle Butkus
If you've got deep nesting, maybe your function is trying to do too many things.=> esta no es una consecuencia correcta de tu frase anterior. Porque justo antes de decir que puede refactorizar el comportamiento A con el código C en el comportamiento A con el código D. el código D es más limpio, concedido, pero "demasiadas cosas" se refieren al comportamiento, que NO ha cambiado. Por lo tanto, no tiene ningún sentido con esta conclusión.
v.oddou
30

Me gustaría agregar que hay un nombre para esos si invertidos: cláusula de guardia. Lo uso siempre que puedo.

Odio leer el código donde hay si al principio, dos pantallas de código y nada más. Simplemente invierta si y regrese. De esa manera nadie perderá el tiempo desplazándose.

http://c2.com/cgi/wiki?GuardClause

Piotr Perak
fuente
2
Exactamente. Es más una refactorización. Ayuda a leer el código más fácilmente como mencionaste.
rpattabi
'regresar' no es suficiente y horrible para una cláusula de guardia. Lo haría: si (ex <= 0) arroja WrongParamValueEx ("[MethodName] Valor de parámetro de entrada incorrecto {0} ... y necesitará capturar la excepción y escribir en el registro de su aplicación
JohnJohnGa
3
@Juan. Tienes razón si esto es de hecho un error. Pero a menudo no lo es. Y en lugar de verificar algo en cada lugar donde se llama método, simplemente lo verifica y regresa sin hacer nada.
Piotr Perak
3
Odiaría leer un método que tenga dos pantallas de código, punto, ifso no. lol
jpmc26
1
Personalmente, prefiero los retornos tempranos exactamente por este motivo (también: evitando el anidamiento). Sin embargo, esto no está relacionado con la pregunta original sobre el rendimiento y probablemente debería ser un comentario.
Patrick M
22

No solo afecta la estética, sino que también evita el anidamiento de código.

En realidad, puede funcionar como una condición previa para garantizar que sus datos también sean válidos.

Rion Williams
fuente
18

Por supuesto, esto es subjetivo, pero creo que mejora mucho en dos puntos:

  • Ahora es inmediatamente obvio que su función no tiene nada que hacer si se conditionmantiene.
  • Mantiene el nivel de anidación bajo. Anidar perjudica la legibilidad más de lo que piensas.
Deestan
fuente
15

Múltiples puntos de retorno fueron un problema en C (y en menor medida en C ++) porque te obligaron a duplicar el código de limpieza antes de cada uno de los puntos de retorno. Con la recolección de basura, el try| finallyconstruir y usingbloquear, realmente no hay razón por la que debas tenerles miedo.

En última instancia, todo se reduce a lo que usted y sus colegas encuentran más fácil de leer.

Richard Poole
fuente
Esa no es la única razón. Hay una razón académica que se refiere al pseudocódigo que no tiene nada que ver con consideraciones prácticas como limpiar cosas. Esta razón académica tiene que ver con respetar las formas básicas de las construcciones imperativas. De la misma manera, no colocas salientes de bucle en el medio. De esta manera, los invariantes se pueden detectar rigurosamente y se puede probar el comportamiento. O la terminación puede ser probada.
v.oddou
1
noticias: de hecho, he encontrado una razón muy práctica por la cual las pausas y los retornos tempranos son malos, y es una consecuencia directa de lo que dije, el análisis estático. aquí tienes, guía de uso del compilador Intel C ++: d3f8ykwhia686p.cloudfront.net/1live/intel/… . Frase clave:a loop that is not vectorizable due to a second data-dependent exit
v.oddou
12

En cuanto al rendimiento, no habrá una diferencia notable entre los dos enfoques.

Pero la codificación es algo más que rendimiento. La claridad y la facilidad de mantenimiento también son muy importantes. Y, en casos como este donde no afecta el rendimiento, es lo único que importa.

Hay escuelas de pensamiento competidoras sobre qué enfoque es preferible.

Una vista es la que otros han mencionado: el segundo enfoque reduce el nivel de anidación, lo que mejora la claridad del código. Esto es natural en un estilo imperativo: cuando no tiene nada que hacer, es mejor que regrese temprano.

Otra opinión, desde la perspectiva de un estilo más funcional, es que un método debe tener solo un punto de salida. Todo en un lenguaje funcional es una expresión. Entonces, las declaraciones siempre deben tener cláusulas else. De lo contrario, la expresión if no siempre tendría un valor. Entonces, en el estilo funcional, el primer enfoque es más natural.

Jeffrey Sax
fuente
11

Las cláusulas de protección o las condiciones previas (como probablemente pueda ver) verifican si se cumple una determinada condición y luego interrumpe el flujo del programa. Son ideales para lugares donde realmente solo le interesa un resultado de una ifdeclaración. Entonces, en lugar de decir:

if (something) {
    // a lot of indented code
}

Invierte la condición y se rompe si se cumple esa condición inversa

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

return no está tan sucio como goto . Le permite pasar un valor para mostrar el resto de su código que la función no pudo ejecutarse.

Verá los mejores ejemplos de dónde se puede aplicar esto en condiciones anidadas:

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

vs:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

Encontrará que pocas personas argumentan que la primera es más limpia pero, por supuesto, es completamente subjetiva. A algunos programadores les gusta saber en qué condiciones está operando algo por sangrado, mientras que prefiero mantener el flujo del método lineal.

No voy a sugerir ni por un momento que las preconfiguraciones cambiarán tu vida o te acostarán, pero es posible que tu código sea un poco más fácil de leer.

Oli
fuente
66
Supongo que soy uno de los pocos entonces. Me resulta más fácil leer la primera versión. Los if anidados hacen que el árbol de decisión sea más obvio. Por otro lado, si hay un par de condiciones previas, estoy de acuerdo en que es mejor ponerlas todas en la parte superior de la función.
Otros en
@Otro lado: completamente de acuerdo. Los retornos escritos en serie hacen que su cerebro necesite serializar posibles caminos. Cuando el if-tree puede mapearse directamente en alguna lógica transitoria, por ejemplo, podría compilarse para lisp. pero la moda de devolución en serie requeriría un compilador mucho más difícil de hacer. El punto aquí obviamente no tiene nada que ver con este escenario hipotético, tiene que ver con el análisis de código, las posibilidades de optimización, las pruebas de corrección, las pruebas de terminación y la detección de invariantes.
v.oddou
1
De ninguna manera a nadie le resulta más fácil leer el código con más nidos. Cuanto más bloqueado sea como algo, más fácil es leer de un vistazo. No hay forma de que el primer ejemplo aquí sea más fácil de leer y solo incluye 2 nidos cuando la mayoría de los códigos como este en el mundo real son más profundos y con cada nido requiere más poder cerebral para seguir.
user441521
1
Si el anidamiento fue el doble de profundo, ¿cuánto tiempo le tomaría responder a la pregunta: "¿Cuándo 'haces algo más'?" Creo que sería difícil responderlo sin un bolígrafo y papel. Pero podría responderlo con bastante facilidad si todo estuviera en guardia.
David Storfer
9

Aquí se hacen varios puntos buenos, pero también pueden ser ilegibles múltiples puntos de retorno , si el método es muy largo. Dicho esto, si va a utilizar múltiples puntos de retorno, asegúrese de que su método sea corto, de lo contrario, se puede perder la bonificación de legibilidad de múltiples puntos de retorno.

Jon Limjap
fuente
8

El rendimiento se divide en dos partes. Tiene rendimiento cuando el software está en producción, pero también desea tener rendimiento durante el desarrollo y la depuración. Lo último que quiere un desarrollador es "esperar" algo trivial. Al final, compilar esto con la optimización habilitada dará como resultado un código similar. Por lo tanto, es bueno conocer estos pequeños trucos que dan resultado en ambos escenarios.

El caso en la pregunta es claro, ReSharper es correcto. En lugar de anidar ifdeclaraciones y crear un nuevo alcance en el código, está estableciendo una regla clara al comienzo de su método. Aumenta la legibilidad, será más fácil de mantener y reduce la cantidad de reglas que uno debe examinar para encontrar a dónde quiere ir.

Ryan Ternier
fuente
7

Personalmente prefiero solo 1 punto de salida. Es fácil de lograr si mantiene sus métodos cortos y al grano, y proporciona un patrón predecible para la próxima persona que trabaje en su código.

p.ej.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

Esto también es muy útil si solo desea verificar los valores de ciertas variables locales dentro de una función antes de que salga. Todo lo que necesita hacer es colocar un punto de interrupción en el retorno final y se garantiza que lo alcanzará (a menos que se produzca una excepción).

ilitirit
fuente
44
bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} Allí, lo arregló para usted. :)
tchen
1
Este ejemplo es muy básico y pierde el punto de este patrón. Tenga alrededor de otras 4 comprobaciones dentro de esta función y luego díganos que es más legible, porque no lo es.
user441521
5

Muchas buenas razones sobre cómo se ve el código . ¿Pero qué hay de los resultados? ?

Echemos un vistazo a un código C # y su forma compilada IL:


using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

Este fragmento simple se puede compilar. Puede abrir el archivo .exe generado con ildasm y verificar cuál es el resultado. No publicaré todo lo relacionado con el ensamblador, pero describiré los resultados.

El código IL generado hace lo siguiente:

  1. Si la primera condición es falsa, salta al código donde está la segunda.
  2. Si es verdad salta a la última instrucción. (Nota: la última instrucción es un retorno).
  3. En la segunda condición, sucede lo mismo después de calcular el resultado. Compare y: llegó a Console.WriteLine si es falso o hasta el final si esto es cierto.
  4. Imprime el mensaje y devuélvelo.

Entonces parece que el código saltará al final. ¿Qué pasa si hacemos un normal si con código anidado?

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

Los resultados son bastante similares en las instrucciones de IL. La diferencia es que antes había saltos por condición: si es falso, vaya al siguiente fragmento de código, si es verdadero, vaya al final. Y ahora el código IL fluye mejor y tiene 3 saltos (el compilador optimizó esto un poco): 1. Primer salto: cuando Longitud es 0 a una parte donde el código salta nuevamente (Tercer salto) hasta el final. 2. Segundo: en medio de la segunda condición para evitar una instrucción. 3. Tercero: si la segunda condición es falsa, salta al final.

De todos modos, el contador del programa siempre saltará.

graffic
fuente
55
Esta es una buena información, pero personalmente no podría importarme menos si la IL "fluye mejor" porque las personas que van a administrar el código no verán ninguna IL
JohnIdol
1
Realmente no puede anticipar cómo funcionará la optimización en la próxima actualización del compilador de C # en comparación con lo que ve hoy. Personalmente, no pasaría CUALQUIER tiempo tratando de modificar el código C # para producir un resultado diferente en la IL, a menos que las pruebas muestren que tienes un problema de rendimiento GRAVE en algún bucle cerrado en algún lugar y te has quedado sin otras ideas . Incluso entonces, pensaría más en otras opciones. En la misma línea, dudo que tenga alguna idea de qué tipo de optimizaciones está haciendo el compilador JIT con el IL que se alimenta para cada plataforma ...
Craig
5

En teoría, la inversión ifpodría conducir a un mejor rendimiento si aumenta la tasa de aciertos de predicción de rama. En la práctica, creo que es muy difícil saber exactamente cómo se comportará la predicción de bifurcación, especialmente después de la compilación, por lo que no lo haría en mi desarrollo diario, excepto si estoy escribiendo código de ensamblaje.

Más sobre predicción de ramas aquí .

jfg956
fuente
4

Eso es simplemente controvertido. No existe un "acuerdo entre los programadores" sobre la cuestión del retorno temprano. Siempre es subjetivo, que yo sepa.

Es posible hacer un argumento de rendimiento, ya que es mejor tener condiciones escritas para que con frecuencia sean ciertas; También se puede argumentar que es más claro. Por otro lado, crea pruebas anidadas.

No creo que obtenga una respuesta concluyente a esta pregunta.

relajarse
fuente
No entiendo tu argumento de rendimiento. Las condiciones son booleanas así que sea cual sea el resultado, siempre hay dos resultados ... No veo por qué invertir una declaración (o no) cambiaría un resultado. (A menos que esté diciendo que agregar un "NO" a la condición agrega una cantidad medible de procesamiento ...
Oli
2
La optimización funciona así: si se asegura de que el caso más común esté en el bloque if en lugar del bloque else, entonces la CPU generalmente ya tendrá las declaraciones del bloque if cargadas en su canalización. Si la condición devuelve falso, la CPU necesita vaciar su canalización y ...
Otherside
También me gusta hacer lo que dice el otro lado solo para facilitar la lectura, odio tener casos negativos en el bloque "luego" en lugar de "otro". Tiene sentido hacer esto incluso sin saber o considerar lo que está haciendo la CPU
Andrew Bullock, el
3

Ya hay muchas respuestas perspicaces, pero aún así, me gustaría dirigirme a una situación ligeramente diferente: en lugar de una condición previa, que debería ponerse sobre una función, piense en una inicialización paso a paso, donde tiene que verificar cada paso para tener éxito y luego continuar con el siguiente. En este caso, no puede verificar todo en la parte superior.

Encontré mi código realmente ilegible al escribir una aplicación host ASIO con ASIOSDK de Steinberg, ya que seguí el paradigma de anidamiento. Fue como ocho niveles de profundidad, y no puedo ver una falla de diseño allí, como lo mencionó Andrew Bullock anteriormente. Por supuesto, podría haber empaquetado algún código interno para otra función, y luego anidar los niveles restantes allí para hacerlo más legible, pero esto me parece bastante aleatorio.

Al reemplazar la anidación con cláusulas de protección, incluso descubrí una idea errónea mía con respecto a una parte del código de limpieza que debería haber ocurrido mucho antes dentro de la función en lugar de al final. Con ramas anidadas, nunca habría visto eso, incluso se podría decir que condujeron a mi error.

Entonces, esta podría ser otra situación en la que los if invertidos pueden contribuir a un código más claro.

Ingo Schalk-Schupp
fuente
3

Evitar múltiples puntos de salida puede conducir a ganancias de rendimiento. No estoy seguro acerca de C #, pero en C ++ la optimización del valor de retorno con nombre (Copy Elision, ISO C ++ '03 12.8 / 15) depende de tener un único punto de salida. Esta optimización evita que la copia construya su valor de retorno (en su ejemplo específico no importa). Esto podría generar ganancias considerables en el rendimiento en bucles cerrados, ya que está guardando un constructor y un destructor cada vez que se invoca la función.

Pero para el 99% de los casos, guardar las llamadas adicionales de constructor y destructor no vale la pérdida de legibilidad que ifintroducen los bloques anidados (como otros han señalado).

shibumi
fuente
2
ahí. Me tomó 3 largas lecturas de decenas de respuestas en 3 preguntas que hacen la misma cosa (2 de las cuales están congeladas), para finalmente encontrar a alguien mencionando NRVO. caramba ... gracias.
v.oddou
2

Es una cuestión de opinión.

Mi enfoque normal sería evitar ifs de una sola línea y retornos en medio de un método.

No querrá líneas como sugiere en todas partes en su método, pero hay algo que decir para verificar un montón de suposiciones en la parte superior de su método, y solo hacer su trabajo real si todas pasan.

Colin Pickard
fuente
2

En mi opinión, el retorno temprano está bien si solo está volviendo vacío (o algún código de retorno inútil que nunca verificará) y podría mejorar la legibilidad porque evita anidar y al mismo tiempo hace explícito que su función está hecha.

Si realmente está devolviendo un returnValue, la anidación suele ser una mejor manera de hacerlo porque devuelve su returnValue solo en un lugar (al final, duh), y puede hacer que su código sea más fácil de mantener en muchos casos.

JohnIdol
fuente
1

No estoy seguro, pero creo que R # intenta evitar saltos lejanos. Cuando tienes IF-ELSE, el compilador hace algo como esto:

Condición falsa -> salto lejano a etiqueta_condición_ falso

etiqueta_condición_verdadera: instrucción1 ... instrucción_n

false_condition_label: instrucción1 ... instrucción_n

bloque final

Si la condición es verdadera, no hay salto ni despliegue de caché L1, pero saltar a false_condition_label puede estar muy lejos y el procesador debe desplegar su propio caché. Sincronizar caché es costoso. R # intenta reemplazar saltos lejanos en saltos cortos y en este caso hay una mayor probabilidad de que todas las instrucciones ya estén en caché.

Marcin Gołembiewski
fuente
0

Creo que depende de lo que prefiera, como se mencionó, no hay ningún acuerdo general afaik. Para reducir la molestia, puede reducir este tipo de advertencia a "Sugerencia"

Bluenuance
fuente
0

Mi idea es que el retorno "en medio de una función" no debería ser tan "subjetivo". La razón es bastante simple, tome este código:

    función do_something (datos) {

      if (! is_valid_data (data)) 
            falso retorno;


       do_something_that_take_an_hour (data);

       istance = new object_with_very_painful_constructor (datos);

          if (istance no es válido) {
               mensaje de error( );
                regreso ;

          }
       connect_to_database ();
       get_some_other_data ();
       regreso;
    }

Tal vez el primer "retorno" no sea TAN intuitivo, pero eso realmente está ahorrando. Hay demasiadas "ideas" sobre códigos limpios, que simplemente necesitan más práctica para perder sus malas ideas "subjetivas".

Joshi Spawnbrood
fuente
Con un idioma de excepción no tienes estos problemas.
user441521
0

Hay varias ventajas para este tipo de codificación, pero para mí la gran victoria es que, si puede regresar rápidamente, puede mejorar la velocidad de su aplicación. Es decir, sé que debido a la Precondición X, puedo regresar rápidamente con un error. Esto elimina primero los casos de error y reduce la complejidad de su código. En muchos casos, debido a que la tubería de la CPU ahora puede estar más limpia, puede detener los bloqueos o interruptores de la tubería. En segundo lugar, si está en un bucle, romper o regresar rápidamente puede ahorrarle mucha CPU. Algunos programadores usan invariantes de bucle para hacer este tipo de salida rápida, pero en esto puede romper su canal de CPU e incluso crear problemas de búsqueda de memoria y significa que la CPU necesita cargarse desde el caché externo. Pero básicamente creo que deberías hacer lo que pretendías, es decir, finalizar el ciclo o la función no crear una ruta de código compleja solo para implementar alguna noción abstracta de código correcto. Si la única herramienta que tienes es un martillo, entonces todo parece un clavo.

David Allan Finch
fuente
Al leer la respuesta de graffic, parece que el código anidado fue optimizado por el compilador de manera que el código ejecutado es más rápido que el uso de múltiples retornos. Sin embargo, incluso si los retornos múltiples fueran más rápidos, esta optimización probablemente no acelerará tanto su aplicación ... :)
Hangy
1
No estoy de acuerdo: la primera ley trata de obtener el algoritmo correcto. Pero todos somos ingenieros, lo que se trata de resolver el problema correcto con los recursos que tenemos y hacer en el presupuesto disponible. Una aplicación que es demasiado lenta no es adecuada para su propósito.
David Allan Finch