Código legible limpio vs código rápido difícil de leer. ¿Cuándo cruzar la línea?

67

Cuando escribo código, siempre trato de hacer mi código lo más limpio y legible posible.

De vez en cuando llega un momento en que necesita cruzar la línea y pasar de un código limpio agradable a un código un poco más feo para hacerlo más rápido.

¿Cuándo está bien cruzar esa línea?

Ken Cochrane
fuente
69
Respondiste tu propia pregunta, cruzas la línea cuando necesitas cruzar la línea
gnibbler
66
Además, su "código sucio" puede funcionar tan rápido como el "código limpio" en el hardware dentro de 6 meses. Sin embargo, no vaya por la borda como lo hizo Windows. :)
Mateen Ulhaq
21
Hay una diferencia significativa entre un algoritmo difícil de entender y un código difícil de entender. A veces, el algoritmo que necesita implementar es complicado, y el código necesariamente será confuso, simplemente porque expresa una idea compleja. Pero si el código en sí es el punto difícil, entonces el código debería ser reparado.
tylerl
8
En muchos casos, un compilador / intérprete inteligente puede optimizar el código limpio y legible para que tenga el mismo rendimiento que el código "feo". Por lo tanto, hay pocas excusas, a menos que el perfil indique lo contrario.
Dan Diplo
1
Cuando se trata de compiladores en estos días, su código feo probablemente será el mismo que su código limpio (suponiendo que no haga nada realmente extraño). Especialmente en .NET, no es como los días C ++ / MFC donde la forma en que define sus variables tendrá un impacto en el rendimiento. Escribir código mantenible. algunos códigos terminarán siendo complejos, pero eso no significa que sean feos.
DustinDavis

Respuestas:

118

Cruzas la línea cuando

  • Ha medido que su código es demasiado lento para su uso previsto .
  • Ha intentado mejoras alternativas que no requieren eliminar el código.

Aquí hay un ejemplo del mundo real: un sistema experimental que estoy ejecutando producía datos demasiado lentamente, tomaba más de 9 horas por ejecución y usaba solo el 40% de la CPU. En lugar de desordenar demasiado el código, moví todos los archivos temporales a un sistema de archivos en memoria. Se agregaron 8 nuevas líneas de código no feo, y ahora la utilización de la CPU está por encima del 98%. Problema resuelto; No se requiere fealdad.

Norman Ramsey
fuente
2
También se asegura de mantener el código original, más lento y limpio, tanto como una implementación de referencia como para recurrir cuando el hardware cambia y su código más rápido y pirateado ya no funciona.
Paul R
44
@PaulR ¿Cómo guardas ese código? En forma de comentarios? Eso está mal, en mi opinión: los comentarios quedan desactualizados, nadie los lee y, personalmente, si veo un código comentado, generalmente lo elimino; para eso está el control de código fuente. Un comentario sobre un método que explica lo que hace es mejor, en mi opinión.
Evgeni
55
@ Eugene: por lo general, mantengo la versión original de una rutina nombrada fooy renombrarla, por lo foo_refgeneral, se encuentra inmediatamente arriba fooen el archivo fuente. En mi instrumento de prueba que llamo fooy foo_refpara la validación y la medición del rendimiento relativo.
Paul R
55
@Paul si lo está haciendo, podría ser una buena idea no pasar la prueba si la versión optimizada es cada vez más lenta que la función de referencia. Esto puede suceder si las suposiciones que hiciste para acelerarlo ya no son ciertas.
user1852503
58

Es una falsa dicotomía. Puede hacer que el código sea rápido y fácil de mantener.

La forma de hacerlo es escribirlo limpio, especialmente con una estructura de datos tan simple como sea posible.

Luego descubres dónde están los drenajes de tiempo (ejecutándolo, después de haberlo escrito, no antes), y corríjalos uno por uno. (Aquí hay un ejemplo).

Agregado: Siempre escuchamos sobre compensaciones, ¿verdad, como una compensación entre tiempo y memoria, o una compensación entre velocidad y facilidad de mantenimiento? Si bien tales curvas pueden existir, no se debe suponer que un programa determinado está en la curva , o incluso en cualquier lugar cerca de ella.

