visual studio mega mac full code c# resharper

c# - studio - Invierta la instrucción "if" para reducir la anidación



resharper visual studio 2017 full mega (24)

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

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

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

ReSharper me dio la advertencia anterior (invierta la instrucción "si" para reducir la anidación), y sugirió la siguiente corrección:

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

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


Cláusulas de previsión o precondiciones (como probablemente pueda ver) verifique si se cumple una determinada condición y luego rompe el flujo del programa. Son geniales para lugares en los que realmente solo está interesado en un resultado de una declaración if . 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 invertida

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

Verá los mejores ejemplos de dónde esto se puede aplicar 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ás pocas personas que argumentan que el primero es más limpio, pero por supuesto, es completamente subjetivo. A algunos programadores les gusta saber con qué condiciones está funcionando algo por indentación, mientras que prefiero que el flujo de método sea lineal.

No voy a sugerir por un momento que los precon cambiarán tu vida o te harán sentir bien pero quizás encuentres tu código un poco más fácil de leer.


Como otros han mencionado, no debería haber un golpe de rendimiento, pero hay otras consideraciones. Aparte de esas preocupaciones válidas, esto también puede hacer que te atrapen las trampas en algunas circunstancias. Supongamos que está tratando con un double lugar:

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

Contraste 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, puede no serlo.


Creo que depende de lo que prefiera, como se mencionó, no hay un acuerdo general afaik. Para reducir el fastidio, puede reducir este tipo de advertencia a "Sugerencia"


El rendimiento se divide en dos partes. Tiene rendimiento cuando el software está en producción, pero también desea tener rendimiento mientras desarrolla y depura. 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. Así que es bueno saber estos pequeños trucos que rinden frutos en ambos escenarios.

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


En lo que respecta al rendimiento, no habrá una diferencia notable entre los dos enfoques.

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

Existen escuelas de pensamiento que compiten en cuanto a qué enfoque es preferible.

Una vista es la que otros han mencionado: la segunda aproximación reduce el nivel de anidamiento, lo que mejora la claridad del código. Esto es natural en un estilo imperativo: cuando no le queda nada por hacer, también puede regresar temprano.

Otra vista, 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, si las declaraciones siempre deben tener una cláusula else. De lo contrario, la expresión if no siempre tendrá un valor. Entonces, en el estilo funcional, el primer enfoque es más natural.


En mi opinión, el retorno temprano está bien si estás volviendo vacío (o algún código de retorno inútil que nunca verificará) y puede mejorar la legibilidad porque evitas anidar y al mismo tiempo explicitas que tu función está lista.

Si realmente está devolviendo un returnValue, anidar es generalmente una mejor manera de hacerlo porque devuelve su returnValue solo en un lugar (al final - duh), y podría hacer que su código sea más fácil de mantener en muchos casos.


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

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


Es una cuestión de opinión.

Mi enfoque normal sería evitar ifs de línea única, y regresa en medio de un método.

No querrá que las líneas como estas sugieran 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.


Eso es simplemente controversial. No existe un "acuerdo entre programadores" sobre la cuestión del retorno anticipado. Siempre es subjetivo, hasta donde yo sé.

Es posible hacer un argumento de rendimiento, ya que es mejor tener las condiciones que se escriben, por lo que a menudo son verdaderas; también se puede argumentar que es más claro. Por otro lado, crea pruebas anidadas.

No creo que obtengas una respuesta definitiva a esta pregunta.


Este es un pequeño argumento religioso, pero estoy de acuerdo con ReSharper en que deberías preferir anidar menos. 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 capacidad 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ías generalmente es mucho más fácil de leer.

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

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

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

La lectura deficiente del código lo hará.


Esto es, por supuesto, subjetivo, pero creo que mejora fuertemente en dos puntos:

  • Ahora es inmediatamente obvio que su función no tiene nada que hacer si la condition mantiene.
  • Mantiene el nivel de anidación abajo. Anidar daña la legibilidad más de lo que piensas.

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 Devolución (Copy Elision, ISO C ++ ''03 12.8 / 15) depende de tener un único punto de salida. Esta optimización evita copiar construyendo su valor de retorno (en su ejemplo específico no importa). Esto podría conducir a ganancias considerables en rendimiento en bucles ajustados, ya que está guardando un constructor y un destructor cada vez que se invoca la función.

Pero en el 99% de los casos, guardar las llamadas al constructor y al destructor adicionales no justifica la pérdida de legibilidad anidada if se introducen los bloques (como otros han señalado).


