46 | Cómo escribir código de buena calidad |
En cierto modo, escribir c
ódigo de buena calidad es parecido a escribir prosa de buena calidad: hay que tener las ideas claras y, adem
ás, expresarlas con correcci
ón. Quien se inicia en la programaci
ón frecuentemente piensa en espa
ñol, o cualquiera que sea su lengua, lo que quiere que haga su programa. Sin embargo, a medida que se adquiere desenvoltura en el uso de Wolfram Language, se har
á un h
ábito pensar directamente en t
érminos de c
ódigo, lo que har
á m
ás f
ácil escribir un programa que describir su intenci
ón.
Mi objetivo como dise
ñador de Wolfram Language ha sido que la expresi
ón de cualquier cosa sea lo m
ás f
ácil posible. Los nombres de las funciones en Wolfram Language son muy semejantes a las palabras en lenguaje natural, y he hecho grandes esfuerzos para escogerlos de manera adecuada.
Funciones tales como
Table o
NestList o
FoldList existen en Wolfram Language porque se refieren a acciones que se efect
úan frecuentemente. Tal y como pasa con el lenguaje natural, suele haber diferentes maneras de expresar lo mismo, pero si se quiere lograr c
ódigo de buena calidad hay que encontrar la que resulte m
ás directa y m
ás sencilla.
As
í, para crear una tabla con los 10 primeros cuadrados hay, en Wolfram Language, una porci
ón obvia de buen c
ódigo que no usa m
ás que la funci
ón
Table.
Código en Wolfram Language, sencillo y de buena calidad, para formar la tabla de los 10 primeros cuadrados:
¿Qué necesidad habría de escribir algo diferente? Frecuentemente, no se piensa en la “tabla completa”, sino en los pasos que habría que seguir para construirla. En las primeras épocas de la era de la computación, las máquinas necesitaban de toda la ayuda que el programador pudiera darles, y la única opción era mediante un código que describiera paso a paso lo que tenían que hacer.
Aqu
í se ve un c
ódigo, mucho menos pulcro, que construye la tabla paso a paso:
Wolfram Language permite expresar las cosas a un nivel más alto, y crear código que atrape la idea lo más directamente que se pueda. Una vez que se conoce el lenguaje, será mucho más eficiente operar en ese nivel y, así, escribir código más fácil de entender, tanto por las computadoras como por los seres humanos.
Para escribir c
ódigo de buena calidad, es muy importante preguntarse con frecuencia,
“¿cu
ál es el escenario completo de lo que este c
ódigo pretende lograr?
” Muchas veces, cuando se comienza a tratar con alg
ún problema, se entiende solo una parte, y se escribe el c
ódigo espec
íficamente para eso. Y luego, poco a poco, se extiende, a
ñadi
éndose m
ás y m
ás porciones. En cambio, al reflexionar en t
érminos del escenario completo, de pronto se da uno cuenta de que existe alguna funci
ón poderosa, como
Fold, a la que se puede recurrir para simplificar y hacer que el c
ódigo sea otra vez pulcro y sencillo.
Escriba un c
ódigo para convertir los d
ígitos de
{centenas, decenas, unidades} en un solo entero:
Ejecute el código:
Ahora, se generaliza lo anterior a una lista de cualquier longitud, usando
Table:
Este nuevo código funciona:
A continuación, se simplifica el código multiplicando la lista completa de potencias de 10 al mismo tiempo:
Se borran las definiciones previas y se intenta un nuevo enfoque de tipo recursivo:
Este nuevo enfoque también funciona:
Pero en este punto se da uno cuenta de que, en efecto,
¡se trata de un simple
Fold!
Y, por supuesto, tambi
én hay una funci
ón nativa que hace lo mismo:
¿Por qu
é conviene que el c
ódigo de buena calidad sea simple? Primero, porque es m
ás probable que est
é correcto. Es mucho m
ás f
ácil que alg
ún error se produzca en un c
ódigo complicado que en uno simple. Adem
ás, un c
ódigo simple es casi siempre m
ás general y, por tanto, tiende a cubrir casos que ni siquiera se hab
ían considerado al principio y, as
í, se evitar
á tener que incorporar m
ás y m
ás c
ódigo. Y, por
último, un c
ódigo simple es mucho m
ás f
ácil de leer y entender. (M
ás simple no es sin
ónimo de m
ás breve y, de hecho, hay c
ódigos muy breves que pueden ser muy complicados de entender).
Una versi
ón excesivamente abreviada de
fromdigits, que ya es un poco dif
ícil de entender:
Aunque, claro, sí que funciona:
Si lo que se quiere hacer es complicado, es muy posible que el c
ódigo sea tambi
én complicado. Sin embargo, un c
ódigo de buena calidad se puede subdividir en funciones y definiciones tan simples y autocontenidas cuanto sea posible. Y, as
í, aun en programas muy largos escritos en Wolfram Language, casi no se encontrar
án definiciones que requieran m
ás all
á de unas cuantas l
íneas.
He aquí una sola definición en la que se combinan varios casos:
Es mucho mejor subdividirla en varias definiciones m
ás simples:
Una cuesti
ón muy importante al escribir c
ódigo de buena calidad es la buena selecci
ón de nombres para las funciones. En las funciones nativas de Wolfram Language he hecho grandes esfuerzos, a lo largo de varias d
écadas, para escoger bien sus nombres, y captar en forma breve la esencia de lo que hacen y de c
ómo se piensa en ellas.
Al estar escribiendo algún programa, sucede que hay que definir una nueva función porque se necesita en un contexto muy específico. Pero siempre vale la pena tratar de darle un nombre que se entienda fuera de ese contexto. Porque muchas veces, si no se le encuentra un buen nombre, es señal de que quizá no sea la función que haya que definir.
Una indicaci
ón de que el nombre de una funci
ón es bueno es que, al toparse con
él en alg
ún c
ódigo, inmediatamente se puede saber lo que hace. Y, ciertamente, una caracter
ística importante de Wolfram Language es que a menudo es m
ás f
ácil seguir y entender directamente un c
ódigo bien escrito, que cualquier descripci
ón textual que se quiera hacer del mismo.
¿C
ómo describir lo siguiente en lenguaje llano?
Al escribir alg
ún programa en Wolfram Language, puede presentarse el dilema entre usar una funci
ón nativa, tal vez algo rara, pero que hace exactamente lo que uno quiere, o bien, llegar a la misma funcionalidad, pero utilizando varias funciones m
ás conocidas. Y, s
í, a veces he tratado en este libro de evitar el uso de funciones raras para minimizar el vocabulario. Pero el c
ódigo de mejor calidad tiende a usar funciones
únicas siempre que se pueda, puesto que el nombre de la funci
ón explica su intenci
ón de mejor manera de lo que se lograr
ía si se usan varias diferentes.
Use un c
ódigo breve para invertir los d
ígitos de un entero:
Pero hay una funci
ón nativa cuyo nombre describe m
ás claramente su intenci
ón:
El c
ódigo de buena calidad debe ser correcto y f
ácil de entender. Pero tambi
én debe ser eficiente al ejecutarse. Y, en Wolfram Language, usualmente el c
ódigo simple es tambi
én mejor en ese aspecto; y la raz
ón es que, al explicar las intenciones con mayor claridad, Wolfram Language puede optimizar con m
ás facilidad los c
ómputos que debe hacer internamente.
En cada nueva versión, Wolfram Language ha ido mejorando su capacidad para encontrar automáticamente la manera de ejecutar un código con mayor rapidez. Aunque, claro, siempre ayuda que los algoritmos que se vayan a usar tengan una buena estructuración.
Timing reporta el tiempo utilizado en un c
ómputo (en segundos), adem
ás del resultado:
Grafique el tiempo usado en el c
ómputo de
fib[n] seg
ún las definiciones dadas anteriormente.
Con las definiciones de arriba para fib, el tiempo utilizado crece muy rápidamente:
Sucede que el algoritmo utilizado realiza una cantidad exponencial de trabajo innecesario, pues recalcula una y otra vez lo que ha sido calculado previamente. Esto puede evitarse si se hace una asignaci
ón para
fib[n] cuando se hace la definici
ón de
fib[n_], de tal modo que se vaya almacenando el resultado de cada c
ómputo intermedio.
Redefina la función fib de modo tal que recuerde cada valor que vaya calculando:
En este caso, hasta el 1000, se ve que cada nuevo valor se obtiene en unos cuantos microsegundos:
FromDigits[list] | | arma un entero a partir de sus dígitos |
IntegerReverse[n] | | invierte el orden de los dígitos de un entero |
Timing[expr] | | efectúa un cálculo, e incluye el tiempo que toma en hacerlo |
46.1Encuentre una forma m
ás simple para
Module[{a, i}, a=0; For[i=1, i≤1000, i++, a=i*(i+1)+a];a].
»
46.2Encuentre una forma m
ás simple para
Module[{a, i}, a=x; For[i=1, i≤10, i++, a=1/(1+a)];a].
»
46.3Encuentre una forma m
ás simple para
Module[{i, j, a}, a={}; For[i=1, i≤10, i++, For[j=1, j≤10, j++, a=Join[a, {i, j}]]];a].
»
46.4Obtenga el gr
áfico con los puntos unidos del tiempo usado para realizar en el c
álculo de
n^n, para n hasta 10 000.
»
46.5Produzca la gr
áfica con los puntos unidos del tiempo usado por
Sort para ordenar
Range[n] en orden aleatorio, para n hasta 200.
»
Es la notaci
ón abreviada para
i=i+1. Es la misma notaci
ón utilizada por C y muchos otros lenguajes de bajo nivel para indicar esta operaci
ón de incremento.
¿Qu
é hace la funci
ón
For?
Se trata del an
álogo directo del comando
for(...) en C.
For[start, test, step, body] ejecuta, primero,
start, luego hace la prueba
test, enseguida ejecuta
step, y luego
body. Hace esto repetidamente hasta
test ya no da
True.
¿Por qué algunas porciones abreviadas de código pueden ser difíciles de entender?
Lo más frecuente es que se han ido eliminando variables, y aun funciones, de manera que al final quedan muy pocos nombres para leerse, que son los que podrían dar alguna indicación de lo que supuestamente intenta hacer el código.
¿Cuál es el mejor IDE (entorno de desarrollo integrado) para escribir código en Wolfram Language?
Para la programaci
ón cotidiana, lo mejor es usar los cuadernos de Wolfram. Es importante usar secciones, texto y ejemplos junto con el c
ódigo. En proyectos grandes de software, con m
últiples desarrolladores,
Wolfram Workbench contiene un IDE basado en Eclipse.
Mide el tiempo de CPU usado dentro de Wolfram Language para el c
ómputo del resultado. No toma en cuenta el tiempo usado para mostrar en pantalla el resultado. Tampoco incluye el tiempo usado en operaciones externas, tal como traer informaci
ón de la nube. Si se quiere el tiempo total absoluto, el que medir
ía un reloj com
ún y corriente, habr
á que usar
AbsoluteTiming.
¿C
ómo se obtienen mediciones de tiempo m
ás precisas en el caso de c
ódigos que corren muy r
ápido?
Use
RepeatedTiming, que corre el mismo c
ódigo muchas veces y promedia los tiempos resultantes. (Esto no funciona cuando el c
ódigo se modifica a s
í mismo, como en la
última definici
ón de
fib que se dio m
ás arriba).
¿Qu
é trucos hay para acelerar el c
ódigo?
Adem
ás de esforzarse por mantener el c
ódigo simple, uno de ellos es no recalcular nada que ya haya sido calculado. Tambi
én puede mencionarse que, cuando se tienen muchos n
úmeros, puede ser una buena idea usar
N para que los n
úmeros est
én en forma aproximada. En algunos algoritmos internos se puede escoger el
PerformanceGoal deseado y lograr un equilibrio entre velocidad y exactitud. Existen, adem
ás, funciones tales como
Compile que tienen como prop
ósito que gran parte del trabajo asociado a la optimizaci
ón se realice por separado, de manera mucho m
ás r
ápida que si se hiciera durante el c
ómputo propiamente.
- Pueden generarse comportamientos muy complicados, aun usando código muy simple; de eso trata mi libro, de 1280 páginas, A New Kind of Science. Ahí se puede encontrar un buen ejemplo de lo anterior: CellularAutomaton[30, {{1}, 0}].
- La función fib efectúa el cálculo de Fibonacci[n]. La definición original siempre lleva un proceso recurrente a través de un árbol de valores descendentes O(ϕn), donde ϕ≈1.618 es la razón áurea (GoldenRatio).
- La capacidad de recordar valores que hayan sido calculados previamente por alguna función se llama a veces memoización, a veces programación dinámica y, a veces, simplemente cacheado.
- La función IntegerReverse se introdujo en la Version 10.3.
- Cuando se trata de programas grandes, Wolfram Language tiene un marco estructural para poder aislar funciones dentro de contextos y paquetes.
- If[#1>2, 2#0[#1-#0[#1-2]], 1]&/@Range[50] es un ejemplo de código abreviado que presenta un reto serio para entender lo que quiere decir.