Cualquier programa que esté en la curva puede fácilmente (dándole a un cierto tipo de programador) ser mucho más lento y mucho menos sostenible, y entonces no estará cerca de la curva. Dicho programa tiene mucho espacio para ser más rápido y más fácil de mantener.

En mi experiencia, ahí es donde comienzan muchos programas.

Mike Dunlavey
fuente
Estoy de acuerdo. De hecho, el código rápido que no está limpio se volverá más lento con el tiempo, ya que no puede modificarlo adecuadamente.
edA-qa mort-ora-y
22
No estoy de acuerdo con que sea una falsa dicotomía; En mi opinión, hay escenarios especialmente en el código de la biblioteca (no tanto en el código de la aplicación ) donde la división es muy real. Vea mi respuesta para más.
Marc Gravell
1
Marc, puedes enlazar a las respuestas en los comentarios con la URL de "enlace". programmers.stackexchange.com/questions/89620/…
Todos hemos encontrado momentos en los que necesitamos probar y hacer que el código vaya más rápido. Pero después de experimentar con el generador de perfiles para encontrar la mejor solución (si el código se pone feo), esto no significa que el código deba permanecer feo. Se trata de encontrar la mejor solución que al principio puede no parecer obvia, pero una vez que se encuentra, generalmente se puede codificar limpiamente. Por lo tanto, creo que es una dicotomía falsa y solo una excusa para no ordenar su habitación después de divertirse con sus juguetes. Digo que aguanta y ordena tu habitación.
Martin York
No estoy de acuerdo con que sea una falsa dicotomía. Debido a que hago muchos trabajos gráficos, el ejemplo más obvio para mí es en lazos de gráficos ajustados: no sé con qué frecuencia esto todavía se hace, pero solía ser común que los motores de juegos escritos en C usen ensamblado para la representación central bucles para exprimir hasta la última gota de velocidad. Eso también me hace pensar en situaciones en las que programa en Python pero usa módulos escritos en C ++. "Difícil de leer" siempre es relativo; cada vez que pasa a un lenguaje de nivel inferior para mayor velocidad, ese código es más difícil de leer que el resto.
jhocking
31

En mi existencia de OSS, realizo una gran cantidad de trabajo en la biblioteca dirigido al rendimiento, que está profundamente vinculado a la estructura de datos de la persona que llama (es decir, externa a la biblioteca), sin (por diseño) ningún mandato sobre los tipos entrantes. Aquí, la mejor manera de hacer este rendimiento es la metaprogramación, que (dado que estoy en .NET-land) significa emisión de IL. Es un código feo, feo, pero muy rápido.

De esta manera, acepto que el código de la biblioteca puede ser "más feo" que el código de la aplicación , simplemente porque tiene menos (o quizás no) control sobre las entradas , por lo que necesita realizar algunas tareas a través de diferentes mecanismos. O como lo expresé el otro día:

"codificando sobre el acantilado de la locura, para que no tengas que "

Ahora el código de la aplicación es ligeramente diferente, ya que es allí donde los desarrolladores "normales" (sanos) suelen invertir gran parte de su tiempo colaborativo / profesional; Los objetivos y expectativas de cada uno son (IMO) ligeramente diferentes.

En mi opinión, las respuestas anteriores que sugieren que puede ser rápido y fácil de mantener se refieren al código de la aplicación donde el desarrollador tiene más control sobre las estructuras de datos y no utiliza herramientas como la metaprogramación. Dicho esto, hay diferentes formas de hacer metaprogramación, con diferentes niveles de locura y diferentes niveles de sobrecarga. Incluso en ese ámbito, debe elegir el nivel apropiado de abstracción. Pero cuando quiere de manera activa, positiva y genuina que maneje datos inesperados de la manera más rápida; bien puede ponerse feo. Tratar con eso; p

