He trabajado como desarrollador de software durante muchos años. Según mi experiencia, los proyectos se vuelven más complejos e imposibles de mantener a medida que más desarrolladores se involucran en el desarrollo del producto.
Parece que el software en una determinada etapa de desarrollo tiene la tendencia a volverse "hackier" y "hackier", especialmente cuando ninguno de los miembros del equipo que definió la arquitectura trabaja en la empresa.
Me resulta frustrante que un desarrollador que tiene que cambiar algo tiene dificultades para obtener una visión general de la arquitectura. Por lo tanto, existe una tendencia a solucionar problemas o hacer cambios de una manera que funcione en contra de la arquitectura original. El resultado es un código que se vuelve cada vez más complejo e incluso más difícil de entender.
¿Hay algún consejo útil sobre cómo mantener el código fuente realmente sostenible a lo largo de los años?
Respuestas:
¡La única solución real para evitar la descomposición del código es codificar bien!
Cómo codificar bien es otra pregunta. Es bastante difícil incluso si eres un excelente programador trabajando solo. En un equipo heterogéneo, se vuelve mucho más difícil aún. En (sub) proyectos subcontratados ... solo reza.
Las buenas prácticas habituales pueden ayudar:
fuente
Las pruebas unitarias son tu amigo . Implementarlos fuerza un bajo acoplamiento. También significa que las partes "hacky" del programa se pueden identificar y refactorizar fácilmente. También significa que cualquier cambio puede probarse rápidamente para garantizar que no rompa la funcionalidad existente. Esto debería alentar a sus desarrolladores a modificar los métodos existentes en lugar de duplicar el código por temor a romper cosas.
Las pruebas unitarias también funcionan como un trozo adicional de documentación para su código, que describe lo que debe hacer cada parte. Con extensas pruebas unitarias, sus programadores no deberían necesitar conocer toda la arquitectura de su programa para hacer cambios y usar clases / métodos existentes.
Como un agradable efecto secundario, las pruebas unitarias también reducirán su recuento de errores.
fuente
Todo el mundo aquí menciona rápidamente la descomposición del código , y entiendo completamente y estoy de acuerdo con esto, pero aún se pierde la imagen más grande y el problema más grande que tenemos aquí. La descomposición del código no solo sucede. Además, se mencionan las pruebas unitarias que son buenas, pero en realidad no abordan el problema. Uno puede tener una buena cobertura de prueba de unidad y un código relativamente libre de errores, sin embargo, todavía tiene código y diseño podridos.
Usted mencionó que el desarrollador que trabaja en un proyecto tiene dificultades para implementar una característica y pierde la imagen más amplia de la arquitectura general, y por lo tanto implementa un truco en el sistema. ¿Dónde está el liderazgo técnico para imponer e influir en el diseño? ¿Dónde están las revisiones de código en este proceso?
En realidad no estás sufriendo de la podredumbre del código, pero sí de la podredumbre del equipo . El hecho es que no debería importar si los creadores originales del software ya no están en el equipo. Si el líder técnico del equipo existente comprende total y verdaderamente el diseño subyacente y es bueno en el papel de líder tecnológico, entonces esto no sería un problema.
fuente
Hay varias cosas que podemos hacer:
Dele a una persona la responsabilidad general de la arquitectura. Al elegir a esa persona, asegúrese de que tenga la visión y la habilidad para desarrollar y mantener una arquitectura, y que tenga la influencia y la autoridad para ayudar a otros desarrolladores a seguir la arquitectura. Esa persona debe ser un desarrollador experimentado en quien la gerencia confíe y que sus pares respeten.
Cree una cultura donde todos los desarrolladores se apropien de la arquitectura. Todos los desarrolladores deben participar en el proceso de desarrollo y mantenimiento de la integridad arquitectónica.
Desarrolle un entorno donde las decisiones arquitectónicas se comuniquen fácilmente. Aliente a las personas a hablar sobre diseño y arquitectura, no solo en el contexto del proyecto actual, sino también en general.
Las mejores prácticas de codificación hacen que la arquitectura sea más fácil de ver desde el código: tome tiempo para refactorizar, comentar el código, desarrollar pruebas unitarias, etc. Cosas como las convenciones de nomenclatura y las prácticas de codificación limpias pueden ayudar mucho en la comunicación de la arquitectura, por lo que como equipo necesita tomarse un tiempo para desarrollar y seguir sus propios estándares.
Asegúrese de que toda la documentación necesaria sea clara, concisa, actualizada y accesible. Hacer públicos los diagramas de arquitectura de alto y bajo nivel (sujetarlos a la pared puede ayudar) y mantenerlos públicamente.
Finalmente (como perfeccionista natural) necesito reconocer que la integridad arquitectónica es una aspiración digna, pero que puede haber cosas más importantes, como construir un equipo que pueda funcionar bien en conjunto y enviar un producto que funcione.
fuente
La forma en que abordo este problema es cortarlo en la raíz:
Mi explicación utilizará términos de Microsoft / .NET , pero será aplicable a cualquier plataforma / caja de herramientas:
fuente
Limpie el código podrido refactorizando, mientras escribe las pruebas unitarias. Pague (esta) deuda de diseño en todo el código que toque, siempre que:
Acelere enormemente su ciclo de desarrollo de prueba primero:
Código de refactorización para usar un acoplamiento bajo (de unidades altamente cohesivas internamente) mediante:
El crecimiento orgánico es bueno; El diseño inicial grande es malo.
Tenga un líder que conozca el diseño actual. Si no, lea el código del proyecto hasta que esté bien informado.
Leer libros de refactorización.
fuente
Respuesta simple: no puedes .
Es por eso que debes apuntar a escribir software pequeño y simple . No es fácil.
Eso solo es posible si piensa lo suficiente en su problema aparentemente complejo para definirlo de la manera más simple y concisa posible.
La solución a problemas que realmente son grandes y complejos a menudo aún se puede resolver construyendo sobre módulos pequeños y simples.
En otras palabras, como otros señalaron, la simplicidad y el acoplamiento flexible son los ingredientes clave.
Si eso no es posible o factible, probablemente esté investigando (problemas complejos sin soluciones simples conocidas o ninguna solución conocida). No espere que la investigación produzca directamente productos que se puedan mantener, para eso no es para qué sirve la investigación.
fuente
Trabajo en una base de código para un producto que ha estado en continuo desarrollo desde 1999, por lo que, como pueden imaginar, ahora es bastante complejo. La mayor fuente de piratería en nuestra base de código es por las numerosas veces que hemos tenido que portarlo de ASP Classic a ASP.NET , de ADO a ADO.NET, de postbacks a Ajax , cambiar bibliotecas de interfaz de usuario, estándares de codificación, etc.
En general, hemos hecho un trabajo razonable para mantener la base de código mantenible. Las principales cosas que hemos hecho que contribuyeron a eso son:
1) Refactorización constante: si tiene que tocar un código que es hacky o difícil de entender, se espera que tome un tiempo extra para limpiarlo y se le da margen de maniobra en el cronograma para hacerlo. Las pruebas unitarias hacen que esto sea mucho menos aterrador, ya que puedes probar contra las regresiones más fácilmente.
2) Mantenga un entorno de desarrollo ordenado: esté atento a la eliminación de código que ya no se usa, y no deje copias de seguridad / copias de trabajo / código experimental en el directorio del proyecto.
3) Estándares de codificación consistentes para la vida del proyecto: seamos sinceros, nuestros puntos de vista sobre los estándares de codificación evolucionan con el tiempo. Sugiero seguir con el estándar de codificación con el que comenzó durante la vida de un proyecto, a menos que tenga tiempo de regresar y actualizar todo el código para cumplir con el nuevo estándar. Es genial que haya superado la notación húngara ahora, pero aplique esa lección a nuevos proyectos y no solo cambie la mitad de la secuencia en ese nuevo proyecto.
fuente
Como ha etiquetado la pregunta con la gestión de proyectos, he intentado agregar algunos puntos sin código :)
Planifique la rotación: suponga que todo el equipo de desarrollo habrá desaparecido para cuando llegue a su fase de mantenimiento; ningún desarrollador que valga la pena quiere quedarse atrapado manteniendo su sistema para siempre. Comience a preparar los materiales de entrega tan pronto como tenga tiempo.
La consistencia / uniformidad no se puede enfatizar lo suficiente. Esto desalentará una cultura de 'hacerlo solo' y alentará a los nuevos desarrolladores a preguntar, si tienen dudas.
Manténgalo en la corriente principal: tecnologías utilizadas, patrones de diseño y estándares, porque un nuevo desarrollador del equipo (en cualquier nivel) tendrá más posibilidades de ponerse en marcha rápidamente.
Documentación, especialmente arquitectura, por qué se tomaron decisiones y estándares de codificación. También conserve referencias / notas / hojas de ruta para documentar el dominio comercial: se sorprenderá de lo difícil que es para las empresas corporativas explicar lo que hacen a un desarrollador sin experiencia en el dominio.
Establezca las reglas claramente, no solo para su equipo de desarrollo actual, sino que piense en futuros desarrolladores de mantenimiento. Si esto significa poner un hipervínculo al diseño relevante y codificar la documentación estándar en cada página, que así sea.
Asegúrese de que la arquitectura y especialmente las capas de código estén claramente delimitadas y separadas; esto posiblemente permitirá el reemplazo de capas de código a medida que surjan nuevas tecnologías, por ejemplo, reemplazar una interfaz de usuario de formularios web con una interfaz de usuario jQuery HTML5 , etc., que puede compre un año más o menos de mayor longevidad.
fuente
Una propiedad del código altamente mantenible es la pureza de la función .
La pureza significa que las funciones deben devolver el mismo resultado para los mismos argumentos. Es decir, no deberían depender de los efectos secundarios de otras funciones. Además, es útil si no tienen efectos secundarios.
Esta propiedad es más fácil de observar que las propiedades de acoplamiento / cohesión. No tiene que salir de su camino para lograrlo, y personalmente lo considero más valioso.
Cuando su función es pura, su tipo es una muy buena documentación en sí misma. Además, escribir y leer documentación en términos de argumentos / valor de retorno es mucho más fácil que uno que mencione algún estado global (posiblemente accedido por otros hilos O_O).
Como ejemplo de uso de la pureza ampliamente para ayudar al mantenimiento, puede ver GHC . Es un gran proyecto de aproximadamente 20 años de antigüedad donde se están realizando grandes refactorizaciones y todavía se están introduciendo nuevas características importantes.
Por último, no me gusta demasiado el punto "Mantenlo simple". No puede mantener su programa simple cuando modela cosas complejas. Intente hacer un compilador simple y su código generado probablemente terminará muy lento. Claro, puede (y debe) simplificar las funciones individuales, pero como resultado todo el programa no será simple.
fuente
Además de las otras respuestas, recomendaría capas. No demasiados pero suficientes para separar diferentes tipos de código.
Utilizamos un modelo interno de API para la mayoría de las aplicaciones. Hay una API interna que se conecta a la base de datos. Luego una capa de interfaz de usuario . Diferentes personas pueden trabajar en cada nivel sin interrumpir o romper otras partes de las aplicaciones.
Otro enfoque es hacer que todos lean comp.risks y The Daily WTF para que aprendan las consecuencias del mal diseño y la mala programación, y temerán ver su propio código publicado en The Daily WTF .
fuente
Dado que muchas de estas respuestas parecen centrarse en equipos grandes, incluso desde el principio, voy a poner mi punto de vista como parte de un equipo de desarrollo de dos hombres (tres si incluye al diseñador) para una startup.
Obviamente, los diseños y soluciones simples son los mejores, pero cuando tienes al tipo que literalmente paga tu sueldo, no necesariamente tienes tiempo para pensar en la solución más elegante, simple y fácil de mantener. Con eso en mente, mi primer gran punto es:
Documentación No comentarios, el código debe ser principalmente autodocumentado, sino cosas como documentos de diseño, jerarquías de clase y dependencias, paradigmas arquitectónicos, etc. Cualquier cosa que ayude a un programador nuevo, o incluso existente, a comprender la base del código. Además, documentar esas pseudo-bibliotecas extrañas que eventualmente aparecen, como "agregar esta clase a un elemento para esta funcionalidad" puede ayudar, ya que también evita que las personas reescriban la funcionalidad.
Sin embargo, incluso si tiene un límite de tiempo severo, creo que otra buena cosa a tener en cuenta es:
Evite hacks y soluciones rápidas. A menos que la solución rápida sea la solución real, siempre es mejor descubrir el problema subyacente a algo y luego solucionarlo. A menos que literalmente tenga un escenario de "hacer que esto funcione en los próximos 2 minutos, o esté despedido", hacer la corrección ahora es una mejor idea, porque no va a arreglar el código más tarde, solo va a pasar a la siguiente tarea que tienes.
Y mi consejo favorito personal es más una cita, aunque no puedo recordar la fuente:
"Codifica como si la persona que te sigue es un psicópata homicida que sabe dónde vives"
fuente
/** Gets the available times of a clinic practitioner on a specific date. **/
o/** Represents a clinic practitioner. **/
.Un principio que no se ha mencionado pero que considero importante es el principio abierto / cerrado .
No debe modificar el código que se ha desarrollado y probado: cualquier código de este tipo está sellado. En su lugar, extienda las clases existentes por medio de subclases, o utilícelas escribiendo envoltorios, clases de decorador o usando cualquier patrón que considere adecuado. Pero no cambie el código de trabajo .
Solo mis 2 centavos.
fuente
Sé un explorador . Siempre deje el código más limpio de lo que lo encontró.
Arregla las ventanas rotas . Todos esos comentarios "cambian en la versión 2.0" cuando estás en la versión 3.0.
Cuando hay hacks importantes, diseñe una mejor solución como equipo y hágalo. Si no puedes arreglar el hack como equipo, entonces no entiendes el sistema lo suficientemente bien. "Pídele ayuda a un adulto". Las personas más viejas de alrededor podrían haber visto esto antes. Intente dibujar o extraer un diagrama del sistema. Intente dibujar o extraer los casos de uso que son particularmente extravagantes como diagramas de interacción. Esto no lo soluciona, pero al menos puedes verlo.
¿Qué suposiciones ya no son ciertas que empujaron el diseño en una dirección particular? Puede haber una pequeña refactorización escondiéndose detrás de algo de ese desastre.
Si explica cómo funciona el sistema (incluso un solo caso de uso) y tiene que disculparse por un subsistema una y otra vez, ese es el problema. Qué comportamiento facilitaría el resto del sistema (no importa cuán difícil sea implementarlo en comparación con lo que hay allí). El subsistema clásico para reescribir es uno que contamina cualquier otro subsistema con su semántica operativa e implementación. "Oh, tienes que hacer groz de los valores antes de alimentarlos al subsistema froo, luego los vuelves a desglosar a medida que obtienes la salida del froo. Quizás todos los valores deberían ser groz'ed cuando se leen desde el usuario y el almacenamiento, y el resto del sistema está mal? Esto se vuelve más emocionante cuando hay dos o más grozificaciones diferentes.
Pase una semana como equipo eliminando advertencias para que los problemas reales sean visibles.
Reformatee todo el código al estándar de codificación.
Asegúrese de que su sistema de control de versiones esté vinculado a su rastreador de errores. Esto significa que los cambios futuros son agradables y responsables, y puede resolver POR QUÉ.
Haz algo de arqueología. Encuentre los documentos de diseño originales y revíselos. Pueden estar en esa vieja PC en la esquina de la oficina, en el espacio de la oficina abandonada o en el archivador que nadie abre.
Vuelva a publicar los documentos de diseño en una wiki. Esto ayuda a institucionalizar el conocimiento.
Escriba procedimientos similares a listas de verificación para versiones y compilaciones. Esto evita que las personas tengan que pensar, para que puedan concentrarse en resolver problemas. Automatiza las compilaciones siempre que sea posible.
Prueba la integración continua . Cuanto antes obtenga una compilación fallida, menos tiempo pasará el proyecto fuera de los rieles.
Si el líder de su equipo no hace estas cosas, bueno, eso es malo para la empresa.
Intente asegurarse de que todo el nuevo código obtenga pruebas unitarias adecuadas con cobertura medida. Entonces el problema no puede empeorar mucho.
Intente realizar una prueba unitaria de algunos de los bits antiguos que no se prueban unitariamente. Esto ayuda a reducir el miedo al cambio.
Automatice su prueba de integración y regresión si puede. Al menos tenga una lista de verificación. Los pilotos son inteligentes, se les paga mucho y usan listas de verificación. También se arruinan muy raramente.
fuente
Lea y vuelva a leer Code Complete de Steve McConnell. Es como una biblia de buena escritura de software, desde el diseño inicial del proyecto hasta una sola línea de código y todo lo demás. Lo que más me gusta es que está respaldado por décadas de datos sólidos; no es solo el siguiente mejor estilo de codificación.
fuente
He venido a llamar a esto el "Efecto Winchester Mystery House". Al igual que la casa, comenzó de manera bastante simple, pero a lo largo de los años, muchos trabajadores diferentes agregaron tantas características extrañas sin un plan general que ya nadie lo entiende realmente. ¿Por qué esta escalera no llega a ninguna parte y por qué esa puerta solo se abre en una dirección? ¿Quién sabe?
La forma de limitar este efecto es comenzar con un buen diseño que sea lo suficientemente flexible como para manejar la expansión. Ya se han ofrecido varias sugerencias sobre esto.
Pero, a menudo tomará un trabajo donde el daño ya está hecho, y es demasiado tarde para un buen diseño sin realizar un rediseño y reescritura costoso y potencialmente riesgoso. En esas situaciones, es mejor tratar de encontrar formas de limitar el caos mientras lo abraza hasta cierto punto. Puede molestar su sensibilidad de diseño que todo tenga que pasar por una clase de 'administrador' enorme, fea y única o que la capa de acceso a datos esté estrechamente unida a la interfaz de usuario, pero aprenda a lidiar con eso. Codifique defensivamente dentro de ese marco e intente esperar lo inesperado cuando aparezcan los "fantasmas" de los programadores del pasado.
fuente
La refactorización de códigos y las pruebas unitarias están perfectamente bien. Pero dado que este proyecto de larga duración se está ejecutando en piratas informáticos, esto significa que la administración no está poniendo su pie para limpiar la podredumbre. Se requiere que el equipo introduzca hacks, porque alguien no está asignando recursos suficientes para capacitar a las personas y analizar el problema / solicitud.
Mantener un proyecto de larga duración es una responsabilidad tanto del gerente del proyecto como de un desarrollador individual.
La gente no introduce hacks porque les gusta; son forzados por las circunstancias.
fuente
Solo quiero colocar un problema no técnico y un enfoque (quizás) pragmático.
Si a su gerente no le importa la calidad técnica (código manejable, arquitectura simple, infraestructura confiable, etc.), será difícil mejorar el proyecto. En este caso, es necesario educar a dicho gerente y convencerlo de "invertir" los esfuerzos en la mantenibilidad y el tratamiento de la deuda técnica .
Si sueña con la calidad del código que se encuentra en esos libros, también necesita un jefe que esté preocupado por esto.
O si solo quiere domesticar un "proyecto Frankenstein", estos son mis consejos:
En mi experiencia, la programación es entrópica más que emergente (al menos en el paradigma estructurado imperativo popular). Cuando las personas escriben código para "simplemente trabajar", la tendencia es perder su organización. Ahora organizar el código requiere tiempo, a veces mucho más que hacerlo funcionar.
Más allá de la implementación de funciones y la corrección de errores, tómese su tiempo para limpiar el código.
fuente
Me sorprendió descubrir que ninguna de las numerosas respuestas resaltaba lo obvio: hacer que el software constara de numerosas bibliotecas pequeñas e independientes. Con muchas bibliotecas pequeñas, puede construir un software grande y complejo. Si los requisitos cambian, no tiene que tirar toda la base de código o investigar cómo modificar una gran base de código de bocina para hacer algo más de lo que está haciendo actualmente. Simplemente decide cuáles de esas bibliotecas siguen siendo relevantes después del cambio de requisitos y cómo combinarlas para tener la nueva funcionalidad.
Use cualquier técnica de programación en esas bibliotecas que facilite el uso de la biblioteca. Tenga en cuenta que, por ejemplo, cualquier puntero de función de soporte de lenguaje no orientado a objetos es compatible con la programación orientada a objetos (OOP). Entonces, por ejemplo, en C, puedes hacer POO.
Incluso puede considerar compartir esas bibliotecas pequeñas e independientes entre muchos proyectos (los submódulos git son sus amigos).
No hace falta decir que cada biblioteca pequeña e independiente debe someterse a pruebas unitarias. Si una biblioteca en particular no es comprobable por unidad, está haciendo algo mal.
Si usa C o C ++ y no le gusta la idea de tener muchos archivos .so pequeños, puede vincular todas las bibliotecas en un archivo .so más grande o, alternativamente, puede hacer un enlace estático. Lo mismo es cierto para Java, solo cambie .so a .jar.
fuente
Simple: reduzca los costos de mantenimiento de la mayoría de su código a cero hasta que tenga un número mantenible de partes móviles. El código que nunca necesita ser modificado no incurre en costos de mantenimiento. Recomiendo apuntar a hacer que el código realmente tenga un costo de mantenimiento cero , no intentar reducir el costo en muchas iteraciones de refactorización pequeñas y exigentes. Haz que cueste cero de inmediato.
De acuerdo, es cierto, eso es mucho, mucho más difícil de lo que parece. Pero no es difícil comenzar. Puede tomar una porción de la base de código, probarla, construir una interfaz agradable sobre ella si el diseño de la interfaz es un desastre, y comenzar a hacer crecer las partes de la base de código que son confiables, estables (como faltan razones para cambiar), mientras que simultáneamente reducir las partes que no son confiables e inestables. Las bases de código que se sienten como una pesadilla para mantener a menudo no distinguen las partes móviles que deben cambiarse de las partes que no lo hacen, ya que todo se considera poco confiable y propenso a cambiar.
De hecho, recomiendo que se separe la organización de su base de código en partes "estables" e "inestables", siendo las partes estables una gran PITA para reconstruir y cambiar (lo cual es bueno, ya que no deberían necesitar ser cambiado y reconstruido si realmente pertenecen a la sección "estable").
No es el tamaño de una base de código lo que dificulta el mantenimiento. Es el tamaño de la base de código que debe mantenerse. Depende de millones de líneas de código cada vez que, por ejemplo, uso la API del sistema operativo. Pero eso no contribuye a los costos de mantenimiento de mi producto, ya que no tengo que mantener el código fuente del sistema operativo. Solo uso el código y funciona. El código que simplemente uso y que nunca tengo que mantener no conlleva costos de mantenimiento por mi parte.
fuente