visual studio mega full code c# resharper

c# - mega - resharper visual studio code



Invertir la declaraciĆ³n "if" para reducir el anidamiento (24)

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

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

ReSharper me dio la advertencia anterior (Invertir 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".


Como han mencionado otros, no debería haber un impacto en el rendimiento, pero hay otras consideraciones. Aparte de esas preocupaciones válidas, esto también puede abrirte a problemas en algunas circunstancias. Supongamos que tratas 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, if no lo está.


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


El rendimiento es en dos partes. Usted tiene un rendimiento cuando el software está en producción, pero también desea tener un rendimiento mientras desarrolla y depura. Lo último que un desarrollador quiere es "esperar" por algo trivial. Al final, compilar esto con la optimización habilitada resultará en un código similar. Así que es bueno saber estos pequeños trucos que dan resultado 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á 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 tiene que analizar para encontrar dónde quiere ir.


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

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

Hay escuelas de pensamiento en competencia en cuanto a qué enfoque es preferible.

Una vista es la que otros han mencionado: el segundo enfoque reduce el nivel de anidamiento, lo que mejora la claridad del código. Esto es natural en un estilo imperativo: cuando no te queda nada por hacer, es mejor que vuelvas pronto.

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. Así que si las declaraciones deben tener siempre otras cláusulas. De lo contrario, la expresión if no siempre tendría un valor. Así que en el estilo funcional, el primer enfoque es más natural.


En mi opinión, el retorno anticipado está bien si solo devuelve un valor nulo (o algún código de retorno inútil que nunca va a verificar) y podría mejorar la legibilidad porque evita el anidamiento y al mismo tiempo hace explícito que su función está terminada.

Si en realidad está devolviendo un valor de retorno, el anidamiento suele ser una mejor manera de hacerlo porque devuelve el valor de retorno solo en un lugar (al final - duh), y puede hacer que su código sea más fácil de mantener en muchos casos.


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

Más sobre la predicción de la rama here .


Es una cuestión de opinión.

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

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


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

Es posible presentar un argumento de rendimiento, ya que es mejor tener las condiciones escritas, por lo que la mayoría de las veces son verdaderas; También se puede argumentar que es más claro. Por otro lado, sí crea pruebas anidadas.

No creo que obtendrás una respuesta concluyente a esta pregunta.


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

La razón clave para tener menos anidamientos es mejorar la legibilidad y el mantenimiento del código . Recuerde que muchos otros desarrolladores necesitarán leer su código en el futuro, y el código con menos sangrado 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 debe verse afectada por la presencia de una verificación de condición previa?

En cuanto a los aspectos negativos de volver múltiples veces desde un método, los depuradores son bastante poderosos ahora, y es muy fácil saber exactamente dónde y cuándo está regresando una función en particular.

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

La mala legibilidad del código lo hará.


Este tipo de codificación tiene varias ventajas, pero para mí la gran ventaja es que, si puede regresar rápidamente, puede mejorar la velocidad de su aplicación. IE Sé que, debido a la Condició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 fallos o los interruptores de la tubería. En segundo lugar, si estás en un bucle, romper o salir rápidamente te puede ahorrar un montón de CPU. Algunos programadores utilizan invariantes de bucle para hacer este tipo de salida rápida, pero en este caso puede romper su tubería de CPU e incluso crear un problema de búsqueda de memoria y hacer que la CPU deba cargarse desde el caché externo. Pero básicamente creo que debería hacer lo que pretendía, es decir, finalizar el bucle o la función, no crear una ruta de código compleja solo para implementar una noción abstracta de código correcto. Si la única herramienta que tienes es un martillo, entonces todo parece un clavo.


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

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

Evitar múltiples puntos de salida puede llevar a ganancias de rendimiento. No estoy seguro 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 solo punto de salida. Esta optimización evita la copia al construir su valor de retorno (en su ejemplo específico no importa). Esto podría generar considerables ganancias en el rendimiento de los bucles ajustados, 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 pena por la pérdida de legibilidad que se anida if se introducen bloques (como han señalado otros).


Hay varios puntos buenos aquí, pero los puntos de retorno múltiples también pueden ser ilegibles , si el método es muy largo. Dicho esto, si va a utilizar múltiples puntos de retorno, solo asegúrese de que su método sea corto, de lo contrario se perderá la ventaja de legibilidad de los múltiples puntos de retorno.


La idea de regresar solo al final de una función regresó de los días previos a que los idiomas admitieran excepciones. Permitió que los programas confiaran en poder poner 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 una devolución en el método que causó que se omitiera el código de limpieza . El código de limpieza omitido podría provocar una pérdida de memoria o recurso.

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 provocar un flujo de control que hace que el método finalice. Esto significa que la limpieza se debe realizar mediante el uso de las palabras clave finally o using.

De todos modos, estoy diciendo que creo que mucha gente cita la directriz de ''solo retorno al final de un método'' sin entender por qué siempre fue bueno hacerlo, y que reducir el anidamiento para mejorar la legibilidad es probablemente un objetivo mejor.


Las cláusulas de seguridad o las condiciones previas (como probablemente pueda ver) verifican si se cumple una determinada condición y luego interrumpen el flujo del programa. Son excelentes para lugares en los que realmente solo te interesa un resultado de una declaración if . Así que en lugar de decir:

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

Revertir la condición y romper 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 es 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 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á pocas personas que argumenten que lo primero es más limpio pero, por supuesto, es completamente subjetivo. A algunos programadores les gusta saber en qué condiciones está operando algo con sangría, mientras que yo prefiero mantener el flujo de método lineal.

No sugeriré ni por un momento que los precones cambiarán su vida o lo arreglarán, pero puede que encuentre su código un poco más fácil de leer.


Los puntos de retorno múltiples fueron un problema en C (y, en menor medida, en C ++) porque lo 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 | finally construye y using bloques, realmente no hay razón por la que deberías tenerles miedo.

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


Me gustaría agregar que hay un nombre para aquellos invertidos if''s - Guard Clause. Lo uso siempre que puedo.

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

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


Mi idea es que el retorno "en 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; }

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


Muchas buenas razones acerca de cómo se ve el código . Pero ¿qué pasa con los resultados ?

Echemos un vistazo a algunos códigos 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 simple fragmento puede ser compilado. Puede abrir el archivo .exe generado con ildasm y verificar cuál es el resultado. No publicaré todo lo del 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 una devolución).
  3. En la segunda condición ocurre lo mismo después de que se calcula el resultado. Compare y: acceda a Console.WriteLine si es falso o al final si es verdad.
  4. Imprime el mensaje y vuelve.