Marc Gravell
fuente
44
El hecho de que el código sea feo no significa que deba ser imposible de mantener. Los comentarios y la sangría son gratuitos, y el código feo generalmente se puede encapsular en una entidad manejable (clase, módulo, paquete, función, dependiendo del idioma). El código puede ser igualmente feo, pero al menos las personas podrán juzgar el impacto de los cambios que están a punto de hacerle.
tdammers 05 de
66
@tdammers de hecho, y trato de hacerlo lo más lejos posible; pero es un poco como poner lápiz labial en un cerdo.
Marc Gravell
1
Bueno, tal vez uno debería hacer una distinción entre la sintaxis fea y los algoritmos feos: los algoritmos feos a veces son necesarios, pero la sintaxis fea generalmente es IMO inexcusable.
tdammers 05 de
44
La sintaxis fea de @IMO es bastante inevitable si lo que está haciendo es, por naturaleza, varios niveles de abstracción debajo del nivel de lenguaje habitual.
Marc Gravell
1
@marc ... eso es interesante. Mi primera reacción a que el meta / resumen sea feo fue la sospecha de que el lenguaje / plataforma específica no es propicio para la metacodificación en lugar de alguna ley subyacente que conecte los dos. Lo que me hizo creer que fue el ejemplo de metalevel progresivo en matemáticas que termina en teoría de conjuntos cuya expresión es apenas más fea que el álgebra o incluso la aritmética concreta. Pero entonces, la notación de conjunto es probablemente un idioma completamente diferente y cada nivel de abstracción debajo tiene su propio idioma ...
explore el
26

Cuando ha perfilado el código y verificado que en realidad está causando una desaceleración significativa.

Joel Rein
fuente
3
¿Y qué es "significativo"?
Torre
2
@ hotpaw2: es la respuesta inteligente: supone que los desarrolladores son al menos algo competitivos. De lo contrario, sí, usar algo más rápido que el tipo burbuja es (generalmente) una buena idea. Pero con demasiada frecuencia alguien (para seguir clasificando) cambiará el ordenamiento rápido por el montón por una diferencia del 1%, solo para ver a alguien más cambiarlo seis meses después por la misma razón.
1
No es que nunca una razón para hacer que el código no limpia. Si no puede hacer que su código eficiente sea limpio y fácil de mantener, está haciendo algo mal.
edA-qa mort-ora-y
2
@SF. - el cliente siempre lo encontrará demasiado lento, si puede ser más rápido. No le importa la "limpieza" del código.
Torre
1
@Rook: El cliente puede encontrar el código de interfaz (trivial) demasiado lento. Algunos trucos psicológicos bastante simples mejoran la experiencia del usuario sin acelerar el código: diferir los eventos de botón a una rutina en segundo plano en lugar de realizar las acciones sobre la marcha, mostrar una barra de progreso o algo así, hacer algunas preguntas insignificantes mientras la actividad se realiza en segundo plano ... es cuando esto no es suficiente, puede considerar la optimización real.
SF.
13

El código limpio no es necesariamente exclusivo con el código de ejecución rápida. Normalmente, el código difícil de leer se escribió porque era más rápido de escribir, no porque se ejecuta más rápido.

Escribir código "sucio" en un intento de hacerlo más rápido es posiblemente imprudente, ya que no está seguro de que sus cambios realmente mejoren algo. Knuth lo expresó mejor:

"Deberíamos olvidarnos de pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal . Sin embargo, no debemos dejar pasar nuestras oportunidades en ese crítico 3%. Un buen programador no se dejará llevar por la complacencia por tal razonando, será prudente mirar cuidadosamente el código crítico; pero solo después de que ese código haya sido identificado ".

En otras palabras, escriba el código limpio primero. Luego, perfile el programa resultante y vea si ese segmento es, de hecho, un cuello de botella de rendimiento. Si es así, optimice la sección según sea necesario y asegúrese de incluir muchos comentarios de documentación (posiblemente incluyendo el código original) para explicar las optimizaciones. Luego perfile el resultado para verificar que realmente haya realizado una mejora.

tylerl
fuente
10

