A veces te encuentras con una situación en la que tienes que extender / mejorar algún código existente. Verá que el código antiguo es muy sencillo, pero también es difícil de extender y lleva tiempo leerlo.
¿Es una buena idea reemplazarlo con código moderno?
Hace algún tiempo me gustó el enfoque lean, pero ahora, me parece que es mejor sacrificar muchas optimizaciones en favor de abstracciones más altas, mejores interfaces y código más legible y extensible.
Los compiladores también parecen estar mejorando, por lo que cosas como struct abc = {}
silenciosamente se convierten en memset
s, shared_ptr
s están produciendo el mismo código que el puntero sin formato, las plantillas funcionan súper bien porque producen un código súper delgado, y así sucesivamente.
Pero aún así, a veces se ven matrices basadas en pila y funciones antiguas de C con alguna lógica oscura, y generalmente no están en la ruta crítica.
¿Es una buena idea cambiar ese código si tiene que tocar un pequeño fragmento de cualquier manera?
fuente
Respuestas:
¿Dónde?
En una página de inicio de un sitio web a escala de Google, no es aceptable. Mantenga las cosas lo más rápido posible.
En una parte de una aplicación que utiliza una persona una vez al año, es perfectamente aceptable sacrificar el rendimiento para obtener legibilidad del código.
En general, ¿cuáles son los requisitos no funcionales para la parte del código en la que está trabajando? Si una acción debe realizarse por debajo de 900 ms. en un contexto dado (máquina, carga, etc.) el 80% del tiempo, y en realidad, funciona por debajo de 200 ms. El 100% del tiempo, seguro, hace que el código sea más legible incluso si puede afectar ligeramente el rendimiento. Si, por otro lado, la misma acción nunca se realizó en menos de diez segundos, bueno, debería tratar de ver qué hay de malo en el rendimiento (o el requisito en primer lugar).
Además, ¿cómo la mejora de la legibilidad disminuirá el rendimiento? A menudo, los desarrolladores están adaptando el comportamiento cercano a la optimización prematura: tienen miedo de aumentar la legibilidad, creyendo que destruirá drásticamente el rendimiento, mientras que el código más legible pasará unos microsegundos más haciendo la misma acción.
fuente
goto
era más rápido que para los bucles. Irónicamente, el optimizador funcionó mejor con bucles for, por lo que hizo que el código fuera más lento y difícil de leer.Usualmente no .
Cambiar el código puede causar problemas imprevistos en otras partes del sistema (que a veces pueden pasar desapercibidas hasta mucho más tarde en un proyecto si no tiene una unidad sólida y pruebas de humo en el lugar). Generalmente uso la mentalidad de "si no está roto, no lo arregles".
La excepción a esta regla es si está implementando una nueva característica que toca este código. Si, en ese punto, no tiene sentido y la refactorización realmente necesita llevarse a cabo, entonces hágalo siempre y cuando el tiempo de refactorización (y suficiente prueba y amortiguación para lidiar con los problemas) se tengan en cuenta en las estimaciones.
Por supuesto, perfil, perfil, perfil , especialmente si se trata de un área de ruta crítica.
fuente
En resumen: depende
¿Realmente va a necesitar o usar su versión refactorizada / mejorada?
¿Realmente necesita ser optimizado?
En detalles
¿Vas a necesitar las cosas limpias y brillantes?
Hay cosas de las que hay que ser cauteloso aquí, y debe identificar el límite entre lo que es real, la ganancia medible y lo que es solo su preferencia personal y el mal hábito potencial de tocar el código que no debería ser.
Más específicamente, sepa esto:
Existe una ingeniería excesiva
Es un antipatrón, y viene con problemas integrados:
Algunos también podrían mencionar el principio KISS como referencia, pero aquí es contra-intuitivo: ¿es la forma optimizada la forma simple o la forma de arquitectura limpia? La respuesta no es necesariamente absoluta, como se explica en el resto a continuación.
No lo vas a necesitar
El principio YAGNI no es completamente ortogonal con el otro problema, pero ayuda a hacerse la pregunta: ¿lo va a necesitar?
¿La arquitectura más compleja realmente presenta un beneficio para usted, además de dar la apariencia de ser más sostenible?
Si no se rompió, no lo arregles
Escriba esto en un póster grande y cuélguelo junto a su pantalla o en el área de la cocina en el trabajo, o en la sala de reuniones de desarrollo. Por supuesto, hay muchos otros mantras que vale la pena repetir, pero este en particular es importante cada vez que intentas hacer un "trabajo de mantenimiento" y sientes la necesidad de "mejorarlo".
Es natural para nosotros querer "mejorar" el código o incluso simplemente tocarlo, incluso inconscientemente, mientras lo leemos para tratar de entenderlo. Es algo bueno, ya que significa que somos obstinados e intentamos obtener una comprensión más profunda de los aspectos internos, pero también está vinculado a nuestro nivel de habilidad, nuestro conocimiento (¿cómo decide qué es mejor o no? Bueno, consulte las secciones a continuación) ...), y todas las suposiciones que hacemos sobre lo que creemos que conocemos el software ...:
¿Realmente necesita ser optimizado?
Dicho todo esto, ¿por qué se "optimizó" en primer lugar? Dicen que la optimización prematura es la raíz de todo mal, y si ve un código indocumentado y aparentemente optimizado, por lo general, podría suponer que probablemente no siguió las Reglas de optimización que realmente no necesitaba el esfuerzo de optimización y que era solo el la arrogancia habitual de los desarrolladores está empezando. Una vez más, tal vez sea solo tuyo hablando ahora.
Si es así, ¿dentro de qué límites se vuelve aceptable? Si es necesario, este límite existe y le da espacio para mejorar las cosas, o una línea dura para decidir dejarlo ir.
Además, tenga cuidado con las características invisibles. Lo más probable es que su versión "extensible" de este código también acumule más memoria en tiempo de ejecución y presente incluso una mayor huella de memoria estática para el ejecutable. Las características brillantes de OO vienen con costos poco intuitivos como estos, y pueden ser importantes para su programa y el entorno en el que se supone que se ejecuta.
Medida, medida, medida
Como la gente de Google ahora, ¡se trata de datos! Si puede hacer una copia de seguridad con datos, entonces es necesario.
Hay una historia no tan antigua que por cada $ 1 gastado en desarrollo será seguido por al menos $ 1 en pruebas y al menos $ 1 en soporte (pero en realidad, es mucho más).
El cambio impacta muchas cosas:
Por lo tanto, no es solo el consumo de recursos de hardware (velocidad de ejecución o huella de memoria) lo que necesita medir aquí, sino también el consumo de recursos del equipo . Ambos deben predecirse para definir un objetivo objetivo, medirse, contabilizarse y adaptarse según el desarrollo.
Y para su gerente, eso significa adaptarlo al plan de desarrollo actual, así que comuníquese al respecto y no entre en la furiosa codificación cow-boy / submarine / black-ops.
En general...
Sí, pero...
No me malinterpretes, en general, estaría a favor de hacer por qué sugieres, y a menudo lo defiendo. Pero debe ser consciente del costo a largo plazo.
En un mundo perfecto, es la solución correcta:
En la práctica:
puedes empeorarlo
Necesitas más globos oculares para mirarlo, y cuanto más lo compliques, más globos oculares necesitarás.
no puedes predecir el futuro
No puede saber con absoluta certeza si alguna vez lo necesitará y ni siquiera si las "extensiones" que necesitaría hubieran sido más fáciles y rápidas de implementar en la forma anterior, y si ellos mismos tendrían que estar súper optimizados .
representa, desde la perspectiva de la gerencia, un gran costo sin ganancia directa.
Hazlo parte del proceso
Menciona aquí que es un cambio bastante pequeño, y tiene algunos problemas específicos en mente. Yo diría que generalmente está bien en este caso, pero la mayoría de nosotros también tenemos historias personales de pequeños cambios, ediciones casi quirúrgicas, que eventualmente se convirtieron en pesadillas de mantenimiento y plazos casi vencidos o explotados porque Joe Programmer no vio uno. de las razones detrás del código y tocó algo que no debería haber sido.
Si tiene un proceso para manejar tales decisiones, les quita la ventaja personal:
La cobertura de prueba, la creación de perfiles y la recopilación de datos son difíciles
Pero, por supuesto, su código de prueba y sus métricas pueden sufrir los mismos problemas que está tratando de evitar para su código real: prueba las cosas correctas, y son lo correcto para el futuro, y mide lo correcto ¿cosas?
Aún así, en general, cuanto más pruebas (hasta cierto límite) y midas, más datos recolectas y más seguro estarás. Mal tiempo de analogía: piense en ello como conducir (o la vida en general): puede ser el mejor conductor del mundo, si el automóvil se descompone o alguien decide suicidarse al conducir con su propio automóvil hoy, su Las habilidades pueden no ser suficientes. Hay dos cosas ambientales que pueden afectarlo, y los errores humanos también importan.
Las revisiones de código son las pruebas de pasillo del equipo de desarrollo
Y creo que la última parte es clave aquí: hacer revisiones de código. No sabrás el valor de tus mejoras si las haces solo. Las revisiones de código son nuestras "pruebas de pasillo": siga la versión de Raymond de la Ley de Linus tanto para detectar errores como para detectar sobre ingeniería y otros antipatrones, y para asegurarse de que el código esté en línea con las habilidades de su equipo. No tiene sentido tener el "mejor" código si nadie más puede entenderlo y mantenerlo, y eso se aplica tanto a las optimizaciones crípticas como a los diseños arquitectónicos profundos de 6 capas.
Como palabras de cierre, recuerde:
fuente
En general, primero debe centrarse en la legibilidad y el rendimiento mucho más tarde. La mayoría de las veces, esas optimizaciones de rendimiento son insignificantes de todos modos, pero el costo de mantenimiento puede ser enorme.
Ciertamente, todas las cosas "pequeñas" deberían cambiarse a favor de la claridad ya que, como usted señaló, la mayoría de ellas serán optimizadas por el compilador de todos modos.
En cuanto a las optimizaciones más grandes, puede haber una posibilidad de que las optimizaciones sean realmente críticas para alcanzar un rendimiento razonable (aunque este no es el caso sorprendentemente a menudo). Haría sus cambios y luego perfilaría el código antes y después de los cambios. Si el nuevo código tiene problemas de rendimiento significativos, siempre puede retroceder a la versión optimizada, y si no, puede quedarse con la versión de código más limpia.
Cambie solo una parte del código a la vez y vea cómo afecta el rendimiento después de cada ronda de refactorización.
fuente
Depende de por qué se optimizó el código y cuál sería el efecto de cambiarlo y cuál podría ser el impacto del código en el rendimiento general. También debe depender de si tiene una buena manera de cargar los cambios de prueba.
No debe realizar este cambio sin perfilar antes y después y preferiblemente bajo una carga similar a la que se vería en la producción. Eso significa no usar un pequeño subconjunto de datos en una máquina desarrolladora o realizar pruebas cuando solo un usuario está usando el sistema.
Si la optimización fue reciente, es posible que pueda hablar con el desarrollador y averiguar exactamente cuál era el problema y qué tan lenta era la aplicación antes de la optimización. Esto puede decirle mucho sobre si vale la pena hacer la optimización y para qué condiciones se necesitaba la optimización (un informe que cubre todo un año, por ejemplo, puede no haberse retrasado hasta septiembre u octubre, si está probando su cambio en febrero, la lentitud podría no ser evidente y la prueba no válida).
Si la optimización es bastante antigua, los métodos más nuevos podrían incluso ser más rápidos y más legibles.
En última instancia, esta es una pregunta para su jefe. Lleva mucho tiempo refactorizar algo que ha sido optimizado y asegurarse de que el cambio no haya afectado el resultado final y que funcione tan bien o al menos aceptablemente en comparación con la forma anterior. Es posible que desee que pase su tiempo en otras áreas en lugar de asumir una tarea de alto riesgo para ahorrar unos minutos de tiempo de codificación. O puede estar de acuerdo en que el código es difícil de entender y ha necesitado una intervención frecuente y que ahora hay mejores métodos disponibles.
fuente
si el perfil muestra que la optimización no es necesaria (no está en una sección crítica) o incluso tiene un tiempo de ejecución peor (como resultado de una mala optimización prematura), entonces reemplace con el código legible que es más fácil de mantener
también asegúrese de que el código se comporte igual con las pruebas apropiadas
fuente
Piénselo desde una perspectiva comercial. ¿Cuáles son los costos del cambio? ¿Cuánto tiempo necesita para hacer el cambio y cuánto ahorrará a largo plazo al hacer que el código sea más fácil de extender o mantener? Ahora adjunte una etiqueta de precio a ese momento y compárelo con el dinero perdido al reducir el rendimiento. Tal vez necesite agregar o actualizar un servidor para compensar el rendimiento perdido. Tal vez el producto ya no cumple con los requisitos y ya no se puede vender. Tal vez no hay pérdida. Quizás el cambio aumenta la robustez y ahorra tiempo en otros lugares. Ahora toma tu decisión.
Como nota al margen, en algunos casos podría ser posible mantener ambas versiones de un fragmento. Puede escribir una prueba generando valores de entrada aleatorios y verificar los resultados con la otra versión. Use la solución "inteligente" para verificar el resultado de una solución perfectamente comprensible y obviamente correcta y, por lo tanto, obtenga cierta seguridad (pero no prueba) de que la nueva solución es equivalente a la anterior. O vaya al revés y verifique el resultado del código complicado con el código detallado y, por lo tanto, documente la intención detrás del hack de manera inequívoca.
fuente
Básicamente, se pregunta si la refactorización es una empresa que vale la pena. La respuesta a esto es ciertamente sí.
Pero...
... necesitas hacerlo con cuidado. Necesita pruebas sólidas de unidad, integración, funcionamiento y rendimiento para cualquier código que esté refactorizando. Debe estar seguro de que realmente prueban toda la funcionalidad requerida. Necesita la capacidad de ejecutarlos de manera fácil y repetida. Una vez que tenga eso, debería poder reemplazar los componentes con componentes nuevos que contengan la funcionalidad equivalente.
Martin Fowler escribió el libro sobre esto.
fuente
No debe cambiar el código de trabajo y producción sin una buena razón. "Refactorizar" no es una razón suficiente a menos que no pueda hacer su trabajo sin esa refactorización. Incluso si lo que está haciendo es corregir errores dentro del código difícil en sí, debe tomarse el tiempo para comprenderlo y realizar el menor cambio posible. Si el código es tan difícil de entender, no podrá comprenderlo por completo, por lo que cualquier cambio que realice tendrá efectos secundarios impredecibles: errores, en otras palabras. Cuanto mayor sea el cambio, más probabilidades hay de que cause problemas.
Habría una excepción a esto: si el código incomprensible tuviera un conjunto completo de pruebas unitarias, podría refactorizarlo. Como nunca he visto ni oído hablar de un código incomprensible con pruebas unitarias completas, primero debe escribir las pruebas unitarias, obtener el acuerdo de las personas necesarias de que esas pruebas unitarias representan lo que debería hacer el código, y ENTONCES hacer que el código cambie . Lo he hecho una o dos veces; Es un dolor en el cuello y muy costoso, pero al final produce buenos resultados.
fuente
Si se trata de un código corto que hace algo relativamente simple de una manera difícil de entender, cambiaría la "comprensión rápida" en un comentario extendido y / o una implementación alternativa no utilizada, como
fuente
La respuesta es, sin pérdida de generalidad, sí. Siempre agregue código moderno cuando vea un código difícil de leer y elimine el código incorrecto en la mayoría de los casos. Yo uso el siguiente proceso:
<function>_clean()
. Luego, "compita" su código contra el código incorrecto. Si su código es mejor, elimine el código anterior.QED
fuente
Si pudiera enseñarle al mundo una cosa (sobre Software) antes de morir, le enseñaría que "Rendimiento versus X" es un falso dilema.
La refactorización generalmente se conoce como una bendición para la legibilidad y la confiabilidad, pero puede soportar fácilmente la optimización. Cuando maneja la mejora del rendimiento como una serie de refactorizaciones, puede honrar la Regla del campamento y al mismo tiempo hacer que la aplicación vaya más rápido. En realidad, al menos en mi opinión, es éticamente obligatorio que lo haga.
Por ejemplo, el autor de esta pregunta ha encontrado un código loco. Si esta persona estuviera leyendo mi código, descubrirían que la parte loca tiene 3-4 líneas de largo. Está en un método en sí mismo, y el nombre y la descripción del método indican QUÉ está haciendo el método. El método contendría 2-6 líneas de comentarios en línea que describen CÓMO el código loco obtiene la respuesta correcta, a pesar de su apariencia cuestionable.
Compartimentado de esta manera, puede intercambiar implementaciones de este método como desee. De hecho, probablemente es así como escribí la versión loca para empezar. Puede probar, o al menos preguntar sobre alternativas. La mayoría de las veces descubrirá que la implementación ingenua es notablemente peor (generalmente solo me molesto en una mejora de 2 a 10 veces), pero los compiladores y las bibliotecas siempre están cambiando, y quién sabe lo que puede encontrar hoy que no estaba disponible cuando la función fue escrita?
fuente
Probablemente no sea una buena idea tocarlo: si el código se escribió de esa manera por razones de rendimiento, significa que cambiarlo podría traer problemas de rendimiento que se habían resuelto previamente.
Si no decide cambiar las cosas sean más legibles y extensible: Antes de realizar un cambio, el código de referencia de edad bajo pesada carga. Aún mejor si puede encontrar un documento antiguo o un ticket de problema que describa el problema de rendimiento que se supone que este código de aspecto extraño debe solucionar. Luego, después de realizar los cambios, vuelva a ejecutar las pruebas de rendimiento. Si no es muy diferente, o aún está dentro de los parámetros aceptables, entonces probablemente esté bien.
A veces puede suceder que cuando otras partes de un sistema cambian, este código optimizado para el rendimiento ya no necesita optimizaciones tan pesadas, pero no hay forma de saberlo con certeza sin pruebas rigurosas.
fuente
El problema aquí es distinguir "optimizado" de legible y extensible, lo que nosotros como usuarios vemos como código optimizado y lo que el compilador ve como optimizado son dos cosas diferentes. El código que está buscando cambiar podría no ser un cuello de botella y, por lo tanto, incluso si el código es "sencillo", ni siquiera es necesario que esté "optimizado". O si el código es lo suficientemente antiguo, puede haber optimizaciones hechas por el compilador a las incorporaciones que hacen que el uso de una estructura incorporada simple más nueva sea igual o más eficiente que el código anterior.
Y el código "magra" ilegible no siempre está optimizado.
Solía tener la mentalidad de que el código inteligente / esbelto era un buen código, pero a veces aprovechando las reglas oscuras del lenguaje que dañan en lugar de ayudar en la creación del código, me han mordido más a menudo que en cualquier trabajo incrustado al intentar sea inteligente porque el compilador convierte su código inteligente en algo completamente inutilizable por el hardware incorporado.
fuente
Nunca reemplazaré el código optimizado con código legible porque no puedo comprometer el rendimiento y optaré por utilizar los comentarios adecuados en todas y cada una de las secciones para que cualquiera pueda comprender la lógica implementada en esa sección optimizada que resolverá ambos problemas.
Por lo tanto, el código será optimizado + los comentarios adecuados también lo harán legible.
NOTA: Puede hacer que un Código optimizado sea legible con la ayuda de comentarios apropiados, pero no puede hacer que el Código legible sea optimizado.
fuente
Aquí hay un ejemplo para ver la diferencia entre código simple y código optimizado: https://stackoverflow.com/a/11227902/1396264
hacia el final de la respuesta simplemente reemplaza:
con:
Para ser justos, no tengo idea de con qué se ha reemplazado la declaración if, pero como dice el respondedor, sus operaciones bit a bit dan el mismo resultado (solo voy a tomar su palabra) .
Esto se ejecuta en menos de una cuarta parte del tiempo original (11.54 segundos frente a 2.5 segundos)
fuente
La pregunta principal aquí es: ¿se requiere la optimización?
Si es así, no puede reemplazarlo con un código más lento y legible. Deberá agregar comentarios, etc. para hacerlo más legible.
Si el código no tiene que ser optimizado, entonces no debería serlo (hasta el punto de afectar la legibilidad) y puede re-factorizarlo para hacerlo más legible.
SIN EMBARGO: asegúrese de saber exactamente qué hace el código y cómo probarlo a fondo antes de comenzar a cambiar las cosas. Esto incluye el uso pico, etc. Si no tiene que componer un conjunto de casos de prueba y ejecutarlos antes y después, entonces no tiene tiempo para refactorizar.
fuente
Así es como hago las cosas: primero lo hago funcionar en código legible, luego lo optimizo. Mantengo la fuente original y documento mis pasos de optimización.
Luego, cuando necesito agregar una función, vuelvo a mi código legible, agrego la función y sigo los pasos de optimización que documenté. Debido a que documentaste, es muy rápido y fácil reoptimizar tu código con la nueva función.
fuente
La legibilidad de la OMI es más importante que el código optimizado porque en la mayoría de los casos la microoptimización no vale la pena.
Artículo sobre micro optimizaciones sin sentido :
fuente
La optimización es relativa. Por ejemplo:
Esta suposición:
lleva a:
Referencias
El análisis de costo-beneficio de bitfields para una colección de booleanos - The Old New Thing
Maldad de campo de bits - Hardwarebug
Campos de bits legibles y mantenibles en C | pagetable.com
fuente