Así que he estado programando durante algunos años y recientemente he comenzado a usar ReSharper más. Una cosa que ReSharper siempre me sugiere es "invertir la instrucción 'if' para reducir el anidamiento".
Digamos que tengo este código:
foreach (someObject in someObjectList)
{
if(someObject != null)
{
someOtherObject = someObject.SomeProperty;
}
}
Y ReSharper me sugerirá que haga esto:
foreach (someObject in someObjectList)
{
if(someObject == null) continue;
someOtherObject = someObject.SomeProperty;
}
Parece que ReSharper SIEMPRE sugerirá que invierta los IF, sin importar cuánto anidamiento esté ocurriendo. Este problema es que me gusta anidar al menos en ALGUNAS situaciones. Para mí es más fácil de leer y descubrir qué está pasando en ciertos lugares. Este no es siempre el caso, pero a veces me siento más cómodo anidando.
Mi pregunta es: además de la preferencia personal o la legibilidad, ¿hay alguna razón para reducir la anidación? ¿Hay alguna diferencia de rendimiento o alguna otra cosa que no conozca?
fuente
Respuestas:
Depende. En su ejemplo, tener la
if
declaración no invertida no es un problema, porque el cuerpo de laif
es muy corto. Sin embargo, generalmente desea invertir las declaraciones cuando crecen los cuerpos.Solo somos humanos y mantener múltiples cosas a la vez en nuestras cabezas es difícil.
Cuando el cuerpo de una declaración es grande, invertir la declaración no solo reduce el anidamiento, sino que también le dice al desarrollador que pueden olvidarse de todo lo que está arriba de esa declaración y centrarse solo en el resto. Es muy probable que use las declaraciones invertidas para deshacerse de los valores incorrectos lo antes posible. Una vez que sepa que están fuera del juego, lo único que queda son valores que realmente pueden procesarse.
fuente
break
,continue
y múltiplesreturn
, no están estructuradas.break
/continue
/return
tengo una ventaja real sobre unelse
bloque. Con salidas anticipadas, hay 2 bloques : el bloque de salida y el bloque de permanecer allí; conelse
hay 3 bloques : if, else, y lo que viene después (que se usa sea cual sea la rama tomada). Prefiero tener menos bloques.No evites los negativos. Los humanos apestan al analizarlos incluso cuando la CPU los ama.
Sin embargo, respeta los modismos. Los humanos somos criaturas de hábitos, por lo que preferimos caminos bien usados a caminos directos llenos de malezas.
foo != null
tristemente se ha convertido en un idioma. Ya casi no me doy cuenta de las partes separadas. Miro eso y solo pienso, "oh, es seguro salirfoo
ahora".Si quieres anular eso (oh, algún día, por favor) necesitas un caso realmente fuerte. Necesitas algo que permita que mis ojos se deslicen sin esfuerzo y lo entiendan en un instante.
Sin embargo, lo que nos diste fue esto:
¡Lo cual no es estructuralmente igual!
Eso no está haciendo lo que mi cerebro perezoso esperaba que hiciera. Pensé que podría seguir agregando código independiente pero no puedo. Esto me hace pensar en cosas en las que no quiero pensar ahora. Rompiste mi idioma pero no me diste uno mejor. Todo porque querías protegerme de la única negativa que ha quemado su pequeño ser desagradable en mi alma.
Lo siento, tal vez ReSharper tenga razón al sugerir esto el 90% del tiempo, pero en las comprobaciones nulas, creo que ha encontrado un caso de esquina donde es mejor ignorarlo. Las herramientas simplemente no son buenas para enseñarle qué es el código legible. Pregúntale a un humano. Haz una revisión por pares. Pero no sigas ciegamente la herramienta.
fuente
if (foo != null){ foo.something() }
me permite seguir agregando código sin importarme sifoo
era nulo. Esto no lo hace. Incluso si no necesito hacer eso ahora, no me siento motivado para arruinar el futuro diciendo "bueno, pueden refactorizarlo si lo necesitan". Feh El software solo es suave si es fácil de cambiar.Es el viejo argumento sobre si un descanso es peor que anidar.
La gente parece aceptar la devolución temprana de las verificaciones de parámetros, pero está mal visto en la mayor parte de un método.
Personalmente evito continuar, romper, ir a etc., incluso si eso significa más anidamiento. Pero en la mayoría de los casos puedes evitar ambos.
Obviamente, anidar hace que las cosas sean difíciles de seguir cuando tienes muchas capas
Lo peor es cuando tienes ambos
fuente
foreach (subObject in someObjectList.where(i=>i != null))
no sé por qué nunca he pensado en algo así.El problema con esto
continue
es que si agrega más líneas al código, la lógica se complica y es probable que contenga errores. p.ej¿Quieres llamar
doSomeOtherFunction()
para todos someObject, o solo si no es nulo? Con el código original tienes la opción y es más claro. Concontinue
uno podría olvidar "oh, sí, allá atrás nos saltamos nulos" y ni siquiera tiene la opción de llamarlo para todos algunos Objetos, a menos que ponga esa llamada al comienzo del método, lo que puede no ser posible o correcto por otras razonesSí, muchos anidamientos son feos y posiblemente confusos, pero en este caso usaría el estilo tradicional.
Pregunta adicional: ¿Por qué hay nulos en esa lista para empezar? Puede ser mejor nunca agregar un valor nulo en primer lugar, en cuyo caso la lógica de procesamiento es mucho más simple.
fuente
return
s causa que forman una "ruptura", pero la OMIbreak
ycontinue
conducen a la confusión y, en la mayoría de los casos, lo mejor es evitarlo.La idea es el regreso temprano. Temprano
return
en un ciclo se vuelve tempranocontinue
.Muchas buenas respuestas a esta pregunta: ¿Debo regresar temprano de una función o usar una declaración if?
Tenga en cuenta que, si bien la pregunta se cierra en función de la opinión, más de 430 votos están a favor de la devolución anticipada, ~ 50 votos son "depende" y 8 están en contra de la devolución anticipada. Entonces, hay un consenso excepcionalmente fuerte (o eso, o soy malo contando, lo cual es plausible).
Personalmente, si el cuerpo del bucle estaría sujeto a una continuación temprana y el tiempo suficiente para que tenga importancia (3-4 líneas), tiendo a extraer el cuerpo del bucle en una función donde utilizo el retorno temprano en lugar de continuar temprano.
fuente
Esta técnica es útil para certificar condiciones previas cuando la mayor parte de su método ocurre cuando se cumplen esas condiciones.
Con frecuencia invertimos
ifs
en mi trabajo y empleamos un fantasma más. Solía ser bastante frecuente que mientras revisaba un RP pudiera señalar cómo mi colega podía eliminar tres niveles de anidamiento. Ocurre con menos frecuencia ya que implementamos nuestros ifs.Aquí hay un ejemplo de juguete de algo que se puede implementar
Un código como este, donde hay UN caso interesante (donde el resto son simplemente retornos o pequeños bloques de enunciados) son comunes. Es trivial reescribir lo anterior como
Aquí, nuestro código significativo, las 10 líneas no incluidas, no tiene una sangría triple en la función. Si tiene el primer estilo, está tentado a refactorizar el bloque largo en un método o tolerar la sangría. Aquí es donde entran en juego los ahorros. En un lugar, esto es un ahorro trivial, pero a través de muchos métodos en muchas clases, ahorras la necesidad de generar una función adicional para hacer que el código sea más limpio y no tienes tantos niveles horribles de varios niveles muesca .
En respuesta al ejemplo de código de OP, estoy de acuerdo en que es un colapso excesivo. Especialmente porque en 1TB, el estilo que uso, la inversión hace que el código aumente de tamaño.
fuente
Las sugerencias de los resharpers a menudo son útiles, pero siempre deben evaluarse críticamente. Busca algunos patrones de código predefinidos y sugiere transformaciones, pero no puede tener en cuenta todos los factores que hacen que el código sea más o menos legible para los humanos.
En igualdad de condiciones, es preferible menos anidamiento, por lo que ReSharper sugerirá una transformación que reduzca el anidamiento. Pero todas las demás cosas no son iguales: en este caso, la transformación requiere la introducción de a
continue
, lo que significa que el código resultante es realmente más difícil de seguir.En algunos casos, intercambiando un nivel de anidamiento por un
continue
poder ser una mejora, pero esto depende de la semántica del código en cuestión, que está más allá de la comprensión de las reglas mecánicas de ReSharpers.En su caso, la sugerencia de ReSharper empeoraría el código. Así que ignóralo. O mejor: no use la transformación sugerida, pero piense un poco en cómo se podría mejorar el código. Tenga en cuenta que puede estar asignando la misma variable varias veces, pero solo la última asignación tiene efecto, ya que la anterior simplemente se sobrescribirá. Esto hace ver como un insecto. La sugerencia de ReSharpers no soluciona el error, pero hace que sea un poco más obvio que está ocurriendo una lógica dudosa.
fuente
Creo que el punto más importante aquí es que el propósito del ciclo dicta la mejor manera de organizar el flujo dentro del ciclo. Si está filtrando algunos elementos antes de recorrer el resto, entonces
continue
tiene sentido salir temprano. Sin embargo, si está haciendo diferentes tipos de procesamiento dentro de un bucle, podría tener más sentido hacerloif
realmente obvio, como:Tenga en cuenta que en Java Streams u otros modismos funcionales, puede separar el filtrado del bucle, utilizando:
Por cierto, esta es una de las cosas que me encantan de Perl invertido if (
unless
) aplicado a declaraciones individuales, lo que permite el siguiente tipo de código, que encuentro muy fácil de leer:fuente