¿Es preferible diseñar de arriba hacia abajo o de abajo hacia arriba?


31

Según tengo entendido, el diseño de arriba hacia abajo es refinando el concepto abstracto de alto nivel en concreto más pequeño y partes comprensibles, hasta que se defina el bloque de construcción más pequeño. Por otro lado, bottom up define partes de bajo nivel, luego gradualmente construye bloques de mayor nivel hasta que se forma todo el sistema.

En la práctica, se dice que es mejor combinar los dos métodos: comienza con una especificación de alto nivel para especificar completamente el conocimiento del dominio, su relación y restricciones. Una vez que se comprende bien el problema, se crean los bloques de construcción más pequeños para construir el sistema.

El proceso de:

  • Crear especificaciones de requisitos
  • Crear una especificación de diseño (con diagramas)
  • Implementar
  • Entregar
  • Repetir (en el desarrollo iterativo, en lugar de hacer una parte completa en cada fase, hacemos un poco cada una repetidamente y tenemos una reunión diaria para adaptarnos a los requisitos dinámicos del cliente)

me parece perfectamente normal (con especificaciones como planes). Tiene sus defectos, pero es por eso que obtuvimos un desarrollo iterativo: en lugar de perder tiempo en una fase, dice, el análisis de requisitos para estudiar todo lo posible en el conocimiento del dominio que está sujeto a cambios (posiblemente a diario), hacemos un poco de análisis, un poco de diseño y luego implementarlo.

Otra forma es que cada iteración es una moda de mini-cascada, donde el análisis se realiza en unos pocos días (o una semana). Lo mismo se aplica para el diseño. El resto del tiempo se dedica a la implementación. ¿Hay algo inherentemente incorrecto con el enfoque de arriba hacia abajo en combinación con el desarrollo iterativo?

En su ensayo Programming Bottom Up , Paul Graham parece alentar la construcción desde abajo hacia arriba por completo, o programarla desde abajo hacia arriba, pero no la fase de análisis / diseño de requisitos:

Los programadores experimentados de Lisp dividen sus programas de manera diferente. Además del diseño de arriba hacia abajo, siguen un principio que podría llamarse diseño de abajo hacia arriba: cambiar el idioma para adaptarse al problema.

En lo que a mí respecta, lo que quiso decir es que Lisper todavía realiza el diseño de arriba hacia abajo, pero el programa de abajo hacia arriba, ¿es eso cierto? Otro punto que escribió:

Vale la pena enfatizar que el diseño ascendente no significa simplemente escribir el mismo programa en un orden diferente. Cuando trabajas de abajo hacia arriba, generalmente terminas con un programa diferente. En lugar de un solo programa monolítico, obtendrá un lenguaje más grande con operadores más abstractos y un programa más pequeño escrito en él. En lugar de un dintel, obtendrás un arco.

¿Esto significa que durante el período de escritura de un programa en Lisp, terminas con una herramienta genérica?


Tal vez si proporciona un enlace al libro, documento o artículo al que se refiere, otros podrían hacer una declaración más razonada con respecto a su pregunta. Además, no está claro de qué trata específicamente su pregunta. ¿Está solicitando consejos sobre algunos detalles con respecto al diseño que desea implementar o haciendo preguntas sobre el artículo al que hace referencia? Quizás esto sería más fácil de leer y responder si lo dividiera en un par de preguntas formuladas por separado.
S.Robins

@ S.Robins Para resumir, demostré mi enfoque habitual de arriba hacia abajo para desarrollar software. Sin embargo, algunas personas prefieren de abajo hacia arriba, y uno de ellos es Paul Graham, que es una persona famosa. Pido entender cómo el diseño ascendente ayuda en general, y específicamente en Lisp, ya que tiene características especiales para alentar (como sugirió Paul).
Amumu

1
Amumu he editado el ejemplo, ya que ambas respuestas parecen haberlo omitido de todos modos. Intente mantener sus preguntas concisas y haga solo una pregunta por pregunta .
yannis

Respuestas:


35

De arriba hacia abajo es una excelente manera de describir cosas que sabes o de reconstruir cosas que ya has construido.

