¿Qué cambios son demasiado grandes para que el diseño adecuado los facilite?


11

Esta es una pregunta bastante vaga, pero es algo que nunca he sentido que haya sido respondida de manera satisfactoria al leer sobre el diseño adecuado.

En general, al aprender sobre programación orientada a objetos, abstracción, factorización, etc., el santo grial del diseño, y la razón por la que siempre afirman que está utilizando las técnicas de desarrollo en cuestión, es que hará que su programa sea "fácil de cambiar" , "mantenible", "flexible", o cualquiera de los sinónimos utilizados para expresar un concepto tan productivo. Al marcar los ivars como privados, dividiendo el código en muchos métodos pequeños y autónomos, manteniendo las interfaces generales, supuestamente obtienes la capacidad de modificar tu programa con total facilidad y gracia.

Para cambios relativamente pequeños, esto ha funcionado bien para mí. Los cambios en las estructuras de datos internos utilizados por una clase para aumentar el rendimiento nunca han sido una gran dificultad, y tampoco lo han hecho los cambios en el extremo de la interfaz de usuario, independientemente de la API, como el rediseño de un sistema de entrada de texto o la revisión de los gráficos para un elemento de juego .

Todos estos cambios parecen inherentemente autónomos. Ninguno de ellos implica ningún cambio en el comportamiento o diseño del componente de su programa que se está modificando, en lo que respecta al código externo en cuestión. Ya sea que esté escribiendo o no de forma procedimental o en un estilo OO, con funciones grandes o pequeñas, estos son cambios fáciles de realizar incluso si tiene un diseño moderadamente bueno.

Sin embargo, cada vez que los cambios se vuelven grandes y difíciles, es decir, cambios en la API, ninguno de mis preciosos "patrones" viene al rescate. El gran cambio sigue siendo grande, el código afectado sigue afectado y muchas horas de trabajo de generación de errores me esperan.

Así que mi pregunta es esta. ¿Qué tan grande es el cambio que el diseño apropiado dice que puede facilitar? ¿Existe alguna técnica de diseño adicional, ya sea desconocida para mí o que no he podido implementar, que realmente hace que la modificación de sonido pegajoso sea simple, o esa promesa (que he escuchado hecha por tantos paradigmas diferentes) es simplemente una buena idea, completamente desconectado de las verdades inmutables del desarrollo de software? ¿Existe una "herramienta de cambio" que pueda agregar a mi cinturón de herramientas?

Específicamente, el problema al que me enfrento que me llevó a los foros es este: he estado trabajando en la implementación de un lenguaje de programación interpretado (implementado en D, pero eso no es relevante), y he decidido que los argumentos de mis cierres deberían ser basado en palabras clave, en lugar de posicional como lo son actualmente. Esto requiere modificar todo el código existente que llama funciones anónimas, que, por suerte, es bastante pequeño porque estoy en el desarrollo de mi lenguaje (<2000 líneas), pero sería enorme si hubiera tomado esta decisión en una etapa posterior. En tal situación, ¿hay alguna manera de que, mediante una previsión adecuada en el diseño, podría haber hecho esta modificación más fácil, o hay ciertos (la mayoría) cambios intrínsecamente de gran alcance? Tengo curiosidad si esto es de alguna manera un fracaso de mis propias habilidades de diseño; si lo es, yo '

Para ser claros, no soy en absoluto escéptico con respecto a la POO ni a ninguno de los otros patrones comúnmente utilizados. Para mí, sin embargo, sus virtudes están en la escritura original, más que en el mantenimiento de las bases de código. La herencia le permite abstraer bien los patrones repetitivos, el polimorfismo le permite separar el código por función entendida por el ser humano (qué clase) en lugar de por efecto entendido por la máquina (qué rama de la switchdeclaración), y las funciones pequeñas y autónomas le permiten escriba en un estilo "de abajo hacia arriba" muy agradable. Sin embargo, soy escéptico de sus reclamos de flexibilidad.


No veo cómo el cambio en su idioma toca nada más que el analizador. ¿Podrías aclarar esto?
scarfridge

En un lenguaje similar a smalltalk, es cierto que solo necesitaría modificar el analizador, porque sería una simple cuestión de tratar foo metarg1: bar metarg2: bazcomo foo.metarg1_metarg2_(bar, baz). Sin embargo, mi idioma está haciendo un cambio mayor, es decir, los parámetros basados ​​en la lista a los parámetros basados ​​en el diccionario, lo que afecta el tiempo de ejecución pero no el analizador, en realidad, debido a las propiedades específicas de mi idioma que no entraré en este momento. Dado que no hay un mapeo claro entre palabras clave desordenadas y argumentos posicionales, el tiempo de ejecución es el problema principal.
existe para el