Hay varias ventajas para este tipo de codificación, pero para mí la gran victoria es que si puede regresar rápido, puede mejorar la velocidad de su aplicación. IE Sé que debido a la Precondición X puedo regresar rápidamente con un error. Esto elimina los casos de error primero 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 una gran cantidad de CPU. Algunos programadores utilizan invariantes de bucle para hacer este tipo de salida rápida, pero en esto puede romper su canalización de CPU e incluso crear un problema de búsqueda de memoria y significar que la CPU necesita cargarse desde el caché externo. Pero básicamente creo que deberías hacer lo que pretendías, es decir, terminar con 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.


La idea de regresar solo al final de una función se remonta a los días anteriores a que los idiomas tuvieran soporte para excepciones. Permitió que los programas confiaran en poder poner código de limpieza al final de un método, y luego asegurarse de que se llamaría y que algún otro programador no ocultaría un retorno en el método que provocó que se saltara el código de limpieza. . El código de limpieza omitido podría provocar una pérdida de memoria o de 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 palabras clave finalmente o usando.

De todos modos, digo que creo que mucha gente cita 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.


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, la try | finally construye y using bloques, realmente no hay ninguna razón por la cual les tengas miedo.

En definitiva, se trata de lo que usted y sus colegas encuentran más fácil de leer.


Me gustaría agregar que hay un nombre para esos ifs invertidos - Guard Clause. Lo uso siempre que puedo.

Odio leer el código donde está si al principio, dos pantallas de código y ninguna otra. Simplemente invierte y regresa. De esa forma, nadie perderá tiempo desplazándose.

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


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

function do_something( data ){ if (!is_valid_data( data )) return false; do_something_that_take_an_hour( data ); istance = new object_with_very_painful_constructor( data ); if ( istance is not valid ) { error_message( ); return ; } connect_to_database ( ); get_some_other_data( ); return; }

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


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

Echemos un vistazo a algunos códigos C # y su formulario IL compilado:

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 simple fragmento se puede compilar. Puede abrir el archivo .exe generado con ildasm y comprobar cuál es el resultado. No publicaré todo lo de 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á el segundo.
  2. Si es verdad, salta a la última instrucción. (Nota: la última instrucción es una devolución).
  3. En la segunda condición, lo mismo ocurre después de calcular el resultado. Compare and: llegó a Console.WriteLine si es falso o al final si esto es cierto.
  4. Imprime el mensaje y regresa.

Entonces parece que el código saltará hasta el final. ¿Qué sucede si hacemos un código normal si está 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 a la siguiente pieza de código, si es verdadero, vaya a continuación. Y ahora el código IL fluye mejor y tiene 3 saltos (el compilador optimizó esto un poco): 1. Primer salto: cuando Length es 0 a una parte donde el código salta nuevamente (Third jump) hasta el final. 2. Segundo: en el 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á.


No solo afecta la estética, sino que también evita la anidación del código.

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


Personalmente prefiero solo 1 punto de salida. Es fácil de lograr si mantiene sus métodos cortos y al punto, y proporciona un patrón predecible para la siguiente persona que trabaja 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 ésta finalice. Todo lo que necesita hacer es colocar un punto de interrupción en la declaración final y se garantiza que lo alcanzará (a menos que se produzca una excepción).


Un retorno en el medio del método no es necesariamente malo. Puede ser mejor regresar de inmediato si hace que el intento del código sea más claro. 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 _isDead es verdadero, podemos salir inmediatamente del método. En su lugar, 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.


Ya hay muchas respuestas perspicaces, pero aún así, dirigiría a una situación ligeramente diferente: en lugar de precondición, eso debería ponerse encima de una función, piense en una inicialización paso a paso, donde tiene que verificar que cada paso tenga éxito y luego continuar con el siguiente. En este caso, no puedes verificar todo en la parte superior.

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

Al reemplazar las cláusulas nesting y guard, incluso descubrí un concepto erróneo respecto a una parte del código de limpieza que debería haber ocurrido mucho antes en la función en lugar de al final. Con ramas anidadas, nunca lo habría visto, incluso podrías decir que me llevaron a una idea errónea.

Así que esta podría ser otra situación donde ifs invertidos pueden contribuir a un código más claro.


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

Por otro lado, también hace que tu 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 tonto 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 deberían ser equivalentes (si no están en el nivel IL, entonces, 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 de optimización de código mucho más avanzados que este.