Como la pregunta dice " código rápido y difícil de leer ", la respuesta simple es nunca. Nunca hay una excusa para escribir código que sea difícil de leer. ¿Por qué? Dos razones.

  1. ¿Qué sucede si te atropella un autobús de camino a casa esta noche? ¿O (de manera más optimista y más típica) se retiró de este proyecto y se lo reasignó a otra cosa? El pequeño beneficio que imaginas que has obtenido con tu enredo de código se ve totalmente compensado por el hecho de que nadie más puede entenderlo . El riesgo que esto representa para los proyectos de software es difícil de exagerar. Trabajé una vez con una PBX importantefabricante (si trabaja en una oficina, probablemente tenga uno de sus teléfonos en su escritorio). Su gerente de proyecto me dijo un día que su producto principal, el software patentado que convirtió una caja estándar de Linux en una central telefónica completamente funcional, era conocido dentro de la compañía como "el blob". Nadie lo entendió más. Cada vez que implementaron una nueva característica. golpearon la compilación, luego retrocedieron, cerraron los ojos, contaron hasta veinte y luego miraron entre los dedos para ver si funcionaba. Ningún negocio necesita un producto central que ya no controle, pero es un escenario terriblemente común.
  2. ¡Pero necesito optimizar! De acuerdo, ha seguido todos los excelentes consejos en otras respuestas a esta pregunta: su código está fallando en sus casos de prueba de rendimiento, lo ha perfilado cuidadosamente, ha identificado los cuellos de botella, ha encontrado una solución ... y va a implican un poco de tonterías . Bien: ahora adelante y optimiza. Pero aquí está el secreto (y es posible que desee sentarse para este): la optimización y la reducción en el tamaño del código fuente no son lo mismo. Los comentarios, el espacio en blanco, los corchetes y los nombres significativos de las variables son enormes ayudas para la legibilidad que no le cuestan absolutamente nada porque el compilador los descartará. (O si está escribiendo un lenguaje no compilado como JavaScript, y sí, hay razones muy válidas para optimizar JavaScript: un compresor puede manejarlas). Largas líneas de código estrecho y minimalista (como el que tiene muntoo) publicado aquí ) no tiene nada que ver con la optimización: es un programador que intenta mostrar cuán inteligentes son al empaquetar tanto código en la menor cantidad de caracteres posible. Eso no es inteligente, es estúpido. Un programador verdaderamente inteligente es aquel que puede comunicar sus ideas claramente a los demás.
Mark Whitaker
fuente
2
No puedo aceptar que la respuesta sea "nunca". Algunos algoritmos son inherentemente muy difíciles de entender y / o implementar de manera eficiente. Leer el código, independientemente del número de comentarios, puede ser un proceso muy difícil.
Rex Kerr
4

Cuando es código desechable. Lo digo literalmente: cuando escribes un script para realizar un cálculo o tarea única, y sabes con tanta certeza que nunca tendrás que volver a hacer esa acción que puedes 'rm source-file' sin dudarlo, entonces puedes elegir La ruta fea.

De lo contrario, es una dicotomía falsa: si cree que necesita hacerlo feo para hacerlo más rápido, lo está haciendo mal. (O bien, es necesario revisar sus principios sobre qué es un buen código. El uso de goto es de hecho bastante elegante cuando es la solución adecuada al problema. Sin embargo, rara vez lo es).

maaku
fuente
55
No existe el código desechable. Si tuviera un centavo por cada vez que el "código desechable" entrara en producción porque "está funcionando, no tenemos tiempo para reescribirlo", sería millonario. Cada línea de código que escriba debe escribirse de manera que otro programador competente pueda recogerlo mañana después de que un rayo lo golpee esta noche. De lo contrario, no lo escriba.
Mark Whitaker
No estoy de acuerdo con que sea una falsa dicotomía; En mi opinión, hay escenarios especialmente en el código de la biblioteca (no tanto en el código de la aplicación) donde la división es muy real. Vea mi respuesta para más.
Marc Gravell
@mark, si el "otro programador competente" es realmente competente, entonces el código desechable tampoco debería ser un problema :)
@ Mark - Fácil. Simplemente escriba el código desechable para que falle en cualquier prueba de producción, tal vez de alguna manera que no se pueda arreglar.
hotpaw2
@ Mark, si su "código desechable" entra en producción, entonces ese no es el código desechable. Tenga en cuenta que me tomé el tiempo en mi respuesta para aclarar que estoy hablando de código que literalmente se descarta: es decir, eliminar después del primer uso. De lo contrario, estoy de acuerdo con su sentimiento y lo dije en mi respuesta.
maaku
3