El mayor problema de arriba hacia abajo es que, con frecuencia, simplemente no hay "arriba". Cambiará de opinión sobre lo que debe hacer el sistema al desarrollarlo y al explorar el dominio. ¿Cómo puede ser su punto de partida algo que no sabe (es decir, qué quiere que haga el sistema)?

Un "local" de arriba hacia abajo es algo bueno ... pensar un poco antes de la codificación es claramente bueno. Pero pensar y planificar demasiado no lo es, porque lo que está imaginando no es el escenario real (a menos que ya haya estado allí antes, es decir, si no está construyendo, sino reconstruyendo). De arriba hacia abajo global cuando se construyen cosas nuevas no tiene sentido.

El enfoque de abajo hacia arriba debe ser (globalmente) a menos que conozca el 100% del problema, solo necesita codificar la solución conocida y no le importa buscar posibles soluciones alternativas.

El enfoque de Lisp es el destilado ascendente. No solo construye de abajo hacia arriba, sino que también puede dar forma a los ladrillos de la forma en que los necesita. Nada está arreglado, la libertad es total. Por supuesto, la libertad asume la responsabilidad y puedes hacer cosas horribles haciendo un mal uso de este poder.

Pero un código horrible se puede escribir en cualquier idioma. Incluso en idiomas que tienen forma de jaulas para la mente, diseñados con la esperanza de que con esos idiomas incluso los monos puedan poner en marcha buenos programas (una idea tan errónea en tantos niveles que duele incluso con solo pensarlo).

Su ejemplo es sobre un servidor web. Ahora, en 2012, este es un problema bien definido, tiene especificaciones a seguir. Un servidor web es solo un problema de implementación. Especialmente si su objetivo es escribir un servidor web sustancialmente idéntico a los otros miles de servidores web que existen, entonces nada está realmente claro, excepto algunas minucias. Incluso su comentario sobre RSA sigue hablando de un problema claramente definido, con especificaciones formales.

Con un problema bien definido, con especificaciones formales y soluciones ya conocidas, la codificación solo se conecta en los puntos. De arriba hacia abajo está bien para eso. Este es el cielo del gerente de proyecto.

Sin embargo, en muchos casos no existe un enfoque conocido y probado que se utilice para conectar los puntos. En realidad, muy a menudo es difícil decir incluso cuáles son los puntos.

Supongamos, por ejemplo, que se le pide que instruya a una máquina de corte automática para alinear las piezas que se cortarán con un material impreso que no se ajuste perfectamente al logotipo repetitivo teórico. Se le entregan las partes e imágenes del material tomadas por la máquina.

¿Qué es una regla de alineación? Tú decides. ¿Qué es un patrón, cómo representarlo? Tú decides. ¿Cómo alinear las partes? Tú decides. ¿Se pueden "doblar" las piezas? Depende, algunos no y otros sí, pero, por supuesto, no demasiado. ¿Qué hacer si el material está demasiado deformado para que una parte lo corte aceptablemente? Tú decides. ¿Todos los rollos de material son idénticos? Por supuesto que no, pero no puede molestar al usuario para adaptar las reglas de alineación para cada rollo ... eso sería poco práctico. ¿Qué fotos están viendo las cámaras? El material, lo que sea que eso signifique ... puede ser color, puede ser negro sobre negro donde solo el reflejo de luz hace evidente el patrón. ¿Qué significa reconocer un patrón? Tú decides.

Ahora intente diseñar la estructura general de una solución para este problema y haga una cotización, en dinero y tiempo. Mi apuesta es que incluso la arquitectura de su sistema ... (sí, la arquitectura) estará equivocada. La estimación del costo y el tiempo serán números aleatorios.

Lo implementamos y ahora es un sistema que funciona, pero cambiamos de opinión sobre la forma misma del sistema muchas veces. Agregamos subsistemas completos que ahora ni siquiera se puede acceder desde los menús. Cambiamos los roles maestro / esclavo en los protocolos más de una vez. Probablemente ahora tengamos suficiente conocimiento para intentar reconstruirlo mejor.

