Imagina el siguiente código:
void DoThis()
{
if (!isValid) return;
DoThat();
}
void DoThat() {
Console.WriteLine("DoThat()");
}
¿Está bien usar una devolución dentro de un método vacío? ¿Tiene alguna penalización por desempeño? O sería mejor escribir un código como este:
void DoThis()
{
if (isValid)
{
DoThat();
}
}
Respuestas:
Un método de retorno en un vacío no está mal, es una práctica común invertir
if
declaraciones para reducir el anidamiento .Y tener menos anidamiento en sus métodos mejora la legibilidad y el mantenimiento del código.
En realidad, si tiene un método void sin ninguna declaración de retorno, el compilador siempre generará una instrucción ret al final.
fuente
Existe otra gran razón para usar guardias (a diferencia del código anidado): si otro programador agrega código a su función, está trabajando en un entorno más seguro.
Considerar:
void MyFunc(object obj) { if (obj != null) { obj.DoSomething(); } }
versus:
void MyFunc(object obj) { if (obj == null) return; obj.DoSomething(); }
Ahora, imagina que otro programador agrega la línea: obj.DoSomethingElse ();
void MyFunc(object obj) { if (obj != null) { obj.DoSomething(); } obj.DoSomethingElse(); } void MyFunc(object obj) { if (obj == null) return; obj.DoSomething(); obj.DoSomethingElse(); }
Obviamente, este es un caso simplista, pero el programador ha agregado un bloqueo al programa en la primera instancia (código anidado). En el segundo ejemplo (salida anticipada con guardias), una vez que pasa la guardia, su código está a salvo del uso no intencional de una referencia nula.
Claro, un gran programador no comete errores como este (a menudo). Pero es mejor prevenir que curar: podemos escribir el código de una manera que elimine por completo esta fuente potencial de errores. El anidamiento agrega complejidad, por lo que las mejores prácticas recomiendan refactorizar el código para reducir el anidamiento.
fuente
¿¿¿Mala práctica??? De ninguna manera. De hecho, siempre es mejor manejar las validaciones volviendo del método lo antes posible si las validaciones fallan. De lo contrario, resultaría en una gran cantidad de ifs y elses anidados. Terminar temprano mejora la legibilidad del código.
También verifique las respuestas a una pregunta similar: ¿Debería usar la declaración return / continue en lugar de if-else?
fuente
No es una mala práctica (por todas las razones ya mencionadas). Sin embargo, cuantos más retornos tenga un método, más probable será que se divida en métodos lógicos más pequeños.
fuente
El primer ejemplo está usando una declaración de guardia. De Wikipedia :
Creo que tener un montón de guardias en la parte superior de un método es una forma perfectamente comprensible de programar. Básicamente está diciendo "no ejecute este método si alguno de estos es verdadero".
Entonces, en general, le gustaría esto:
void DoThis() { if (guard1) return; if (guard2) return; ... if (guardN) return; DoThat(); }
Creo que eso es mucho más legible entonces:
void DoThis() { if (guard1 && guard2 && guard3) { DoThat(); } }
fuente
No hay ninguna penalización de rendimiento, sin embargo, la segunda parte del código es más legible y, por lo tanto, más fácil de mantener.
fuente
En este caso, su segundo ejemplo es un código mejor, pero eso no tiene nada que ver con regresar de una función void, es simplemente porque el segundo código es más directo. Pero regresar de una función vacía está completamente bien.
fuente
Está perfectamente bien y sin "penalización de rendimiento", pero nunca escriba una declaración "si" sin corchetes.
Siempre
if( foo ){ return; }
Es mucho más legible; y nunca asumirá accidentalmente que algunas partes del código están dentro de esa declaración cuando no lo están.
fuente
{
. Esto lo alinea{
con su}
en la misma columna, lo que ayuda en gran medida a la legibilidad (es mucho más fácil encontrar las llaves de apertura / cierre correspondientes).if
será fácil distinguir visualmente qué declaraciones requieren llaves cerradas y, por lo tanto, tenerif
declaraciones que controlen una sola declaración será seguro. Empujar la abrazadera abierta de nuevo a la línea con elif
guarda una línea de espacio vertical en cada declaración múltipleif
, pero requerirá el uso de una línea de abrazadera cerrada que de otro modo sería innecesaria.Voy a estar en desacuerdo con todos ustedes, jóvenes secuestradores en este caso.
Usar el retorno en medio de un método, nulo o no, es una muy mala práctica, por razones que fueron articuladas con bastante claridad, hace casi cuarenta años, por el difunto Edsger W. Dijkstra, comenzando con la conocida "Declaración GOTO considerada perjudicial ", y continuando en" Programación estructurada ", de Dahl, Dijkstra y Hoare.
La regla básica es que cada estructura de control y cada módulo debe tener exactamente una entrada y una salida. Un retorno explícito en el medio del módulo rompe esa regla y hace que sea mucho más difícil razonar sobre el estado del programa, lo que a su vez hace que sea mucho más difícil decir si el programa es correcto o no (que es una propiedad mucho más fuerte que "si parece funcionar o no").
La "Declaración de GOTO considerada perjudicial" y la "Programación estructurada" dieron inicio a la revolución de la "Programación estructurada" de la década de 1970. Esas dos piezas son las razones por las que tenemos if-then-else, while-do y otras construcciones de control explícitas en la actualidad, y por qué las declaraciones GOTO en lenguajes de alto nivel están en la lista de especies en peligro de extinción. (Mi opinión personal es que deben estar en la lista de especies extintas).
Vale la pena señalar que Message Flow Modulator, la primera pieza de software militar que NUNCA pasó las pruebas de aceptación en el primer intento, sin desviaciones, exenciones o palabrería de "sí, pero", se escribió en un idioma que ni siquiera tenía una declaración GOTO.
También vale la pena mencionar que Nicklaus Wirth cambió la semántica de la declaración RETURN en Oberon-07, la última versión del lenguaje de programación Oberon, convirtiéndola en una parte final de la declaración de un procedimiento escrito (es decir, una función), en lugar de una instrucción ejecutable en el cuerpo de la función. Su explicación del cambio decía que lo hizo precisamente porque la forma anterior ERA una violación del principio de una salida de la Programación Estructurada.
fuente
Mientras usa protectores, asegúrese de seguir ciertas pautas para no confundir a los lectores.
Ejemplo
// guards point you to the core intent void Remove(RayCastResult rayHit){ if(rayHit== RayCastResult.Empty) return ; rayHit.Collider.Parent.Remove(); } // no guards needed: function split into multiple cases int WonOrLostMoney(int flaw)=> flaw==0 ? 100 : flaw<10 ? 30 : flaw<20 ? 0 : -20 ;
fuente
Lanza una excepción en lugar de no devolver nada cuando el objeto es nulo, etc.
Su método espera que el objeto no sea nulo y no es el caso, por lo que debe lanzar una excepción y dejar que la persona que llama lo maneje.
Pero el regreso temprano no es una mala práctica de otra manera.
fuente