PROGRAMACIÓN DE ABAJO HACIA ARRIBA
OriginalJanuary 1993
(Este ensayo es de la introducción aOn Lisp.)
Es un principio de estilo de programación de larga data 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 la que es fácilmente comprensible, se convierte en una masa de complejidad que oculta errores con la misma facilidad que una gran ciudad oculta fugitivos. Este tipo de 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 deberá dividirse. ¿Cómo se divide un programa? El enfoque tradicional es llamado diseño descendente: se dice "el propósito del programa es hacer estas siete cosas, por lo que lo divido en siete subrutinas principales. La primera subrutina tiene que hacer estas cuatro cosas, por lo 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 Lisp experimentados dividen sus programas de manera diferente. Además del diseño descendente, siguen un principio que podría llamarse diseño ascendente: cambiar el lenguaje para que se adapte al problema. En Lisp, no solo escribe su programa hacia abajo hacia el lenguaje, también construye el lenguaje hacia arriba hacia su programa. Mientras está escribiendo un programa, puede pensar "Ojalá Lisp tuviera tal y tal operador". Así que vas y 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 dibuja y se redibuja, hasta que finalmente se asienta a lo largo de las montañas y los ríos, las fronteras naturales de tu problema. Al final, su programa se verá como si el lenguaje hubiera sido diseñado para él. Y cuando el lenguaje y el programa encajan bien, terminas con 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 tú trabajas de abajo hacia arriba, generalmente terminas con un programa diferente. En lugar de un único 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, tendrás un arco.
En el 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 alto construyes el lenguaje, menos distancia tendrás que recorrer desde la parte superior hacia abajo. Esto trae varias ventajas:
Al hacer que el lenguaje haga más trabajo, el diseño ascendente produce programas que son más pequeños y ágiles. Una 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 allí. Como los diseñadores industriales se esfuerzan por reducir el número de partes móviles en una máquina, los programadores Lisp experimentados utilizan 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 primero programa también serán útiles en los siguientes. Una vez que has adquirido una gran base de utilidades, escribir un nuevo programa puede llevar solo una fracción del esfuerzo que requeriría si tuvieras que empezar con Lisp crudo.
El diseño ascendente hace que los programas sean más fáciles de leer.
Un caso de este tipo de abstracción le pide al lector que comprenda un operador de propósito general; un caso de abstracción funcional le pide al lector que comprenda una subrutina de propósito especial. [1]
Debido a que te obliga siempre a estar 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, serás llevado a notar la similitud y quizás rediseñar el programa de una manera más simple.
El diseño ascendente es posible hasta cierto punto en lenguajes distintos de Lisp. Siempre que veas funciones de biblioteca, está sucediendo un diseño ascendente. Sin embargo, Lisp te da mucho más amplio poderes en este departamento, y aumentar el lenguaje juega un papel proporcionalmente más importante en el estilo Lisp: tanto que Lisp no es solo un lenguaje diferente, sino una forma completamente diferente de programar.
Es cierto que este estilo de desarrollo es más adecuado para 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 El mito del hombre-mes, 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, en términos relativos, simplemente porque es más pequeño. Cuando un grupo pequeño también aprovecha las ventajas de las técnicas que Lisp hace posibles, puede ganar por completo.
Nuevo: Descargue On Lisp de forma gratuita.
[1] "Pero nadie puede leer el programa sin entender todas tus nuevas utilidades". Para ver por qué tales afirmaciones suelen ser erróneas, consulte la Sección 4.8.