Otras compañías, por supuesto, resolvieron el mismo problema ... pero a menos que esté en una de estas compañías, probablemente su proyecto detallado de arriba hacia abajo será una broma. Podemos diseñarlo de arriba hacia abajo. No puedes porque nunca lo hiciste antes.

Probablemente también puedas resolver el mismo problema. Trabajando de abajo hacia arriba sin embargo. Comenzando con lo que sabes, aprendiendo lo que no sabes y sumando.

Se desarrollan nuevos sistemas de software complejos, no diseñados. De vez en cuando, alguien comienza a diseñar un nuevo y complejo sistema de software mal especificado desde cero (tenga en cuenta que con un gran proyecto de software complejo solo hay tres posibilidades: a] la especificación es difusa, b] la especificación es incorrecta y contradictoria o c] ambos ... y más a menudo [c] es el caso).

Estos son los proyectos típicos de grandes empresas con miles y miles de horas arrojados solo a diapositivas de PowerPoint y diagramas UML. Invariablemente fallan por completo después de quemar cantidades embarazosas de recursos ... o, en un caso muy excepcional, finalmente entregan una pieza de software demasiado cara que implementa solo una pequeña parte de las especificaciones iniciales. Y ese software invariablemente es profundamente odiado por los usuarios ... no el tipo de software que compraría, sino el tipo de software que usa porque se ve obligado a hacerlo.

¿Significa esto que creo que deberías pensar solo en el código? Por supuesto no. Pero en mi opinión, la construcción debería comenzar desde abajo (ladrillos, código de concreto) y debería subir ... y su enfoque y atención al detalle deberían, en cierto sentido, "desvanecerse" a medida que se aleja de lo que tiene. A menudo, se presenta de arriba hacia abajo como si tuviera que poner el mismo nivel de detalle en todo el sistema a la vez: solo manténgalo dividiendo cada nodo hasta que todo sea obvio ... en los módulos de realidad, el subsistema "crece" a partir de subrutinas. Si no tiene experiencia previa en el problema específico, su diseño descendente de un subsistema, módulo o biblioteca será horrible. Puede diseñar una buena biblioteca una vez que sepa qué funciones poner, no al revés.

Muchas de las ideas de Lisp se están volviendo más populares (funciones de primera clase, cierres, tipeo dinámico por defecto, recolección de basura, metaprogramación, desarrollo interactivo), pero Lisp sigue siendo hoy (entre los lenguajes que conozco) bastante único en lo fácil que es dar forma al código por lo que necesitas

Los parámetros de palabras clave, por ejemplo, ya están presentes, pero si no estuvieran presentes, podrían agregarse. Lo hice (incluida la verificación de palabras clave en tiempo de compilación) para un compilador de Lisp de juguete con el que estoy experimentando y no requiere mucho código.

En cambio, con C ++, lo máximo que puede obtener es un grupo de expertos en C ++ que le dicen que los parámetros de palabras clave no son tan útiles, o una implementación de plantilla increíblemente compleja, rota y con respaldo medio que de hecho no es tan útil. ¿Son las clases C ++ objetos de primera clase? No, y no hay nada que puedas hacer al respecto. ¿Puedes tener introspección en tiempo de ejecución o en tiempo de compilación? No, y no hay nada que puedas hacer al respecto.

Esta flexibilidad de lenguaje de Lisp es lo que lo hace ideal para la construcción de abajo hacia arriba. Puede construir no solo subrutinas, sino también la sintaxis y la semántica del lenguaje. Y en cierto sentido, Lisp es de abajo hacia arriba.


Sí, lo que quise decir era un top down local. Sigue refinando el requisito y evolucionando a través de las iteraciones. El punto de arriba hacia abajo es que queremos que la estructura del código sea lo más correcta posible. Refactorización continua. Por ejemplo, al principio desea pasar una cadena a una función, pero luego necesita una clase completa para satisfacer los cambios, y la refactorización lleva mucho tiempo si la función ya se usa en todas partes.
Amumu

Para reconstruir algo claramente conocido de una manera claramente conocida, de arriba hacia abajo está bien. Su navegador web es un ejemplo de esto. Incluso usar un lenguaje como C ++ que lo obligue a anticipar y especificar formalmente (usando una sintaxis terrible) todos los tipos para los valores contenidos en todas las variables de su programa sigue siendo una opción viable.
6502