Respuestas:


13

A veces, un cambio es lo suficientemente grande como para diseñar una ruta de migración. Incluso si los puntos de inicio y finalización están bien diseñados, a menudo no puede simplemente cambiar el pavo frío. Muchos diseñadores buenos no pueden diseñar buenas rutas de migración.

Esta es la razón por la que creo que cada programador debe hacer un software de escritura temporal para entornos de producción 24/7. Hay algo sobre las pérdidas que se citan en el orden de su salario anual por minuto que lo motiva a aprender a escribir un código de migración sólido.

Lo que la gente hace naturalmente para un gran cambio es eliminar la vieja manera, ponerla en la nueva forma, luego pasar un par de días arreglando un montón de errores de compilación, hasta que su código finalmente se esté compilando nuevamente para que pueda comenzar a probar. Dado que el código no se pudo probar durante dos días, de repente tienes un gran lío de errores enredados para resolver.

Para un gran cambio de diseño, como cambiar de argumentos posicionales a palabras clave, una buena ruta de migración sería agregar primero soporte para argumentos de palabras clave sin eliminar el soporte posicional . Luego, cambia metódicamente el código de llamada para usar argumentos de palabras clave. Esto es similar a cómo lo hizo antes, excepto que esta vez puede probar a medida que avanza, porque el código de llamada sin cambios todavía funciona. Debido a que sus cambios son más pequeños entre las pruebas, los errores son más fáciles de solucionar antes. Luego, cuando se ha cambiado todo el código de llamada, es trivial eliminar el soporte posicional.

Esta es la razón por la cual las API publicadas desprecian los métodos antiguos en lugar de eliminarlos. Proporciona una ruta de migración perfecta para llamar al código. Puede parecer más trabajo apoyar a ambos por un tiempo, pero recuperas el tiempo en las pruebas.

Por lo tanto, para responder a su pregunta como se formuló originalmente, casi no hay cambios que sean demasiado grandes para que el diseño adecuado los haga más fáciles. Si su cambio parece demasiado grande, solo necesita diseñar algunas etapas intermedias temporales para que su código migre.


+1 por admitir el nuevo caso y el caso anterior para que pueda eliminar gradualmente a medida que avanza.
Erik Reppen

9

Te recomiendo que leas el ensayo Big Ball of Mud .

Básicamente, el punto de que el diseño tiende a deteriorarse a medida que avanza con el desarrollo y tiene que dedicar trabajo a contener la complejidad. La complejidad en sí no puede eliminarse, solo contenerse, porque es inherente al problema que está tratando de resolver.

Esto lleva a los principios fundamentales del desarrollo ágil. Los dos más relevantes son "no lo va a necesitar" diciéndole que no prepare el diseño para las características que aún no está a punto de implementar, porque es poco probable que obtenga el diseño correcto de todos modos y "refactorice sin piedad" diciéndole que trabaje en mantener la cordura del código durante todo el proyecto.

Parece que la respuesta es que ningún diseño es capaz de facilitar ningún cambio más allá de los ejemplos artificiales diseñados específicamente para mostrar que el diseño es más fuerte de lo que realmente es.

En una nota al margen, debe ser escéptico sobre la programación orientada a objetos. Es una gran herramienta en muchos contextos, pero debe ser consciente de sus límites. Especialmente el mal uso de la herencia puede conducir a un código increíblemente complicado. Por supuesto, debe ser igualmente escéptico sobre cualquier otra técnica de diseño. Cada uno tiene sus usos y cada uno tiene sus puntos débiles. No hay bala de plata.


1
+1: el diseño inicial novedoso rara vez tiene éxito. Los diseños exitosos son recapitulaciones de diseños probados, o fueron refinados iterativamente.
Kevin Cline

1
+1 debe ser escéptico de todos los conceptos de programación, solo a través de la crítica objetiva aplicamos nuestras habilidades analíticas para encontrar la solución correcta en lugar de solo una solución .
Jimmy Hoffa

4

En términos generales, hay "partes de código" (funciones, métodos, objetos, lo que sea) e "interfaces entre partes de código" (API, declaraciones de funciones, lo que sea; incluido el comportamiento).

Si se puede cambiar un fragmento de código sin cambiar la interfaz de la que dependen otros fragmentos de código, entonces el cambio será más fácil (y no importa si usa OOP o no).

Si una parte del código no se puede cambiar sin cambiar la interfaz de la que dependen otras partes del código, entonces el cambio será más difícil (y no importa si usa OOP o no).