Así que parece que el código saltará hasta el 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 de los 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 lo optimizó 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á.


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

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 salga. Todo lo que necesita hacer es colocar un punto de interrupción en la declaración final y se le garantiza que lo alcanzará (a menos que se lance una excepción).


Un retorno en el medio del método no es necesariamente malo. Podría ser mejor regresar inmediatamente 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 _isDead es verdadero, podemos salir del método inmediatamente. Podría ser mejor estructurarlo de esta manera:

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

He escogido este código del catálogo de refactorización . Esta refactorización específica se llama: Reemplazar condicionalmente anidada con cláusulas de guardia.


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

Encontré mi código realmente ilegible al escribir una aplicación de host ASIO con ASIOSDK de Steinberg, ya que seguí el paradigma de anidamiento. Tuvo ocho niveles de profundidad, y no puedo ver una falla 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 anidar los niveles restantes allí para hacerlo más legible, pero esto me parece bastante aleatorio.

Al reemplazar las cláusulas de anidación con las de guardia, incluso descubrí una idea errónea de la 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 hubiera visto eso, incluso se podría decir que llevaron a mi idea errónea.

Entonces, esta podría ser otra situación en la que los 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 anidamiento dentro del método. Esto generalmente se considera como una ventaja, ya que hace que los métodos sean más fáciles de entender (y, de hecho, many tools analysis static 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 ridículo hablar de "puntos de salida múltiples"; casi cualquier cosa puede lanzar, por lo que existen numerosos puntos de salida potenciales en todos los métodos).

Respecto al rendimiento : ambas versiones deben ser equivalentes (si no están en el nivel de IL, luego de que haya finalizado el jitter 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.