No creo que mi ejemplo sea una forma claramente conocida, porque supongo que está hecho por alguien con conocimientos básicos de comunicación de datos, y el código C ++ es el resultado de un diseño muy básico del conocimiento del dominio (en este caso, redes) . Es un ejemplo de diseño de evolución. En la próxima iteración, el desarrollador aprende más sobre el conocimiento del dominio, por lo que extiende su diseño para que se ajuste al conocimiento evolucionado (como los aspectos de seguridad adicionales en cada capa del servidor web). Luego refina la implementación de acuerdo con el nuevo diseño.
Amumu

El punto aquí es que la solución real derivada de un lenguaje independiente del dominio (como el lenguaje natural, el lenguaje matemático ... depende del dominio). El acto de codificar es solo un mapeo detallado de la solución generada al código.
Amumu

1
Hola señor.6502. Después de un año, a medida que aprendía más cosas, empiezo a ver que lo que dijiste se volvió más cierto. Hice otro hilo: programmers.stackexchange.com/questions/179000/… . Si tienes tiempo, espero que vuelvas a aclarar mi pensamiento: D.
Amumu

6

No estoy seguro de cómo se aplicaría esta respuesta a Lisp, pero acabo de terminar de leer Principios, patrones y prácticas ágiles , y el autor, tío Bob , aboga firmemente por el enfoque de arriba hacia abajo para C # (también aplicable a C ++) con el que completamente de acuerdo.

Sin embargo, a diferencia de otras respuestas que llegaron a la conclusión de que el enfoque de arriba hacia abajo significa que en la primera interacción solo entrega documentos y diseño general, el libro apunta a otro método: TDD combinado con diseño evolutivo.

La idea es que comience desde arriba y defina sus niveles de abstracción más altos (o el más alto local) y tan pronto como se definan, haga que hagan un trabajo útil para que la función # 1 funcione de inmediato. Luego, a medida que agrega más y más funciones, refactoriza su código y evoluciona el diseño según sea necesario, sin perder de vista los principios SÓLIDOS.. De esta manera, no terminará con demasiadas capas de abstracción y no terminará con un diseño de bajo nivel que no se ajuste a la arquitectura general. Si no está seguro de lo que esto significa, el libro que mencioné anteriormente tiene un capítulo completo con un ejemplo donde los desarrolladores toman conceptos y comienzan con diagramas UML y clases de bajo nivel, solo para darse cuenta de que la mitad de esas clases no son necesarias una vez que la codificación realmente comienza. En esencia, con este enfoque, el código de bajo nivel se presiona naturalmente a medida que se definen más detalles de alto nivel en el proyecto.

Y, por último, si practica SOLID, no debe encontrarse con una situación en la que tenga definidas abstracciones de alto nivel y luego entrar en detalles y de repente descubrir que no hay OOD ni abstracciones. Eso no es realmente la culpa del diseño de arriba hacia abajo, sino de la ingeniería perezosa.

Si está interesado en leer más sobre XP y el diseño evolutivo, aquí hay una buena lectura de Martin Fowler, otro gran autor: "Is Design Dead?"


Perfecto. Esto es de lo que hablo. También es bueno saber sobre el principio SOLID, y Agile admite el enfoque de arriba hacia abajo (pensé que con TDD y XP, las personas saltan al código lo antes posible y evitan la documentación).
Amumu

@Amumu: agregué un enlace a otro artículo escrito por Fowler. Puede leer más sobre la diferencia entre la codificación de vaquero sin diseño / documentación frente a lo que XP / Agile realmente está promoviendo. Aunque personalmente, creo firmemente que la documentación tiene valor y he estado promoviendo activamente a mi equipo para mantener nuestros documentos de diseño, he ido cambiando gradualmente mis puntos de vista. Ahora nuestras "tareas de diseño" son todo tipo de cosas de pizarra / servilleta, mientras que los documentos reales se actualizan solo después de escribir la historia. También hemos estado reduciendo lo que está documentado, por lo que los documentos solo cubren cosas de alto nivel / arquitectura
DXM

