PROGRAMACIÓN DE ABAJO HACIA ARRIBA
OriginalEnero de 1993
(Este ensayo es de la introducción a On Lisp .)
Un principio de larga data en el estilo de programación es que los elementos funcionales de un programa no deben ser demasiado grandes. Si algún componente de un programa crece más allá de la etapa en que es fácilmente comprensible, se convierte en una masa de complejidad que oculta errores con la misma facilidad con la que una gran ciudad oculta fugitivos. Ese software será difícil de leer, difícil de probar y difícil de depurar.
De acuerdo con este principio, un programa grande debe dividirse en partes, y cuanto más grande sea el programa, más debe dividirse. ¿Cómo se divide un programa? El enfoque tradicional se llama diseño de arriba hacia abajo: se dice "el propósito del programa es hacer estas siete cosas, así que lo divido en siete subrutinas principales. La primera subrutina tiene que hacer estas cuatro cosas, por lo que a su vez tendrá cuatro de sus propias subrutinas", y así sucesivamente. Este proceso continúa hasta que todo el programa tiene el nivel adecuado de granularidad: cada parte es lo suficientemente grande como para hacer algo sustancial, pero lo suficientemente pequeña como para ser entendida como una sola unidad.
Los programadores experimentados de Lisp dividen sus programas de forma 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 lenguaje para que se adapte al problema. En Lisp, no sólo escribes tu programa hacia abajo en dirección al lenguaje, sino que también construyes el lenguaje hacia arriba en dirección a tu programa. Mientras escribes un programa, puedes pensar: "Ojalá Lisp tuviera tal y tal operador". Así que te pones a escribirlo. Después te das cuenta de que usar el nuevo operador simplificaría el diseño de otra parte del programa, y así sucesivamente. El lenguaje y el programa evolucionan juntos. Como la frontera entre dos estados en guerra, la frontera entre el lenguaje y el programa se dibuja y vuelve a dibujar, hasta que finalmente se detiene en las montañas y los ríos, las fronteras naturales de tu problema. Al final, tu programa parecerá como si el lenguaje hubiera sido diseñado para él. Y cuando el lenguaje y el programa se adaptan bien, terminas con un código claro, pequeño y eficiente.
Vale la pena enfatizar que el diseño de abajo hacia arriba no significa simplemente escribir el mismo programa en un orden diferente. Cuando se trabaja de abajo hacia arriba, generalmente se termina con un programa diferente. En lugar de un programa único y monolítico, se obtiene 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, se obtiene una arquitectura.
En un código típico, una vez que se abstraen las partes que son meramente de contabilidad, lo que queda es mucho más corto; cuanto más se construye el lenguaje, menos distancia hay que recorrer desde arriba hasta abajo. Esto trae varias ventajas:
Al hacer que el lenguaje haga más trabajo, el diseño ascendente produce programas más pequeños y más ágiles. Un programa más corto no tiene que dividirse en tantos componentes, y menos componentes significa programas que son más fáciles de leer o modificar. Menos componentes también significa menos conexiones entre componentes y, por lo tanto, menos posibilidades de errores. Mientras que los diseñadores industriales se esfuerzan por reducir la cantidad de partes móviles en una máquina, los programadores Lisp experimentados usan el diseño ascendente para reducir el tamaño y la complejidad de sus programas.
El diseño ascendente promueve la reutilización del código. Cuando escribes dos o más programas, muchas de las utilidades que escribiste para el primer programa también serán útiles en los siguientes. Una vez que hayas adquirido un amplio sustrato de utilidades, escribir un nuevo programa puede requerir solo una fracción del esfuerzo que requeriría si tuvieras que empezar con Lisp puro.
El diseño de abajo hacia arriba hace que los programas sean más fáciles de leer.
Una instancia de este tipo de abstracción pide al lector que comprenda un operador de propósito general; una instancia de abstracción funcional pide al lector que comprenda una subrutina de propósito especial. [1]
Dado que te obliga a estar siempre atento a los patrones en tu código, trabajar de abajo hacia arriba te ayuda a aclarar tus ideas sobre el diseño de tu programa. Si dos componentes distantes de un programa tienen una forma similar, notarás la similitud y tal vez rediseñes el programa de una manera más sencilla.
El diseño ascendente es posible hasta cierto punto en lenguajes distintos de Lisp. Siempre que se ven funciones de biblioteca, se está produciendo un diseño ascendente. Sin embargo, Lisp ofrece poderes mucho más amplios en este aspecto, y la ampliación del lenguaje desempeña un papel proporcionalmente mayor en el estilo Lisp, tanto que Lisp no es sólo un lenguaje diferente, sino una forma totalmente distinta de programar.
Es cierto que este estilo de desarrollo se adapta mejor a los programas que pueden ser escritos por grupos pequeños. Sin embargo, al mismo tiempo, amplía los límites de lo que puede ser realizado por un grupo pequeño. En The Mythical Man-Month , Frederick Brooks propuso que la productividad de un grupo de programadores no crece linealmente con su tamaño. A medida que el tamaño del grupo aumenta, la productividad de los programadores individuales disminuye. La experiencia de la programación Lisp sugiere una forma más alegre de expresar esta ley: a medida que el tamaño del grupo disminuye, la productividad de los programadores individuales aumenta. Un grupo pequeño gana, relativamente hablando, simplemente porque es más pequeño. Cuando un grupo pequeño también aprovecha las técnicas que Lisp hace posibles, puede ganar rotundamente .
Nuevo: Descarga On Lisp gratis .
[1] "Pero nadie puede leer el programa sin comprender todas sus nuevas utilidades". Para ver por qué tales afirmaciones suelen ser erróneas, consulte la Sección 4.8.