2010-03-06 25 views
8

Muchos años de codificación me han llevado a creer y tratar de llegar a este tipo de si la codificación condicional: (demostrado en C, pero es relevante casi a cualquier idioma)¿Cuál es la mejor práctica de codificación para las condiciones?

if(a <= 0) 
     return false; 
if(strlen(str) <= a) 
     return false; 
if(str[a] == NULL) 
     return false; 
return true; 

que creo que es mucho más fácil de leer que la siguiente muestra la codificación, especialmente en un gran número de condiciones:

if(a >0) 
{ 
    if(strlen(str) > a) 
    { 
      if(str[a] != NULL) 
      { 
       return true; 
      } 
     } 
    } 

Mientras que el código superior es mucho más legible, en grandes condiciones en que estarían haciendo el cierre de tantos "}", y el último código creo es compilado a un código de mejor rendimiento.

¿Cuál crees que es mejor usar?

(El código de este ejemplo es sólo para demostrar no lo tome literalmente)

+1

El segundo formato será legible. Y puedes tener estas tres expresiones en && condición. Entonces, solo usarás una condición si. – Pavunkumar

+1

Quizás mi muestra de codificación no era la "correcta" para la pregunta. Sé que puedes ponerlo en una condición de línea. Pero estoy tratando de preguntar sobre el hábito de codificación en sí. – aviv

Respuesta

2

Si sigue Diseño por contrato, entonces tiene condiciones previas que deben cumplirse cuando se ingresa la función. Aunque hay situaciones en las que se puede demostrar que ya se cumplen algunas condiciones previas cuando se ingresa la función y en esos casos no será necesaria una prueba, supongamos que las pruebas son necesarias. Bajo estas suposiciones, las precondiciones de función-contrato deben verificarse antes de que se pueda hacer cualquier otra cosa y las comprobaciones deben considerarse independientes del trabajo real que se supone que la función debe hacer. De esto se deduce que:

  • pruebas de pre-condiciones deben aparecer antes que cualquier otro código en la función
  • si una prueba para una pre-condición falla, la función debe devolver inmediatamente

Volviendo inmediatamente garantiza la separación completa de las pruebas de precondición del cuerpo real de la función, no solo semánticamente sino también léxicamente. Esto hace que sea fácil revisar el código y determinar si se sigue o no el contrato de función. También facilita la adición o eliminación de condiciones previas más adelante en caso de que se revise el contrato de función.

Código de ejemplo:

// function contract 
// 
// pre-conditions: 
// 
// o bar must not be zero 
// o foo_ptr must not be NULL 
// o foo_ptr must refer to a foo variant of type blue_foo 
// 
// ... 

Foo *foo_blue_init_with_bar(Foo *foo_ptr, int bar, foo_status *status) { 

    // ***** Test Pre-conditions ***** 

    // bar must not be zero 
    if (bar == 0) { 
     if (status != NULL) *status = FOO_STATUS_INVALID_BAR; 
     return NULL; 
    } 

    // foo_ptr must not be NULL 
    if (foo_ptr == NULL) { 
     if (status != NULL) *status = FOO_STATUS_INVALID_FOO_POINTER; 
     return NULL; 
    } 

    // foo_ptr must refer to a foo variant of type blue_foo 
    if (foo_ptr->type != blue_foo) { 
     if (status != NULL) *status = FOO_STATUS_INVALID_FOO_VARIANT; 
     return NULL; 
    } 

    // ***** Actual Work Goes Here ***** 

    ... 

} // end foo_blue_init_with_bar 

Por otro lado, si usted no sigue el Diseño por contrato, entonces tal vez puede ser considerada como una cuestión de preferencia personal.

5

Personalmente voy con la primera sección. Regrese lo antes posible.

+0

Me imagino que un compilador decente optimizaría el segundo caso para que, de hecho, regrese temprano de todos modos. ¿No? – amn

+0

¿quién dijo algo acerca de la optimización del compilador en esta respuesta? – Anurag

0

Elegir el segundo deseo sería mejor, ya que describe mejor la lógica, y la mejor práctica común es que es mejor tener solo una declaración de retorno dentro de una función.

No seleccionaré el primero por razones de rendimiento, ya que el compilador hará la optimización de todos modos, prefiero el código que sea legible.

+1

¿por qué es la mejor práctica para 1 declaración de devolución dentro de una función? –

+1

Eso es un "viejo" axioma ", que tener solo un retorno en un método reduce la complejidad. No es tan común en estos días, pero todavía se manejaba. –

8

La pregunta no es muy clara, aquí. Si vamos a comparar esto:

if (c1) return false; 
if (c2) return false; 
if (c3) return false; 
return true; 

Con esta:

if (!c1) { 
    if (!c2) { 
    if (!c3) { 
     return true; 
    } 
    } 
} 
return false; 

Entonces yo votaría por ninguno de ellos. Hacer esto en su lugar:

return !c1 && !c2 && !c3; 

Si la pregunta es sobre si es o no múltiples retornos son aceptables, entonces esa pregunta ya ha sido discutido (ver: Should a function have only one return statement)

2