3

Para mí, los comentarios más importantes que Paul Graham hace en su artículo son estos:

Los programadores [...] de Lisp siguen un principio que podría llamarse diseño de abajo hacia arriba: cambiar el idioma para adaptarlo al problema. En Lisp, no solo escribe su programa hacia el idioma, sino que también lo construye hacia su programa.

O, como se conoce en los círculos de C ++: el diseño de la biblioteca es el diseño del lenguaje (Bjarne Stroustrup)

La idea principal del diseño de arriba hacia abajo es: primero planifica, luego codifica. Beanow tiene razón cuando escribe , que hay problemas cuando el código ejecutable llega tarde en el proceso. En el diseño ascendente siempre tiene código, y ese código puede ser probado.

Además, su código no es plano. Con eso quiero decir, tiende a tener más niveles de abstracciones más pequeñas. En el diseño de arriba hacia abajo, las personas a menudo terminan con grandes abstracciones hasta cierto nivel arbitrario y por debajo de eso no hay abstracciones en absoluto. El código diseñado desde abajo hacia arriba OTOH a menudo contiene menos control de bajo nivel y estructuras de datos, ya que es probable que se abstraigan.


2

Idealmente, escribir un programa en cualquier idioma, no solo Lisp, le permite escribir un conjunto completo de herramientas genéricas que pueden acelerarlo en su próximo programa o acelerar las mejoras en su actual.

En la práctica, hacer un seguimiento de estas herramientas puede ser difícil. La documentación es pobre y desorganizada y las personas que los conocen se van. En la práctica, reutilizar el código suele ser más problemático de lo que vale. Pero si el código está documentado y organizado adecuadamente, y los programadores se quedan (o mantienen su propio suministro de código útil), se puede ahorrar una gran cantidad de trabajo agarrando el código del almacén en lugar de reconstruirlo.

Todo el diseño tiene que estar de arriba hacia abajo o no sabría lo que estaba haciendo. ¿Estás construyendo un cobertizo o un automóvil? No puedes entender eso con un diseño ascendente. Pero si va a construir una rueda delantera izquierda, podría pensar que podría necesitar más ruedas más adelante, tanto para este proyecto como para otros. Y si construyes una rueda reutilizable, tendrás cuatro por el precio de uno. (Y 18 para ese tractor-remolque que está construyendo a continuación, todo gratis).

Tenga en cuenta que, a diferencia de los autos reales y las ruedas reales, si ha construido una "rueda" de software, ha construido un número infinito de ellas.

Más sobre el diseño de arriba hacia abajo: si bien debe comenzar con esto, me siento obligado a señalar que si no puede construir una rueda, debe averiguarlo antes de hacer mucho trabajo en su automóvil. Por lo tanto, debe trabajar de abajo hacia arriba casi en paralelo con el trabajo de arriba hacia abajo. Además, saber que puede construir una rueda puede sugerir muchos proyectos en los que no habría pensado antes, como el tractor-remolque. Creo que el enfoque de arriba hacia abajo tiene que dominar, pero con un toque muy ligero.

Entonces, para seguir parafraseando a Paul Graham, idealmente cuando escribes un programa terminas con muchas partes reutilizables que pueden ahorrar mucho tiempo tanto en el programa original como en otros. Para distanciarme un poco de Paul Graham, esto funciona en cualquier idioma (aunque algunos fomentan el proceso más que otros).

El enemigo de este proceso casi mágico son los programadores con perspectivas a muy corto plazo. Esto puede deberse a defectos de personalidad, pero más comúnmente al cambiar de trabajo y responsabilidades demasiado rápido, tanto dentro como entre empresas que lo emplean.


Wow, me respondieron desde el 16 de febrero y la bandeja de entrada no mostraba nada. Estoy de acuerdo con usted, siempre es necesario tener un plan, incluso uno muy ligero, y luego construir un poco y luego refinar el plan. Es perfectamente razonable. Sin embargo, no parece entender por qué a muchos desarrolladores les encanta planear todo en su cabeza mientras escriben código. Se puede ahorrar una buena cantidad de tiempo y pueden enfocarse para trabajar más en el problema real en lugar de refactorizar continuamente la arquitectura del código a lo largo del proceso de implementación.
Amumu

