¿Cómo mantener un producto de software grande y complejo mantenible a lo largo de los años?

156

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?

chrmue
fuente
99
libros muy recomendables: 'Guía de supervivencia de proyectos de software' de Steve McConnell, 'Desarrollo rápido' de Steve McConnell, 'Refactorización' de Martin Fowler
Imran Omar Bukhsh
15
... y 'Clean Code' de Uncle Bob;) (Robert C. Martin)
Gandalf
34
¿No es esta misma pregunta algo que ha generado varias décadas de lectura pesada y cursos completos en las universidades?
desviarse el
17
@Eric Yin - No estoy de acuerdo con los comentarios. Para mí son un olor a código y, a largo plazo, los proyectos tienden a hacer más daño que bien porque inevitablemente se desactualizan y se vuelven engañosos.
JohnFx
8
@Eric Yin: lucha por el código autodocumentado. Use comentarios de intención solo donde mejoren la comprensión.
Mitch Wheat

Respuestas:

138

¡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:

  1. Mantenlo simple.
  2. Mantenlo simple. Esto se aplica especialmente a la arquitectura, el "panorama general". Si los desarrolladores están teniendo dificultades para conseguir el cuadro grande, que se van a codificar en contra de ella. Así que simplifique la arquitectura para que todos los desarrolladores la obtengan. Si la arquitectura tiene que ser menos que simple, entonces los desarrolladores deben estar capacitados para comprender esa arquitectura. Si no lo internalizan, entonces no deberían codificarlo.
  3. Objetivo de bajo acoplamiento y alta cohesión . Asegúrese de que todos en el equipo entiendan esta idea. En un proyecto que consta de partes cohesivas y poco acopladas, si algunas de las partes se vuelven desarmables, simplemente puede desconectar y reescribir esa parte. Es más difícil o casi imposible si el acoplamiento está apretado.
  4. Se consistente. Las normas a seguir importan poco, pero sí siga algunas normas. En un equipo, todos deben seguir los mismos estándares, por supuesto. Por otro lado, es fácil apegarse demasiado a los estándares y olvidarse del resto: tenga en cuenta que si bien los estándares son útiles, son solo una pequeña parte de hacer un buen código. No hagas un gran número de eso.
  5. Las revisiones de código pueden ser útiles para lograr que un equipo trabaje de manera consistente.
  6. Asegúrese de que todas las herramientas (IDE, compiladores, control de versiones, sistemas de compilación, generadores de documentación, bibliotecas, computadoras , sillas , entorno general, etc.) estén bien mantenidas para que los desarrolladores no tengan que perder su tiempo con problemas secundarios como como conflictos de versiones de archivos de proyectos de lucha, actualizaciones de Windows, ruido y cualquier cosa banal pero irritante. Tener que perder repetidamente un tiempo considerable con cosas tan poco interesantes reduce la moral, lo que al menos no mejorará la calidad del código. En un equipo grande, podría haber uno o más tipos cuyo trabajo principal es mantener las herramientas de desarrollo.
  7. Al tomar decisiones tecnológicas, piense qué se necesitaría para cambiar la tecnología; qué decisiones son irreversibles y cuáles no. Evaluar las decisiones irreversibles con mucho cuidado. Por ejemplo, si decide escribir el proyecto en Java , esa es una decisión prácticamente irreversible. Si decide usar un formato binario autoelaborado para archivos de datos, esa también es una decisión bastante irreversible (una vez que el código está en libertad y tiene que seguir admitiendo ese formato). Pero los colores de la interfaz gráfica de usuario se pueden ajustar fácilmente, las características que inicialmente se omiten se pueden agregar más adelante, por lo que no se preocupe tanto por tales problemas.
Joonas Pulakka
fuente
8
Estos son grandes puntos. Debo admitir que lucho con "mantenerlo simple". Parece que significa diferentes cosas para diferentes personas en diferentes contextos, lo que hace que "simple" sea bastante complejo (pero tengo una tendencia natural a complicar las cosas).
Kramii
3
Estoy perfectamente de acuerdo con sus puntos, especialmente "KIS". Pero veo una tendencia a que más y más desarrolladores (¿más jóvenes?) Usen estructuras bastante complejas para describir incluso los contextos más simples.
chrmue
10
@chrmue: Vea "cómo escribir Factorial en Java" ;-)
Joonas Pulakka
8
Umm, y "8. Hazlo simple".
Dawood ibn Kareem
77
# 6 es digno de un +10.
Jim G.
55

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.