El primer enfoque se ve aestheticly mejor, pero Don Creo que captura bastante lo que estás tratando de hacer. Si todas las condiciones lógicamente pertenecen juntas, colóquelas en una declaración if (puse mis operadores booleanos al comienzo de una línea para que sea más fácil comentar las condiciones individuales durante la prueba (un cuelgue de las cláusulas WHERE de SQL grande). ) :

if(a > 0 
     && strlen(str) > a 
     && str[a] != NULL) 
    { 
     return true; 
    } 

o, al devolver un valor lógico (que es un caso especial por lo que pueden no ser aplicables a su pregunta):

return (a > 0 
     && strlen(str) > a 
     && str[a] != NULL); 
1

Si las condiciones tienen una semántica similares o relacionados, es probable que sea mejor use una expresión lógica en lugar de un grupo de if s. Algo así como

return a > 0 && strlen(str) > a && str[a] != NULL; 

Pero cuando se tiene que utilizar el if, el mejor enfoque para la organización de la ramificación a menudo (aunque no siempre) depende de la naturaleza de if sí.

La mayoría de las veces if s en el código están "desequilibradas": o bien tienen una sola rama (no else), o una de las ramas es "pesada", mientras que a otra rama es "clara". En casos como ese, siempre es mejor tratar primero con la rama "simple" y luego indicar explícitamente que el procesamiento para esta rama ha terminado. Esto se logra utilizando la primera variante de su pregunta: detectar la situación que requiere un procesamiento "simple", hágalo, e inmediatamente return (si tiene que abandonar la función) o haga continue (si tiene que pasar a la siguiente) iteración del ciclo). Es ese inmediato return (o continue) que ayudan al lector a comprender que esta rama está lista y que ya no es necesario preocuparse más por ella. Entonces, una función típica sería la siguiente: un grupo de if s que "interceptan" los casos simples, los manejan (si es necesario) y return inmediatamente. Y solo después de que se manejan todos los casos simplistas, comienza el procesamiento genérico "pesado". Una función organizada así es mucho más fácil de leer que la que tiene un montón de if s anidados (como su segunda variante).

Algunos podrían argumentar que return s en el medio de la función o continue en el medio del ciclo van en contra de la idea de "programación estructurada". Pero de alguna manera las personas se pierden el hecho de que el concepto de "programación estructurada" nunca ha sido diseñado para el uso práctico. No, el principio "return early" (o "continue early" en el ciclo) mejora significativamente la legibilidad del código específicamente porque enfatiza explícitamente el hecho de que el procesamiento para esta rama ha terminado. En el caso de "estructurado" if s algo así es mucho menos obvio.

Para resumir lo anterior: Evite anidados if s. Son difíciles de leer. Evite "desequilibrado" if s. También son difíciles de leer. Prefiere el estilo de ramificación "plana" que trata primero con casos simples e inmediatamente finaliza el proceso haciendo un return explícito o continue.

0

Una razón principal para ir con el primer enfoque es que la sangría se mantiene dentro de niveles sanos. Cualquier cosa más allá de dos niveles comienza a volverse inmanejable. Personalmente, intento no exceder un solo nivel de sangría dentro de un método la mayoría de las veces.

Sin embargo, intuitivamente, al menos para mí, la segunda opción es más clara ya que la lógica parece clara. Cuando se cumplen las tres condiciones, regresamos verdadero. Tiendo a almacenar cada resultado booleano en una variable cuyo nombre tenga sentido en cuanto a lo que transmite el valor, y devolver su valor AND.

isPositive = a > 0; 
isStringLonger = str.length() > a; 
isCharacterNonNull = str[a] != NULL; 

return isPositive && isStringLonger && isCharacterNonNull; 

Acabo de inventar los nombres según su ejemplo para que parezcan basura pero el punto es que no deberían.

Alternativamente, cuando estas tres variables parecen mucho, las combino en una sola variable antes de devolverlas.

isValidString = isPositive && isStringLonger && isCharacterNonNull; 
return isValidString; 

En cuanto a las declaraciones de retorno múltiples ir, he encontrado que la forma del sufijo condición para la mayoría de las declaraciones de Ruby es muy limpio y fácil de leer.

return false if a <= 0 
return false if str.length() <= a 
return false if !str[a].nil? 
return true 
0

Sus dos muestras hacen cosas diferentes (vea el código a continuación). Yo preferiría la segunda si fue escrito como lo de abajo (excepto tal vez con menos corchetes.

if((a>0) && (strlen(str) > a) && (str[a] != NULL)) 
{ 
    return true; 
} 

o escrita como

return ((a>0) && (strlen(str) > a) && (str[a] != NULL)) 

Para responder a su pregunta. Yo prefiero la primera (a menos el código de arriba es correcto) y yo siempre prefiero

  1. Menos Tabbing. Prefiero todo lo más a la izquierda que su segundo ejemplo se rompe (pero no el mío)
  2. Regrese a la parte superior si es posible ex if (cond) return blah; else {code(); }
  3. tiene código corto en la parte superior de código y ya en la parte inferior

    si (cond) { código(); } else if (cond2) { code(); código(); } else { código(); código(); código(); }

Cuestiones relacionadas