@Amumu Una razón para planificar mientras escribe: Algunos programadores saben mucho sobre lo que están haciendo con el teclado y el mouse, son su cuello de botella de codificación, no el proceso de planificación. (El código reutilizable les permitiría hacer un trabajo nuevo y creativo que les permitiría pensar de nuevo en la parte difícil)
RalphChapin,

@Amumu Otra razón para planificar mientras escribe: con algunas combinaciones de lenguaje / programador / problema, el idioma es la mejor manera de pensar sobre el problema. El lenguaje es la forma más fácil de poner sus pensamientos en "papel". Solo he leído pequeños fragmentos aquí y allá sobre Lisp, pero por eso sospecho que es especialmente bueno para esto. (Algunos idiomas se crearon por primera vez como una forma de expresar un problema en lugar de hacer que una máquina lo resuelva). Si es tan fácil reorganizar su código como sus pensamientos, también podría codificarlo. Pero el programador, el lenguaje y el problema deben combinar bien para que esto funcione.
RalphChapin

Es confiable La idea de la planificación es separar el conocimiento del dominio y las identificaciones de relación entre los conceptos del dominio del proceso de programación real. Imagínese si una docena de otras clases utilizan una clase, sin un plan adecuado para crear la interfaz para comunicarse con otras clases, podemos entrar fácilmente en la integración y refactorizar el infierno. En este caso, el tiempo perdido para escribir y reestructurar el código excede en gran medida el tiempo dedicado a la identificación en papel.
Amumu

Tomemos un ejemplo: supongamos que estamos escribiendo una aplicación cliente / servidor. Lo primero que tenemos que identificar es el protocolo definiendo los mensajes de intercambio, cómo se presenta el mensaje en la memoria (es decir, encabezado 16 bytes, id 4 bytes ...). El diagrama no es igual a modelado / planificación: abstratt.com/blog/2009/05/03/on-code-being-model . No necesitamos usar UML o diagrama para modelar, ya que UML se enfoca más o menos solo en OOP, y el tiempo dedicado a escribir en el diagrama de clase UML no es menor que escribir el código real, si no más.
Amumu

1

¿Qué tiene de malo el enfoque de arriba hacia abajo en combinación con el desarrollo iterativo?

El uso de un enfoque de arriba hacia abajo con desarrollo iterativo no entrega ningún código de trabajo en las primeras iteraciones. Ofrece documentos de diseño y de tal forma que el cliente tendrá dificultades para dar su opinión. Respuestas como, "sí, supongo (esto es demasiado abstracto para mí)" no lo ayudarán a especificar los detalles del sistema deseado por parte del cliente.

En cambio, solo crea una descripción general de lo que se solicita. (Requisitos) para usar como una guía general para lo que define un producto terminado y lo que merece prioridad para implementar. A partir de ahí, crea productos de trabajo para cada iteración con los que el cliente puede jugar para ver si esto era lo que tenía en mente. De lo contrario, no será un problema, ya que no se han dedicado cientos de horas a diseñar un sistema que funcione de manera diferente a lo que el cliente pide ahora.

¿Paul Graham alentó la construcción desde abajo hacia arriba por completo? ¿O simplemente programarlo de abajo hacia arriba, pero no la fase de análisis / diseño de requisitos?

No hablaré por otra persona. Tampoco leí su ensayo. La respuesta que te doy proviene de mi educación y de mis experiencias.

Esto significa que, durante el período de escritura de un programa en Lisp, terminas con una herramienta genérica que puede usarse para escribir programas similares al original, ¿no?

El uso de este enfoque significa que terminará con más bloques de construcción abstractos. Simplemente porque tendrá que construir subsistemas independientes que se puedan presentar de inmediato, no puede crear un diseño de arriba hacia abajo que esté muy entrelazado y deba implementarse de una vez. Mejora la reutilización, el mantenimiento, pero sobre todo permite una respuesta más flexible al cambio.


1
Boldface, a mis ojos, es innecesario cuando ya estás citando en bloque ...
mk12
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.