Contexto
Recientemente me interesé en producir un código mejor formateado. Y por mejor quiero decir "seguir las reglas respaldadas por suficientes personas para considerarlo una buena práctica" (ya que, por supuesto, nunca habrá una "mejor" forma única de codificar).
En estos días, codifico principalmente en Ruby, así que comencé a usar un linter (Rubocop) para proporcionarme información sobre la "calidad" de mi código (esta "calidad" está definida por la guía de estilo ruby del proyecto impulsado por la comunidad ).
Tenga en cuenta que usaré "calidad" como en "calidad del formateo", no tanto sobre la eficiencia del código, incluso si en algunos casos, la eficiencia del código en realidad se ve afectada por la forma en que se escribe el código.
De todos modos, al hacer todo eso, me di cuenta (o al menos recordé) algunas cosas:
- Algunos lenguajes (más notablemente Python, Ruby y otros) permiten crear código de líneas geniales.
- Seguir algunas pautas para su código puede hacerlo significativamente más corto y aún así muy claro
- Sin embargo, seguir estas pautas demasiado estrictamente puede hacer que el código sea menos claro / fácil de leer.
- El código puede respetar algunas pautas casi a la perfección y aún ser de baja calidad.
- La legibilidad del código es principalmente subjetiva (como en "lo que encuentro claro podría ser completamente oscuro para un desarrollador desarrollador")
Esas son solo observaciones, no reglas absolutas, por supuesto. También notará que la legibilidad del código y las siguientes pautas pueden parecer ajenas en este punto, pero aquí las pautas son una forma de reducir la cantidad de formas de reescribir un fragmento de código.
Ahora, algunos ejemplos, para aclarar todo eso.
Ejemplos
Tomemos un caso de uso simple: tenemos una aplicación con un " User
" modelo. Un usuario tiene una dirección opcional firstname
y surname
obligatoria email
.
Quiero escribir un método " name
" que devolverá entonces el nombre ( firstname + surname
) del usuario si al menos su firstname
o surname
está presente, o su email
como un valor de retorno en caso contrario.
También quiero que este método tome un " use_email
" como parámetro (booleano), lo que permite utilizar el correo electrónico del usuario como valor de reserva. Este " use_email
" parámetro debe ser predeterminado (si no se pasa) como " true
".
La forma más simple de escribir eso, en Ruby, sería:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
Este código es la forma más simple y fácil de entender. Incluso para alguien que no "habla" Ruby.
Ahora intentemos hacerlo más corto:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
Esto es más corto, aún fácil de entender, si no más fácil (la cláusula de protección es más natural de leer que un bloque condicional). La cláusula de protección también lo hace más compatible con las pautas que estoy usando, así que gane-gane aquí. También reducimos el nivel de sangría.
Ahora usemos algo de magia Ruby para hacerlo aún más corto:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
Incluso más corto y siguiendo las pautas perfectamente ... pero mucho menos claro ya que la falta de declaración de devolución hace que sea un poco confuso para aquellos que no están familiarizados con esta práctica.
Es aquí donde podemos comenzar a hacer la pregunta: ¿realmente vale la pena? Deberíamos decir "no, hacerlo legible y agregar ' return
'" (sabiendo que esto no respetará las pautas). ¿O deberíamos decir "Está bien, es la forma de Ruby, aprende el maldito idioma"?
Si tomamos la opción B, ¿por qué no hacerlo aún más corto?
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Aquí está, el one-liner! Por supuesto, es más corto ... aquí aprovechamos el hecho de que Ruby devolverá un valor u otro dependiendo de cuál esté definido (ya que el correo electrónico se definirá en las mismas condiciones que antes).
También podemos escribirlo:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
Es breve, no es tan difícil de leer (quiero decir, todos hemos visto lo que puede parecer un trazador de líneas feo), bueno Ruby, cumple con la directriz que uso ... Pero aún así, en comparación con la primera forma de escribir es mucho menos fácil de leer y entender. También podemos argumentar que esta línea es demasiado larga (más de 80 caracteres).
Pregunta
Algunos ejemplos de código pueden mostrar que elegir entre un código de "tamaño completo" y muchas de sus versiones reducidas (hasta el famoso one-liner) puede ser difícil ya que, como podemos ver, los one-liners no pueden ser tan temibles, pero aún así, nada superará el código de "tamaño completo" en términos de legibilidad ...
Así que aquí está la verdadera pregunta: ¿dónde parar? ¿Cuándo es corto, lo suficientemente corto? ¿Cómo saber cuándo el código se vuelve "demasiado corto" y menos legible (teniendo en cuenta que es bastante subjetivo)? Y aún más: ¿cómo codificar siempre en consecuencia y evitar mezclar frases con trozos de código "de tamaño completo" cuando me da la gana?
TL; DR
La pregunta principal aquí es: cuando se trata de elegir entre un "fragmento de código largo pero claro, legible y comprensible" y un "poderoso, más corto pero más difícil de leer / entender", sabiendo que esos dos son los mejores y los mejores. fondo de una escala y no las dos únicas opciones: ¿cómo definir dónde está la frontera entre "lo suficientemente claro" y "no tan claro como debería ser"?
La pregunta principal no es la clásica "One-liners vs. legibilidad: ¿cuál es mejor?" pero "¿Cómo encontrar el equilibrio entre esos dos?"
Editar 1
Los comentarios en los ejemplos de código están destinados a ser "ignorados", están aquí para aclarar lo que está sucediendo, pero no deben tenerse en cuenta al evaluar la legibilidad del código.
fuente
return
palabra clave agregada . Esos siete personajes agregan bastante claridad en mis ojos.[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... porquefalse.blank?
devuelve verdadero y el operador ternario te ahorra algunos caracteres ... ¯ \ _ (ツ) _ / ¯return
supone que debe agregar la palabra clave? No proporciona información alguna . Es puro desorden.Respuestas:
No importa qué código escriba, legible es lo mejor. Corto es el segundo mejor. Por lo general, legible significa lo suficientemente breve como para que pueda entender el código, los identificadores bien nombrados y adherirse a los modismos comunes del idioma en el que está escrito el código.
Si esto fuera un lenguaje agnóstico, creo que definitivamente estaría basado en una opinión, pero dentro de los límites del lenguaje Ruby, creo que podemos responderlo.
Primero, una característica y una forma idiomática de escribir Ruby es omitir la
return
palabra clave cuando devuelve un valor, a menos que regrese temprano de un método.Otra característica e idioma combinados es el uso de
if
declaraciones finales para aumentar la legibilidad del código. Una de las ideas de manejo en Ruby es escribir código que se lea como lenguaje natural. Para esto, vamos a _why's Poignant Guide to Ruby, Chapter 3 .Dado esto, el ejemplo de código # 3 es más idiomático para Ruby:
Ahora, cuando leemos el código, dice:
Lo cual está bastante cerca del código real de Ruby.
Es solo 2 líneas de código real, por lo que es bastante breve y se adhiere a los modismos del lenguaje.
fuente
use_email
las otras condiciones ya que es una variable en lugar de una llamada a la función. Pero, de nuevo, la interpolación de cuerdas inunda la diferencia de todos modos.do send an email if A, B, C but no D
, seguir su premisa sería natural escribir 2 bloques if / else , cuando probablemente sería más fácil de codificarif not D, send an email
. Tenga cuidado al momento de leer el lenguaje natural y transformelo en código porque puede hacer que escriba una nueva versión de la "Historia interminable" . Con clases, métodos y variables. No es gran cosa después de todo.if !D
es mejor, está bien que LondresD
tenga un nombre significativo. Y si el!
operador se pierde entre el otro código,NotD
sería apropiado tener un identificador llamado .No creo que obtenga una mejor respuesta que "use su mejor juicio". En resumen, debe luchar por la claridad en lugar de la brevedad . A menudo, el código más corto también es el más claro, pero si te enfocas solo en lograr la brevedad, la claridad puede verse afectada. Este es claramente el caso en los últimos dos ejemplos, que requiere más esfuerzo para comprender que los tres ejemplos anteriores.
Una consideración importante es la audiencia del código. La legibilidad, por supuesto, depende totalmente de la persona que lee. ¿Las personas que espera que lean el código (además de usted) realmente conocen los modismos del lenguaje Ruby? Bueno, esta pregunta no es algo que las personas al azar en Internet puedan responder, esta es solo su propia decisión.
fuente
Parte del problema aquí es "qué es la legibilidad". Para mí, miro tu primer ejemplo de código:
Y me resulta difícil de leer ya que está lleno de comentarios "ruidosos" que simplemente repiten el código. Pelarlos:
y ahora es mucho más legible. Al leerlo, pienso "hmm, me pregunto si Ruby es compatible con el operador ternario. En C #, puedo escribirlo como:
¿Es posible algo así en Ruby? Trabajando en tu publicación, veo que hay:
Todas las cosas buenas Pero eso no es legible para mí; simplemente porque tengo que desplazarme para ver toda la línea. Así que arreglemos eso:
Ahora estoy feliz. No estoy completamente seguro de cómo funciona la sintaxis, pero puedo entender lo que hace el código.
Pero solo soy yo. Otras personas tienen ideas muy diferentes sobre lo que hace que sea un código agradable de leer. Por lo tanto, debe conocer a su audiencia al escribir código. Si eres un principiante absoluto, entonces querrás que sea simple y posiblemente escribirlo como tu primer ejemplo. Si trabaja entre un conjunto de desarrolladores profesionales con muchos años de experiencia en ruby, escriba código que aproveche el lenguaje y que sea breve. Si está en algún punto intermedio, entonces busca un punto intermedio.
Sin embargo, una cosa diría: tenga cuidado con el "código inteligente", como en su último ejemplo. Pregúntate a ti mismo, ¿
[firstname, surname].all?(&:blank?)
agrega algo más que hacerte sentir inteligente porque muestra tus habilidades, incluso si ahora es un poco más difícil de leer? Yo diría que este ejemplo probablemente se incluye en esa categoría. Sin embargo, si comparara cinco valores, lo vería como un buen código. Entonces, de nuevo, no hay una línea absoluta aquí, solo ten en cuenta que eres demasiado inteligente.En resumen: la legibilidad requiere que conozca a su audiencia y apunte su código en consecuencia y escriba un código breve pero claro; nunca escriba código "inteligente". Mantenlo corto, pero no demasiado corto.
fuente
return "#{firstname} #{surname}".strip
Esta es probablemente una pregunta en la que es difícil no dar una respuesta basada en una opinión, pero aquí están mis dos centavos.
Si encuentra que acortar el código no afecta la legibilidad, o incluso lo mejora, hágalo. Si el código se vuelve menos legible, entonces debe considerar si hay una razón bastante buena para dejarlo así. Hacerlo solo porque es más corto o genial, o simplemente porque puedes, son ejemplos de malas razones. También debe considerar si acortar el código lo haría menos comprensible para otras personas con las que trabaja.
Entonces, ¿cuál sería una buena razón? Realmente es una decisión, pero un ejemplo podría ser algo así como una optimización del rendimiento (después de las pruebas de rendimiento, por supuesto, no de antemano). Algo que le brinda algún beneficio que está dispuesto a pagar con una menor legibilidad. En ese caso, puede mitigar el inconveniente proporcionando un comentario útil (que explique qué hace el código y por qué tuvo que ser un poco críptico). Aún mejor, puede extraer ese código en una función separada con un nombre significativo, de modo que sea solo una línea en el sitio de la llamada que explica lo que está sucediendo (a través del nombre de la función) sin entrar en detalles (sin embargo, las personas tienen diferencias opiniones sobre esto, por lo que esta es otra decisión que debe hacer).
fuente
La respuesta es un poco subjetiva, pero tienes que preguntarte con toda la honestidad que puedas reunir, si pudieras entender ese código cuando regreses a él en un mes o dos.
Cada cambio debería mejorar la capacidad de la persona promedio para comprender el código. Para que el código sea comprensible, es útil usar las siguientes pautas:
Dicho esto, hay momentos en que el código expandido ayuda a comprender mejor lo que está sucediendo. Un ejemplo con eso proviene de C # y LINQ. LINQ es una gran herramienta y puede mejorar la legibilidad en algunas situaciones, pero también me he encontrado con varias situaciones en las que era mucho más confuso. He recibido algunos comentarios en la revisión por pares que sugirió convertir la expresión en un bucle con declaraciones if apropiadas para que otros puedan mantenerla mejor. Cuando cumplí, tenían razón. Técnicamente, LINQ es más idiomático para C #, pero hay casos en los que degrada la comprensibilidad y una solución más detallada lo mejora.
Digo todo eso para decir esto:
Recuerde, usted o alguien como usted tendrá que mantener ese código más adelante. La próxima vez que te encuentres, podrían pasar meses. Hazte un favor y no persigas la reducción de los recuentos de líneas a costa de poder entender tu código.
fuente
La legibilidad es una propiedad que desea tener, y tener muchas líneas no lo es. Entonces, en lugar de "one-liners vs legibilidad" la pregunta debería ser:
¿Cuándo aumentan la legibilidad los one-liners y cuándo la dañan?
Creo que las frases sencillas son buenas para la legibilidad cuando cumplen estas dos condiciones:
Por ejemplo, digamos que
name
no fue un buen ... nombre para su método. Que combinar el nombre y el apellido, o usar el correo electrónico en lugar del nombre, no era algo natural. Entonces, en lugar dename
lo mejor que se te ocurrió, resultó largo y engorroso:Un nombre tan largo indica que es muy específico: si fuera más general, podría haber encontrado un nombre más general. Por lo tanto, envolverlo en un método no ayuda con la legibilidad (es demasiado larga) ni con DRYness (demasiado específico para usar en otro lugar), por lo que es mejor dejar el código allí.
Aún así, ¿por qué hacerlo de una sola línea? Por lo general, son menos legibles que el código multilínea. Aquí es donde deberíamos verificar mi segunda condición: el flujo del código circundante. ¿Qué pasa si tienes algo como esto?
El código multilínea en sí mismo es legible, pero cuando intenta leer el código circundante (imprimiendo los diversos campos) esa construcción multilínea está interrumpiendo el flujo. Esto es más fácil de leer:
Su flujo no se interrumpe, y puede enfocarse en la expresión específica si lo necesita.
¿Es este tu caso? ¡Definitivamente no!
La primera condición es menos relevante: ya consideró que es lo suficientemente general como para merecer un método, y se le ocurrió un nombre para ese método que es mucho más legible que su implementación. Obviamente no lo extraerías a una función nuevamente.
En cuanto a la segunda condición, ¿interrumpe el flujo del código circundante? ¡No! El código que lo rodea es una declaración de método, que elegir
name
es el único propósito. La lógica de elegir el nombre no está interrumpiendo el flujo del código circundante, ¡es el propósito mismo del código circundante!Conclusión: no haga que todo el cuerpo de la función sea de una sola línea
Las frases sencillas son buenas cuando quieres hacer algo un poco complejo sin interrumpir el flujo. Una declaración de función ya está interrumpiendo el flujo (para que no se interrumpa cuando se llama a esa función), por lo que hacer que todo el cuerpo de la función sea de una sola línea no ayuda a la legibilidad.
Nota
Me refiero a las funciones y métodos "completos", no a las funciones en línea o las expresiones lambda que generalmente son parte del código circundante y deben ajustarse a su flujo.
fuente