Siempre que el costo estimado de menor rendimiento en el mercado sea mayor que el costo estimado de mantenimiento de código para el módulo de código en cuestión.

La gente todavía hace SSE / NEON / etc retorcidos codificados a mano. ensamblaje para tratar de vencer al software de algunos competidores en el popular chip de CPU de este año.

hotpaw2
fuente
Una buena perspectiva comercial, a veces los programadores necesitan mirar más allá de lo meramente técnico.
this.josh
3

No olvide que puede hacer que el código difícil de leer sea fácil de entender mediante la documentación y los comentarios adecuados.

En general, el perfil después de haber escrito un código fácil de leer que realiza la función deseada. Los cuellos de botella pueden requerir que haga algo que lo haga parecer más complicado, pero lo arregla explicándose.

Carlos
fuente
0

Para mí es una proporción de estabilidad (como en cemento cementado, arcilla cocida al horno, puesta en piedra, escrita con tinta permanente). Cuanto más inestable sea su código, ya que cuanto mayor sea la probabilidad de que tenga que cambiarlo en el futuro, más flexible será, como arcilla húmeda, para mantenerse productivo. También enfatizo la flexibilidad y no la legibilidad. Para mí, la facilidad de cambiar el código es más importante que la facilidad de leerlo. El código puede ser fácil de leer y una pesadilla para cambiar, y ¿de qué sirve poder leer y comprender fácilmente los detalles de implementación si son una pesadilla para cambiar? A menos que sea solo un ejercicio académico, generalmente el punto de poder comprender fácilmente el código en una base de código de producción es con la intención de poder cambiarlo más fácilmente según sea necesario. Si es difícil cambiar, entonces muchos de los beneficios de la legibilidad desaparecen. La legibilidad solo es generalmente útil en el contexto de la flexibilidad, y la flexibilidad solo es útil en el contexto de la inestabilidad.

Naturalmente, incluso el código más difícil de mantener imaginable, independientemente de lo fácil o difícil que sea leerlo, no representa un problema si nunca hay una razón para cambiarlo, solo úselo. Y es posible lograr tal calidad, especialmente para el código de sistema de bajo nivel donde el rendimiento a menudo tiende a contar más. Tengo un código C que todavía uso regularmente, que no ha cambiado desde finales de los 80. No ha necesitado cambiar desde entonces. El código es fugitivo, está escrito en los días poco complicados, y apenas lo entiendo. Sin embargo, todavía es aplicable hoy, y no necesito entender su implementación para aprovecharlo al máximo.

Escribir minuciosamente las pruebas es una forma de mejorar la estabilidad. Otro es el desacoplamiento. Si su código no depende de nada más, entonces la única razón para que cambie es si él mismo necesita cambiar. A veces, una pequeña cantidad de duplicación de código puede servir como un mecanismo de desacoplamiento para mejorar drásticamente la estabilidad de una manera que lo convierte en una compensación digna si, a cambio, obtiene un código que ahora es completamente independiente de cualquier otra cosa. Ahora ese código es invulnerable a los cambios en el mundo exterior. Mientras tanto, el código que depende de 10 bibliotecas externas diferentes tiene 10 veces la razón para que cambie en el futuro.

Otra cosa útil en la práctica es separar su biblioteca de las partes inestables de su base de código, posiblemente incluso compilándola por separado, como lo haría para las bibliotecas de terceros (que también están destinadas a ser utilizadas, no modificadas, al menos no por su equipo). Solo ese tipo de organización puede evitar que las personas lo manipulen.

Otro es el minimalismo. Cuanto menos intente hacer su código, es más probable que pueda hacer lo que hace bien. Los diseños monolíticos son casi permanentemente inestables, ya que cada vez que se les agrega más funcionalidad, más incompletos parecen.

La estabilidad debe ser su objetivo principal siempre que intente escribir código que inevitablemente será difícil de cambiar, como el código SIMD paralelo que ha sido microajustado hasta la muerte. Contrarresta la dificultad de mantener el código maximizando la probabilidad de que no tenga que cambiar el código y, por lo tanto, no tendrá que mantenerlo en el futuro. Eso reduce los costos de mantenimiento a cero, sin importar cuán difícil sea mantener el código.


fuente