Los principales beneficios de OOP son que las "interfaces" públicas están claramente marcadas (por ejemplo, los métodos públicos de un objeto) y otro código no puede usar interfaces internas (por ejemplo, los métodos privados del objeto). Debido a que las interfaces públicas están claramente marcadas, las personas tienden a ser más cuidadosas al diseñar esas interfaces públicas (lo que ayuda a evitar los cambios más difíciles). Debido a que las interfaces internas no pueden ser utilizadas por otro código, puede cambiarlas sin preocuparse por otro código que dependa de ellas.


Otra ventaja de OOP es que encapsular los datos con la lógica que opera en él conduce a interfaces públicas más pequeñas (si se hace bien).
Michael Borgwardt

2

No existe una metodología de diseño que pueda salvarlo de las molestias de un gran cambio de requisitos / alcance. Es simplemente imposible imaginar y tener en cuenta todos los cambios posibles que podrían pensarse en una fecha posterior.

Donde un buen diseño lo ayuda es ayudarlo a comprender cómo encajan todas las partes y qué se verá afectado por el cambio propuesto.
Además, un buen diseño puede limitar las partes afectadas por la mayoría de los cambios propuestos.

En resumen, un buen diseño facilita todos los cambios, pero un buen diseño no puede convertir un gran cambio en uno pequeño. (Por otro lado, un diseño malo / no puede convertir cualquier cambio pequeño en uno grande).


1

Dado que el diseño tiene la intención de llevar un proyecto de desarrollo desde cero nada en blanco a un producto final confiable que funcione (al menos el diseño para uno), y ese es el cambio más grande que puede haber en un proyecto, dudo que exista tal cosa como Un cambio demasiado grande para manejar.

Si algo es lo contrario. Algunos cambios son demasiado pequeños para molestarse con cualquier "diseño" o pensamiento elegante. Correcciones de errores simples, por ejemplo.

Recuerde que los patrones de diseño son para ayudar a pensar con claridad y encontrar buenas soluciones de diseño para situaciones comunes, como crear grandes cantidades de pequeños objetos de tipo idéntico. No hay una lista de patrones completa. Usted, y todos los demás, tendremos problemas de diseño que no son comunes, que no encajan en ningún casillero. Los intentos de hacer cada pequeño movimiento en un proceso de desarrollo de software de acuerdo con un patrón oficial u otro, es una forma demasiado religiosa de hacer las cosas, y conduce a problemas inmanejables.

Todo el trabajo en software es, naturalmente, la generación de errores. Los futbolistas se lesionan. Los músicos tienen callos o espasmos labiales dependiendo de su instrumento. (Si tienes espasmos en los labios mientras tocas la guitarra, lo estás haciendo mal). Los desarrolladores de software tienen errores. Nos encanta encontrar formas de reducir su tasa de desove, pero nunca será cero.


3
La posición inicial también puede ser negativa. No subestimes el poder de Legacy :)
Maglob

Diseñar un proyecto desde cero es la parte fácil. Pero incluso si puede comenzar un nuevo proyecto desde cero, lo cual es raro en sí mismo, solo disfrutará construir desde cero durante los primeros días. La primera decisión de diseño inicial resultó ser incorrecta y las cosas solo comenzarán a ir cuesta abajo a partir de ahí. El diseño desde cero es totalmente irrelevante en la práctica.
Jan Hudec

1

El costo de los cambios radicales suele ser proporcional al tamaño de la base del código. Por lo tanto, la reducción del tamaño de la base del código siempre debe ser uno de los objetivos de cualquier diseño adecuado.

En su ejemplo, el diseño adecuado ya ha facilitado su trabajo: tener que hacer cambios en una base de código de 2000 líneas, en lugar de una línea de código de 20000 o 200000 líneas.

El diseño adecuado reduce los cambios radicales, pero no los elimina.

Los cambios radicales se pueden automatizar con bastante facilidad si las herramientas de análisis del lenguaje ofrecen el soporte adecuado. Se puede aplicar un cambio de refactorización general a toda la base del código en un estilo de búsqueda y reemplazo (respetando adecuadamente las reglas del lenguaje). El programador solo necesita hacer un juicio de sí / no por cada búsqueda.


+1 para sugerencia de automatización. Creo que realmente lo usaré para muchas de las funciones de mi biblioteca que llaman a cierres: es una simple cuestión de detectar qué funciones toman bloques y modificarlas para tener un parámetro de símbolo adicional para que el nombre del argumento pase el valor como.
existe-para el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.