PROGRAMACIÓN DE ABAJO HACIA ARRIBA
OriginalEnero de 1993
(Este ensayo es de la introducción deOn Lisp.)
Es un principio de larga data del estilo de programación que los elementos funcionales de un programa no deben ser demasiado grandes. Si algún componente de un programa crece más allá del punto en el que es fácilmente comprensible, se convierte en una masa de complejidad que oculta errores tan fácilmente como una gran ciudad oculta a los 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 piezas, 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 descendente: 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, así que a su vez tendrá cuatro subrutinas propias", y así sucesivamente. Este proceso continúa hasta que todo el programa tenga el nivel adecuado de granularidad: cada parte lo suficientemente grande como para hacer algo sustancial, pero lo suficientemente pequeña como para ser entendida como una sola unidad.
Los programadores de Lisp experimentados dividen sus programas de manera diferente. Además del diseño descendente, siguen un principio que se podría llamar diseño ascendente: cambiar el lenguaje para adaptarlo al problema. En Lisp, no solo escribes tu programa hacia abajo en el lenguaje, sino que también construyes el lenguaje hacia arriba hacia tu programa. Mientras escribes un programa, puedes pensar "Me gustaría que Lisp tuviera tal o cual operador". Entonces lo escribes. 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, el límite entre el lenguaje y el programa se traza y se vuelve a trazar, hasta que finalmente se establece a lo largo de 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 ajustan bien entre sí, obtienes un código que es claro, pequeño y eficiente.
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 programa único y monolítico, obtendrás 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.
En el código típico, una vez que abstraes las partes que son simplemente tareas administrativas, lo que queda es mucho más corto; cuanto más alto construyas el lenguaje, menos distancia tendrás que recorrer desde arriba hacia él. Esto trae varias ventajas:
Al hacer que el lenguaje haga más trabajo, el diseño ascendente produce programas más pequeños y ágiles. Un programa más corto no tiene que dividirse en tantos componentes, y menos componentes significa programas 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 allí. Al igual que los diseñadores industriales se esfuerzan por reducir el número de piezas móviles en una máquina, los programadores de 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 de código. Cuando escribes dos o más programas, muchos de los servicios públicos que escribiste para el primer programa también serán útiles en los siguientes. Una vez que hayas adquirido un gran sustrato de servicios públicos, escribir un nuevo programa puede requerir solo una fracción del esfuerzo que se necesitaría si tuvieras que partir de Lisp en bruto.
El diseño ascendente hace que los programas sean más fáciles de leer.
Una instancia de este tipo de abstracción le pide al lector que entienda un operador de propósito general; una instancia de abstracción funcional le pide al lector que entienda una subrutina de propósito especial. [1]
Debido a que te hace estar siempre atento a los patrones en tu código, trabajar de abajo hacia arriba ayuda a aclarar tus ideas sobre el diseño de tu programa. Si dos componentes distantes de un programa son similares en forma, se te llevará a notar la similitud y quizás a rediseñar el programa de una manera más sencilla.
El diseño ascendente es posible en cierta medida en otros lenguajes además de Lisp. Cada vez que ves funciones de biblioteca, se está produciendo un diseño ascendente. Sin embargo, Lisp te da poderes mucho más amplios en este departamento, y aumentar el lenguaje juega un papel proporcionalmente más grande en el estilo de Lisp, hasta el punto de que Lisp no es solo un lenguaje diferente, sino una forma completamente diferente 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, extiende los límites de lo que puede hacer 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 aumenta el tamaño del grupo, la productividad de los programadores individuales disminuye. La experiencia de la programación de Lisp sugiere una forma más optimista de formular esta ley: a medida que disminuye el tamaño del grupo, la productividad de los programadores individuales aumenta. Un grupo pequeño gana, en términos relativos, simplemente porque es más pequeño. Cuando un grupo pequeño también aprovecha las técnicas que Lisp hace posibles, puede ganar de forma absoluta.
Nuevo: Descarga On Lisp gratis.
[1] "Pero nadie puede leer el programa sin entender todos tus nuevos servicios públicos". Para ver por qué tales afirmaciones suelen estar equivocadas, consulta la Sección 4.8.