Tom Squires
fuente
3
Punto muy importante. Me hice cargo de un sistema heredado, muchas clases, muchas líneas de código, sin documentación, sin pruebas unitarias. Después de crear diligentemente pruebas unitarias para todas las correcciones y mejoras de código, el diseño del sistema ha evolucionado hacia un estado más limpio y más fácil de mantener. Y tenemos el "coraje" para reescribir partes centrales significativas (cubiertas por pruebas unitarias).
Sam Goldberg
40

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.

maple_shaft
fuente
Muy buen punto, ¡le pegaste al blanco! Es triste decirlo, pero eso es exactamente lo que está sucediendo aquí. Y parece imposible cambiar las cosas sin el líder tecnológico ...
chrmue
44
@chrmue Tal como van las cosas, supongo, pero me estoy cansando de eso. En muchos sentidos, desearía volver a ser un desarrollador junior cuando no estaba tan consciente de lo mal que parece estar todo lo que me rodea. Parece que estoy llegando a mi crisis de mitad de carrera temprano. Pero estoy divagando ... encantado de ayudar.
maple_shaft
1
@Murph ¿Por qué no debería tener un equipo que lo sepa todo durante la fase de Mantenimiento? Independientemente de todos los jefes que he esperado de un líder de equipo, y cuando era líder de equipo, no esperaba menos de mí mismo.
maple_shaft
1
@maple_shaft porque a) No supongo que haya un líder de equipo dedicado a ese proyecto y ese es más o menos el primer requisito yb) Creo que necesita comprender el diseño y la implementación (todo) y eso es difícil. Por un lado, todos argumentamos que no deberíamos tener una sola fuente de todos los conocimientos en nuestros proyectos y, sin embargo, aquí estamos diciendo que tenemos que tener uno. Eso no cuadra?
Murph
2
@maple_shaft probablemente soy un viejo programador gruñón (-: Pero hay un problema en que a menudo es el "estilo" de implementación que debe seguirse profundamente en una base de código existente, y que puede ser ajeno tanto al codificador como al líder (para muchos de razones del mundo real)
Murph
18

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.

Kramii
fuente
1
+1, especialmente para "formar un equipo que pueda funcionar bien en conjunto y enviar un producto que funcione".
Deworde
1
Es una buena idea tener a una persona como la raíz responsable de la arquitectura. Sin embargo, usted tiene un "olor a equipo" si esa responsabilidad se usa con frecuencia: el equipo debería naturalmente llegar a conclusiones comunes, en lugar de depender de una persona para proporcionar las respuestas. ¿Por qué? El conocimiento total del proyecto siempre se comparte, fijarlo en una persona conducirá a mayores problemas al final: solo su punto de vista está satisfecho, cortando efectivamente las alas del resto del equipo. En lugar de eso, contrata a las mejores personas y deja que lo resuelvan juntos.
casper
1
@casper: Exactamente. Expresas lo que tenía en mente mejor que yo.
Kramii
18

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:

  1. Use estándares para nombrar, codificar, registrar, flujo de errores, flujo de procesos, básicamente cualquier cosa.
  2. No tenga miedo de decir adiós a los miembros del equipo que no cumplan con los estándares. Algunos desarrolladores simplemente no pueden trabajar dentro de un conjunto definido de estándares y se convertirán en enemigos de la quinta columna en el campo de batalla para mantener limpio el código base
  3. No tenga miedo de asignar miembros del equipo menos calificados a las pruebas durante largos períodos de tiempo.
  4. Use todas las herramientas de su arsenal para evitar registrar el código podrido: esto implica herramientas dedicadas, así como pruebas unitarias previamente escritas que prueban los archivos de compilación, los archivos de proyecto, la estructura de directorios, etc.
  5. En un equipo de aproximadamente 5-8 miembros, haga que su mejor tipo refactorice casi constantemente, limpiando el desorden que los demás dejan atrás. Incluso si encuentra los mejores especialistas en el campo, seguirá teniendo un desastre: es inevitable, pero puede verse limitado por la refactorización constante.
  6. Escriba pruebas unitarias y manténgalas; NO dependa de las pruebas unitarias para mantener limpio el proyecto, no lo hacen.
  7. Discutir todo. No tengas miedo de pasar horas para discutir cosas en el equipo. Esto difundirá la información y eliminará una de las causas principales del mal código: confusión sobre tecnologías, objetivos, estándares, etc.
  8. Tenga mucho cuidado con los consultores que escriben código: su código será, casi por definición, el verdadero material de mierda.
  9. Haga las revisiones preferiblemente como el paso del proceso antes de registrarse. No tengas miedo de deshacer los commits.
  10. Nunca use el principio de apertura / cierre a menos que en la última etapa antes del lanzamiento: simplemente conduce a que el código podrido se deje oler.
  11. Siempre que se encuentre un problema, tómese el tiempo para comprenderlo al máximo antes de implementar una solución: la mayor parte de la descomposición del código proviene de la implementación de la solución a problemas que no se comprenden completamente.
  12. Utiliza las tecnologías adecuadas. Estos a menudo vendrán en conjuntos y serán nuevos: es mejor depender de una versión beta de un marco en el que tenga garantizado el soporte en el futuro, que depender de marcos extremadamente estables, pero obsoletos, que no son compatibles.
  13. Contrata a las mejores personas.
  14. Deja el resto, no estás dirigiendo una cafetería.
  15. Si la gerencia no es la mejor arquitecta e interfiere en el proceso de toma de decisiones, busque otro trabajo.
casper
fuente
1
Ahora, ¿qué haría si tuviera que mantener y mejorar una aplicación VB6 de pesadilla de 12 años con cientos de formularios y clases mientras trabajaba en una reescritura de C # en el 'tiempo libre'?
jfrankcarr
77
No estoy de acuerdo con el n. ° 3. Las pruebas no deben verse como un castigo para los no entrenados. De hecho, debe ser realizado por todos los desarrolladores, ¡y contar tan igualmente importante como la codificación y el diseño! Si tienes al Sr. Sin Entrenamiento en el equipo, ¡sácalo del equipo para entrenarlo!
NWS
1
"No estoy de acuerdo con el # 3. Las pruebas no deben verse como un castigo para los no entrenados". - No debería y no es lo que escribí, déjame explicarte: Las pruebas son una buena forma de permitir que las personas en las que no confías, aún confían en realizar cambios para entrar en el código. Los mejores probadores que he encontrado son los que aspiran a convertirse en contribuyentes del código y están demostrando su competencia al revisar el código, ejecutar el programa y muestran la capacidad de correlacionar sus hallazgos en tiempo de ejecución con el código fuente. No es un castigo, es entrenamiento.
casper
1
"No estoy de acuerdo con el # 10: eso ayuda con la calidad del código si se hace correctamente" Esto es absolutamente falso en mi experiencia laboral: el código bloqueado no se puede refactorizar, lo que significa que permanecerá en su estado actual hasta que se desbloquee. Se puede verificar que este estado funciona en alguna etapa, pero en una etapa posterior esta verificación es un falso positivo: todo el código debe dejarse abierto para refactorizar hasta la etapa anterior a la prueba final del sistema.
casper
3
@casper: En mi humilde opinión, el principio abierto / cerrado no debe entenderse como "no se puede cambiar la fuente", sino "diseñar el código como si se congelara". Asegúrese de que es posible extender el código como necesario sin requerir cambios en él. El resultado es inherentemente más débilmente acoplado y altamente cohesivo que el código promedio. Este principio también es crucial al desarrollar una biblioteca para uso de terceros, ya que no pueden simplemente entrar y modificar su código, por lo que necesita que sea extensible correctamente.
Kevin Cathcart
12

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:

  • Desarrollar una nueva característica
  • Arreglar un problema

Acelere enormemente su ciclo de desarrollo de prueba primero:

  • Refactorización para convertir módulos de código a un lenguaje de script
  • Use máquinas de prueba rápidas basadas en la nube

Código de refactorización para usar un acoplamiento bajo (de unidades altamente cohesivas internamente) mediante:

  • Funciones más simples (más) (rutinas)
  • Módulos
  • Objetos (y clases o prototipos)
  • Funciones puras (sin efectos secundarios)
  • Delegación preferible, sobre herencia
  • Capas (con API)
  • Colecciones de pequeños programas de un solo propósito que pueden operar juntos

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.

MarkDBlackwell
fuente
1
+1 ¡Buena primera respuesta, forma perfecta de presentarte!
Yannis
11

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.

Joh
fuente
9

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.

JohnFx
fuente
8

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.

StuartLC
fuente
7

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.

Rotsor
fuente
2
Otro nombre para lo que describe es la propiedad de ser determinista
jinglesthula
6

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 .

james
fuente
6

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"

Aatch
fuente
Siempre he encontrado que los comentarios de función y clase son útiles, incluso si solo proporcionan separación de segmentos de código en ubicaciones de función y clase mediante el resaltado de sintaxis. Raramente pongo comentarios en el código de función, pero escribo una línea para cada clase y función, como /** Gets the available times of a clinic practitioner on a specific date. **/o /** Represents a clinic practitioner. **/.
Nick Bedford
5

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.

Giorgio
fuente
¿Qué pasa si los requisitos comerciales expresados ​​en el código de trabajo cambian? No tocar un código mal factorizado pero técnicamente "funcional" puede perjudicarlo a largo plazo, especialmente cuando llega el momento de realizar los cambios necesarios.
jinglesthula
@jinglesthula: Abrir para extensión significa que puede agregar la nueva funcionalidad como una nueva implementación (por ejemplo, clase) de una interfaz existente. Por supuesto, un código mal estructurado no permite esto: debe haber una abstracción como una interfaz que permite que el código "cambie" agregando un nuevo código en lugar de modificar el código existente.
Giorgio
5
  • 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.

Tim Williscroft
fuente
4

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.

dwenaus
fuente
3

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.

jfrankcarr
fuente
2

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.

Peter Mortensen
fuente
Forzado por las circunstancias? Las personas introducen hacks porque (a) no saben mejor => necesitan entrenamiento, (b) no ven la imagen más grande => comunicación, documentación y disciplina necesarias, (c) piensan que son más inteligentes => eso es lo más grande obstáculo para superar (d) forzado por las circunstancias => las revisiones rápidas están bien cuando están presionadas por el tiempo, pero alguien tiene que asumir la responsabilidad y limpiar el código después. Cualquier otra "circunstancia" es simplemente BS . Hay excepciones a esa regla de oro, pero las excepciones más llamadas deletrean "flojo".
JensG
2

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:

  • Organiza y simplifica
  • Acortar funciones
  • Priorice la legibilidad sobre la eficiencia (cuando sea aceptable, por supuesto, y algunas ganancias de eficiencia son demasiado miserables para mantenerse)

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.

Eric.Void
fuente
"... el proyecto está condenado" - en mi experiencia, esto no es necesariamente así. La alternativa es educar a dicho gerente y convencerlo de "invertir" los esfuerzos en el mantenimiento y abordar la deuda técnica
mosquito
Lo siento, no pude evitar escribir eso, ya que tuve una experiencia cuando el gerente ignoró todos mis consejos de deuda técnica. Pero creo que tienes razón: aproximadamente un año después de que abandoné ese proyecto, el gerente perdió toda su autoridad sobre el equipo técnico por su incapacidad para administrarlos.
Eric.Válido
1

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.

juhist
fuente
Esto parece ser un poco teórico desde mi punto de vista. Por supuesto, los proyectos que mencioné en mi pregunta se construyeron con múltiples bibliotecas y módulos. Mi experiencia en los últimos 26 años de desarrollo de software es que cuanto mayor es el proyecto se pone en la organización inicial
chrmue
0

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.

user204677
fuente