¿Por qué las declaraciones “if elif else” prácticamente nunca están en formato de tabla?

73
if   i>0 : return sqrt(i)  
elif i==0: return 0  
else     : return 1j * sqrt(-i)

VS

if i>0:  
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

Teniendo en cuenta los ejemplos anteriores, no entiendo por qué virtualmente nunca veo el primer estilo en bases de código. Para mí, usted convierte el código en un formato tabular que muestra claramente lo que quiere. La primera columna puede ser virtualmente ignorada. La segunda columna identifica la condición y la tercera columna le da el resultado que desea. Parece, al menos para mí, sencillo y fácil de leer. Sin embargo, siempre veo que este tipo simple de caso / situación de cambio se presenta en el formato extendido de tabulación. ¿Porqué es eso? ¿La gente encuentra el segundo formato más legible?

El único caso en el que esto podría ser problemático es si el código cambia y se alarga. En ese caso, creo que es perfectamente razonable refactorizar el código en un formato largo y sangrado. ¿Todos lo hacen de la segunda manera simplemente porque así se hizo siempre? Siendo un defensor del diablo, supongo que otra razón podría ser porque las personas encuentran dos formatos diferentes según la complejidad de las afirmaciones if / else para ser confusas. Cualquier apreciación sería apreciada.

    
pregunta horta 26.07.2016 - 21:31

10 respuestas

93

Una de las razones puede ser que no estés usando idiomas en los que es popular.

Algunos ejemplos contrarios:

Haskell con guardias y con patrones:

sign x |  x >  0        =   1
       |  x == 0        =   0
       |  x <  0        =  -1

take  0     _           =  []
take  _     []          =  []
take  n     (x:xs)      =  x : take (n-1) xs

Erlang con patrones:

insert(X,Set) ->
    case lists:member(X,Set) of
        true  -> Set;
        false -> [X|Set]
    end.

Emacs lisp:

(pcase (get-return-code x)
  ('success       (message "Done!"))
  ('would-block   (message "Sorry, can't do it now"))
  ('read-only     (message "The shmliblick is read-only"))
  ('access-denied (message "You do not have the needed rights"))
  (code           (message "Unknown return code %S" code)))

Por lo general, veo que el formato de la tabla es bastante popular entre los lenguajes funcionales (y en general, los basados en expresiones), mientras que romper las líneas es más popular en otros (en su mayoría basados en declaraciones).

    
respondido por el viraptor 27.07.2016 - 01:47
134

Es más legible. Algunas razones por las que:

En el momento en que intentes hacer algo de esto, terminarás reescribiéndolo en sentencias de varias líneas. Lo que significa que acabas de perder el tiempo!

También la gente inevitablemente agrega algo como:

if i>0:  
   print('foobar')
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

No es muy frecuente que hagas esto antes de decidir que este formato es mucho mejor que tu alternativa. ¡Ah, pero podrías incluirlo todo en una línea! Enderland muere en el interior .

O esto:

if   i>0 : return sqrt(i)  
elif i==0 and bar==0: return 0  
else     : return 1j * sqrt(-i)

Lo que es realmente molesto. A nadie le gusta formatear cosas como esta.

Y por último, comenzará la guerra santa del problema de "cuántos espacios hay para las pestañas". Lo que se reproduce perfectamente en su pantalla como formato de tabla puede no representarse en la mía, dependiendo de la configuración

La legibilidad no debería depender de la configuración de IDE de todos modos.

    
respondido por el enderland 26.07.2016 - 21:52
55

Soy un firme creyente en que 'el código se lee muchas veces, escrito pocos, por lo que la legibilidad es muy importante'

Una cosa clave que me ayuda cuando leo el código de otras personas es que sigue los patrones "normales" que mis ojos están entrenados para reconocer. Puedo leer la forma sangrada más fácilmente porque la he visto tantas veces que se registra casi automáticamente (con poco esfuerzo cognitivo de mi parte). No es porque sea más "bonito", sino porque sigue las convenciones a las que estoy acostumbrado. Convención vence a 'mejor' ...

    
respondido por el Art Swri 26.07.2016 - 22:31
16

Junto con los otros inconvenientes ya mencionados, el diseño tabular aumenta las probabilidades de conflicto de combinación de control de versión que requieren intervención manual.

Cuando se debe realinear un bloque de código alineado de forma tabular, el sistema de control de versión tratará cada una de esas líneas como modificadas:

diff --git a/foo.rb b/foo.rb
index 40f7833..694d8fe 100644
--- a/foo.rb
+++ b/foo.rb
@@ -1,8 +1,8 @@
 class Foo

   def initialize(options)
-    @cached_metadata = options[:metadata]
-    @logger          = options[:logger]
+    @metadata = options[:metadata]
+    @logger   = options[:logger]
   end

 end

Ahora suponga que mientras tanto, en otra rama, un programador ha agregado una nueva línea al bloque de código alineado:

diff --git a/foo.rb b/foo.rb
index 40f7833..86648cb 100644
--- a/foo.rb
+++ b/foo.rb
@@ -3,6 +3,7 @@ class Foo
   def initialize(options)
     @cached_metadata = options[:metadata]
     @logger          = options[:logger]
+    @kittens         = options[:kittens]
   end

 end

La fusión de esa rama fallará:

wayne@mercury:/tmp/foo$ git merge add_kittens
Auto-merging foo.rb
CONFLICT (content): Merge conflict in foo.rb
Automatic merge failed; fix conflicts and then commit the result.

Si el código que se está modificando no hubiera utilizado la alineación tabular, la fusión se habría realizado automáticamente.

(Esta respuesta fue "plagiada" de mi propio artículo desalentando la alineación tabular en el código) .

    
respondido por el Wayne Conrad 28.07.2016 - 23:57
8

Los formatos tabulares pueden ser muy agradables si las cosas siempre encajan dentro del ancho asignado. Sin embargo, si algo excede el ancho asignado, a menudo es necesario que una parte de la tabla no esté alineada con el resto, o bien que se ajuste el diseño de todo lo demás en la tabla para que coincida con el elemento largo .

Si los archivos de origen se editaron utilizando programas diseñados para funcionar con datos de formato tabular y podrían manejar elementos demasiado largos utilizando un tamaño de fuente más pequeño, dividiéndolos en dos líneas dentro de la misma celda, etc., entonces podría tener sentido para usar formatos tabulares más a menudo, pero la mayoría de los compiladores quieren archivos de origen que estén libres de los tipos de marcado que dichos editores necesitarían almacenar para mantener el formato. El uso de líneas con cantidades variables de sangría pero sin otro diseño no es tan bueno como el formato tabular en el mejor de los casos, pero no causa tantos problemas en el peor de los casos.

    
respondido por el supercat 27.07.2016 - 01:38
6

Hay una declaración de 'cambio' que proporciona este tipo de cosas para casos especiales, pero supongo que no es eso lo que estás preguntando.

He visto declaraciones de if en formato de tabla, pero tiene que haber una gran cantidad de condiciones para que valga la pena. 3 si las declaraciones se muestran mejor en el formato tradicional, pero si tiene 20, entonces es mucho más fácil mostrarlas en un bloque grande que está formateado para hacerlo más claro.

Y ahí está el punto: claridad. Si lo hace más fácil de ver (y su primer ejemplo no es fácil de ver dónde está: delimitador), entonces formatéelo para adaptarlo a la situación. De lo contrario, quédese con lo que la gente espera, ya que siempre es más fácil de reconocer.

    
respondido por el gbjbaanb 27.07.2016 - 09:35
1

Si su expresión es así de fácil, la mayoría de los lenguajes de programación ofrecen el operador?: branching:

return  ( i > 0  ) ? sqrt( i)
      : ( i == 0 ) ? 0
        /* else */ : 1j * sqrt( -i )

Este es un formato tabular legible corto. Pero la parte importante es: veo de un vistazo lo que es la acción "principal". Esta es una declaración de retorno! Y el valor se decide por ciertas condiciones.

Si, por otro lado, tienes ramas que ejecutan un código diferente, me parece mucho más fácil sangrar estos bloques. Porque ahora hay diferentes acciones "principales" dependiendo de la sentencia if. En un caso lanzamos, en un caso registramos y devolvemos o simplemente regresamos. Hay un flujo de programa diferente dependiendo de la lógica, por lo que los bloques de código encapsulan las diferentes ramas y las hacen más prominentes para el desarrollador (por ejemplo, leyendo una función para captar el flujo del programa)

if ( i > 0 )
{
    throw new InvalidInputException(...);
}
else if ( i == 0 )
{
    return 0;
}
else
{
    log( "Calculating sqrt" );
    return sqrt( -i );
}
    
respondido por el Falco 27.07.2016 - 13:00
1

Como ya lo ha dicho Enderland, está asumiendo que solo tiene un "retorno" como acción y que puede etiquetar ese "retorno" al final de la condición. Me gustaría dar algunos detalles adicionales de por qué esto no va a tener éxito.

No sé cuáles son sus idiomas preferidos, pero he estado codificando en C durante mucho tiempo. Hay una serie de estándares de codificación cuyo objetivo es evitar algunos errores de codificación estándar al rechazar las construcciones de código que son propensas a errores, ya sea en la codificación inicial o durante el mantenimiento posterior. Estoy más familiarizado con MISRA-C, pero hay otros, y en general todos tienen reglas similares porque están abordando los mismos problemas en el mismo idioma.

Un error popular que los estándares de codificación a menudo abordan es este pequeño error: -

if (x == 10)
    do_something();
    do_something_else();

Esto no hace lo que crees que hace. En lo que respecta a C, si x es 10, se llama a do_something() , pero luego a do_something_else() se le llama independientemente del valor de x . Solo la acción que sigue inmediatamente a la sentencia "if" es condicional. Esto puede ser lo que pretendía el codificador, en cuyo caso hay una trampa potencial para los mantenedores; o puede que no sea lo que pretendía el codificador, en cuyo caso hay un error. Es una pregunta de entrevista popular.

La solución en los estándares de codificación es imponer llaves alrededor de todas las acciones condicionales, incluso si son de una sola línea. Ahora tenemos

if (x == 10)
{
    do_something();
    do_something_else();
}

o

if (x == 10)
{
    do_something();
}
do_something_else();

y ahora funciona correctamente y está claro para los mantenedores.

Notará que esto es completamente incompatible con su formato de estilo de tabla.

Algunos otros idiomas (por ejemplo, Python) observaron este problema y decidieron que dado que los programadores estaban usando espacios en blanco para aclarar el diseño, sería una buena idea usar espacios en blanco en lugar de llaves. Así que en Python,

if x == 10:
    do_something()
    do_something_else()

condiciona las llamadas tanto a do_something() como a do_something_else() en x == 10, mientras que

if x == 10:
    do_something()
do_something_else()

significa que solo do_something() es condicional en x, y do_something_else() siempre se llama.

Es un concepto válido, y encontrarás que algunos idiomas lo usan. (Lo vi por primera vez en Occam2, hace mucho tiempo cuando.) Sin embargo, una vez más, puedes ver fácilmente que tu formato de estilo de tabla es incompatible con el idioma.

    
respondido por el Graham 27.07.2016 - 13:53
1

El diseño tabular puede ser útil en algunos casos limitados, pero hay pocas veces con las que es útil.

¿En casos simples?: puede ser una mejor opción. En los casos medios, un interruptor es a menudo un ajuste mejor (si su idioma lo tiene). En casos complicados, es posible que las tablas de llamadas se ajusten mejor.

Ha habido muchas veces en las que el código de refactorización lo he reorganizado para que sea tabular para que sea obvio. Rara vez es así que lo dejo así porque en la mayoría de los casos hay una mejor manera de resolver el problema una vez que lo entiendes. Ocasionalmente, una práctica de codificación o un estándar de diseño lo prohíbe, en cuyo caso un comentario es útil.

Hubo algunas preguntas sobre ?: . Sí, es el operador ternario (o, como me gusta pensar en ello, el valor si). a primera vista, este ejemplo es un poco complicado para?: (y el uso excesivo?: no ayuda a la legibilidad pero le duele), pero con un poco de reflexión El ejemplo se puede reorganizar como se muestra a continuación, pero creo que en este caso el cambio es el más legible solución.

if i==0: return 0
return i>0?sqrt(i):(1j*sqrt(-i))
    
respondido por el hildred 27.07.2016 - 02:40
-3

No veo nada malo con el formato de tabla. Preferencia personal, pero usaría un ternario como este:

return i>0  ? sqrt(i)       :
       i==0 ? 0             :
              1j * sqrt(-i)

No es necesario repetir return todas las veces :)

    
respondido por el Navin 27.07.2016 - 17:33

Lea otras preguntas en las etiquetas