Mi estilo de codificación para llamadas a funciones anidadas es el siguiente:
var result_h1 = H1(b1);
var result_h2 = H2(b2);
var result_g1 = G1(result_h1, result_h2);
var result_g2 = G2(c1);
var a = F(result_g1, result_g2);
Recientemente me cambié a un departamento donde se usa mucho el siguiente estilo de codificación:
var a = F(G1(H1(b1), H2(b2)), G2(c1));
El resultado de mi forma de codificar es que, en caso de una función de bloqueo, Visual Studio puede abrir el volcado correspondiente e indicar la línea donde se produce el problema (estoy especialmente preocupado por las violaciones de acceso).
Me temo que, en caso de un bloqueo debido al mismo problema programado en la primera forma, no podré saber qué función ha causado el bloqueo.
Por otro lado, cuanto más procesamiento ponga en una línea, más lógica obtendrá en una página, lo que mejora la legibilidad.
¿Es correcto mi miedo o me falta algo y, en general, cuál es el preferido en un entorno comercial? ¿Legibilidad o mantenibilidad?
No sé si es relevante, pero estamos trabajando en C ++ (STL) / C #.
fuente
HX
yGX
las invocaciones puede cambiar en el de una sola línea, como El orden de evaluación de los argumentos de la función no está especificado. Si por alguna razón depende del orden de los efectos secundarios (a sabiendas o sin saberlo) en las invocaciones, esta "refactorización de estilo" podría terminar afectando más que la legibilidad / mantenimiento.result_g1
lo que realmente usarías o este valor realmente representa algo con un nombre sensible? por ejpercentageIncreasePerSecond
. Esa sería mi prueba para decidir entre los dosRespuestas:
Si te sentiste obligado a expandir un trazador de líneas como
No te culpo. Eso no solo es difícil de leer, es difícil de depurar.
¿Por qué?
Si lo expandes con resultados intermedios obtienes
y aún es difícil de leer ¿Por qué? Resuelve dos de los problemas e introduce un cuarto:
Es densoAlgunos depuradores solo resaltarán todo de una vezSi lo expande con nombres que agreguen un significado semántico nuevo, bueno, ¡incluso mejor! Un buen nombre me ayuda a entender.
Ahora al menos esto cuenta una historia. Soluciona los problemas y es claramente mejor que cualquier otra cosa que se ofrezca aquí, pero requiere que se te ocurran los nombres.
Si lo hace con nombres sin sentido como
result_this
yresult_that
porque simplemente no puede pensar en buenos nombres, entonces realmente preferiría que nos ahorre el desorden de nombres sin sentido y lo expanda usando un buen espacio en blanco:Es igual de legible, si no más, que el que tiene nombres de resultados sin sentido (no es que estos nombres de funciones sean tan geniales).
Es densoAlgunos depuradores solo resaltarán todo de una vezEstá lleno de nombres no descriptivosCuando no se te ocurren buenos nombres, es tan bueno como parece.
Por alguna razón, a los depuradores les encantan las nuevas líneas, por lo que debería encontrar que depurar esto no es difícil:
Si eso no es suficiente, imagine que
G2()
se llamó en más de un lugar y esto sucedió:Creo que es bueno que, dado que cada
G2()
llamada estaría en su propia línea, este estilo lo lleva directamente a la llamada infractora en general.Por lo tanto, no use los problemas 1 y 2 como excusa para seguir con el problema 4. Use buenos nombres cuando pueda pensar en ellos. Evite nombres sin sentido cuando no pueda.
Lightness Races en el comentario de Orbit señala correctamente que estas funciones son artificiales y tienen nombres muy pobres. Así que aquí hay un ejemplo de cómo aplicar este estilo a algún código de la naturaleza:
Odio mirar ese flujo de ruido, incluso cuando no es necesario ajustar las palabras. Así es como se ve bajo este estilo:
Como puede ver, descubrí que este estilo funciona bien con el código funcional que se mueve en el espacio orientado a objetos. Si puedes encontrar buenos nombres para hacer eso en un estilo intermedio, entonces tienes más poder para ti. Hasta entonces estoy usando esto. Pero en cualquier caso, por favor, encuentre alguna forma de evitar nombres de resultados sin sentido. Hacen que me duelan los ojos.
fuente
Estoy totalmente en desacuerdo con esto. Solo mirar sus dos ejemplos de código dice que esto es incorrecto:
Se escucha leer. "Legibilidad" no significa densidad de información; significa "fácil de leer, comprender y mantener".
A veces, el código es simple y tiene sentido usar una sola línea. Otras veces, hacerlo solo hace que sea más difícil de leer, sin ningún beneficio obvio más allá de agrupar más en una línea.
Sin embargo, también lo llamaría al afirmar que "los bloqueos fáciles de diagnosticar" significan que el código es fácil de mantener. El código que no falla es mucho más fácil de mantener. "Fácil de mantener" se logra principalmente a través del código, fácil de leer y comprender, respaldado con un buen conjunto de pruebas automatizadas.
Entonces, si está convirtiendo una sola expresión en una de varias líneas con muchas variables solo porque su código a menudo falla y necesita una mejor información de depuración, entonces deje de hacerlo y haga que el código sea más robusto. Debería preferir escribir código que no necesite depuración sobre código que sea fácil de depurar.
fuente
F(G1(H1(b1), H2(b2)), G2(c1))
es difícil de leer, esto no tiene nada que ver con estar demasiado abarrotado. (No estoy seguro si quería decir eso, pero podría interpretarse de esta manera). Anidar tres o cuatro funciones en una sola línea puede ser perfectamente legible, en particular si algunas de las funciones son simples operadores infijos. El problema aquí son los nombres no descriptivos, pero ese problema es aún peor en la versión de varias líneas, donde se introducen aún más nombres no descriptivos . Agregar solo repetitivo casi nunca ayuda a la legibilidad.G1
toma 3 parámetros o solo 2 yG2
es otro parámetro paraF
. Tengo que entrecerrar los ojos y contar los paréntesis.F (G1 (H1 b1) (H2 b2)) (G2 c1)
.)result_h1
no se puede reutilizar si no existe, y la plomería entre las 4 variables es obvio.Su primer ejemplo, la forma de asignación única, es ilegible porque los nombres elegidos no tienen ningún significado. Eso podría ser un artefacto de tratar de no revelar información interna de su parte, el verdadero código podría estar bien a ese respecto, no podemos decirlo. De todos modos, es de largo aliento debido a la densidad de información extremadamente baja, que generalmente no se presta para una fácil comprensión.
Su segundo ejemplo está condensado en un grado absurdo. Si las funciones tenían nombres útiles, eso podría estar bien y ser legible porque no hay demasiado , pero tal como es es confuso en la otra dirección.
Después de introducir nombres significativos, puede observar si una de las formas parece natural, o si hay un medio dorado para disparar.
Ahora que tiene un código legible, la mayoría de los errores serán obvios, y los otros al menos tendrán más dificultades para esconderse de usted.
fuente
Como siempre, cuando se trata de legibilidad, el fracaso es extremo . Puedes tomar cualquier buen consejo de programación, convertirlo en una regla religiosa y usarlo para producir código completamente ilegible. (Si no me cree en esto, vea a estos dos ganadores de IOCCC , borsanyi y goren, y eche un vistazo a cuán diferente usan las funciones para hacer que el código sea completamente ilegible. Sugerencia: Borsanyi usa exactamente una función, mucho, mucho más ...)
En su caso, los dos extremos son 1) usar solo declaraciones de expresión única y 2) unir todo en declaraciones grandes, concisas y complejas. Cualquiera de los enfoques llevados al extremo hace que su código sea ilegible.
Su tarea, como programador, es lograr un equilibrio . Para cada declaración que escriba, es su tarea responder la pregunta: "¿Es esta declaración fácil de entender y sirve para que mi función sea legible?"
El punto es que no existe una única complejidad de declaración medible que pueda decidir qué es bueno incluir en una sola declaración. Tome por ejemplo la línea:
Esta es una declaración bastante compleja, pero cualquier programador que valga la pena debería poder comprender de inmediato lo que hace. Es un patrón bastante conocido. Como tal, es mucho más legible que el equivalente
que rompe el conocido patrón en un número aparentemente sin sentido de pasos simples. Sin embargo, la declaración de su pregunta
me parece demasiado complicado, aunque es una operación menos que el cálculo de distancia . Por supuesto, eso es una consecuencia directa de mí sin saber nada acerca de
F()
,G1()
,G2()
,H1()
, oH2()
. Podría decidir de manera diferente si supiera más sobre ellos. Pero ese es precisamente el problema: la complejidad aconsejable de una declaración depende en gran medida del contexto y de las operaciones involucradas. Y usted, como programador, es el que debe analizar este contexto y decidir qué incluir en una sola declaración. Si le importa la legibilidad, no puede descargar esta responsabilidad a alguna regla estática.fuente
@Dominique, creo que en el análisis de su pregunta, está cometiendo el error de que "legibilidad" y "mantenibilidad" son dos cosas separadas.
¿Es posible tener un código que sea mantenible pero ilegible? Por el contrario, si el código es extremadamente legible, ¿por qué sería imposible de mantener debido a que es legible? ¡Nunca he oído hablar de ningún programador que se haya enfrentado a estos factores, teniendo que elegir uno u otro!
En términos de decidir si usar variables intermedias para llamadas a funciones anidadas, en el caso de 3 variables dadas, llamadas a 5 funciones separadas y algunas llamadas anidadas a 3 profundas, tendería a usar al menos algunas variables intermedias para desglosar eso, como has hecho
Pero ciertamente no voy tan lejos como para decir que las llamadas a funciones nunca deben anidarse en absoluto. Es una cuestión de juicio en las circunstancias.
Diría que los siguientes puntos se refieren a la sentencia:
Si las funciones llamadas representan operaciones matemáticas estándar, son más capaces de anidarse que las funciones que representan alguna lógica de dominio oscura cuyos resultados son impredecibles y no necesariamente pueden ser evaluados mentalmente por el lector.
Una función con un solo parámetro es más capaz de participar en un nido (ya sea como una función interna o externa) que una función con múltiples parámetros. Mezclar funciones de diferentes aries en diferentes niveles de anidación es propenso a dejar el código como la oreja de un cerdo.
Un conjunto de funciones que los programadores están acostumbrados a ver expresado de una manera particular, tal vez porque representa una técnica o ecuación matemática estándar, que tiene una implementación estándar, puede ser más difícil de leer y verificar si se divide en variables intermedias.
Un pequeño nido de llamadas de función que realiza una funcionalidad simple y que ya es fácil de leer, y luego se descompone en exceso y se atomiza, es capaz de ser más difícil de leer que uno que no se descompuso en absoluto.
fuente
Ambos son subóptimos. Considere los comentarios.
O funciones específicas en lugar de generales:
Al decidir qué resultados detallar, tenga en cuenta el costo (copia vs referencia, valor l versus valor r), legibilidad y riesgo, individualmente para cada declaración.
Por ejemplo, no hay valor agregado al mover conversiones simples de unidad / tipo a sus propias líneas, porque son fáciles de leer y es muy improbable que fallen:
Con respecto a su preocupación por analizar volcados de memoria, la validación de entrada suele ser mucho más importante: es muy probable que la falla real ocurra dentro de estas funciones en lugar de la línea que las llama, e incluso si no, generalmente no necesita que se le diga exactamente dónde Las cosas explotaron. Es mucho más importante saber dónde las cosas comenzaron a desmoronarse, que saber dónde explotaron finalmente, que es lo que atrapa la validación de entrada.
fuente
La legibilidad es la mayor parte de la mantenibilidad. ¿Duda de mí? Elija un proyecto grande en un idioma que no conoce (preferiblemente tanto el lenguaje de programación como el lenguaje de los programadores), y vea cómo haría para refactorizarlo ...
Pondría legibilidad como entre 80 y 90 de mantenibilidad. El otro 10-20 por ciento es cuán susceptible es refactorizar.
Dicho esto, efectivamente pasas 2 variables a tu función final (F). Esas 2 variables se crean usando otras 3 variables. Habría sido mejor pasar b1, b2 y c1 a F, si F ya existe, cree D que haga la composición para F y devuelva el resultado. En ese momento, solo es cuestión de darle un buen nombre a D, y no importará qué estilo uses.
En un no relacionado, usted dice que más lógica en la página ayuda a la legibilidad. Eso es incorrecto, la métrica no es la página, es el método, y la MENOS lógica que contiene un método, más legible es.
Legible significa que el programador puede mantener la lógica (entrada, salida y algoritmo) en su cabeza. Cuanto más lo hace, MENOS puede entenderlo un programador. Lea sobre la complejidad ciclomática.
fuente
Independientemente de si está en C # o C ++, siempre que esté en una compilación de depuración, una posible solución es envolver las funciones
Puede escribir expresiones en línea y aún así señalar dónde está el problema simplemente mirando el rastro de la pila.
Por supuesto, si llama a la misma función varias veces en la misma línea, no puede saber qué función, sin embargo, aún puede identificarla:
Esto no es una bala de plata, pero no es tan malo a mitad de camino.
Sin mencionar que el grupo de funciones de ajuste puede incluso ser más beneficioso para la legibilidad del código:
fuente
En mi opinión, el código autodocumentado es mejor tanto para la mantenibilidad como para la legibilidad, independientemente del idioma.
La declaración dada anteriormente es densa, pero "auto documentada":
Cuando se divide en etapas (más fácil de probar, seguramente) pierde todo el contexto como se indicó anteriormente:
Y obviamente, el uso de nombres de variables y funciones que indiquen claramente su propósito es invaluable.
Incluso los bloques "si" pueden ser buenos o malos en la autodocumentación. Esto es malo porque no puede forzar fácilmente las primeras 2 condiciones para probar la tercera ... no están relacionadas:
Éste tiene más sentido "colectivo" y es más fácil crear condiciones de prueba:
Y esta declaración es solo una cadena de caracteres aleatoria, vista desde una perspectiva autodocumentada:
Mirando la declaración anterior, la mantenibilidad sigue siendo un desafío importante si las funciones H1 y H2 alteran las mismas "variables de estado del sistema" en lugar de unificarse en una sola función "H", porque alguien eventualmente alterará H1 sin siquiera pensar que hay un Función H2 para mirar y podría romper H2.
Creo que un buen diseño de código es muy desafiante porque no hay reglas estrictas que puedan detectarse y aplicarse sistemáticamente.
fuente