|
Adolfo Di Mare |
Presento un conjunto de convenciones para escribir programas en lenguaje Pascal, orientadas principalmente a la versión 5.0 del Turbo Pascal. El uso de estas convenciones ayuda a lograr una mejor legibilidad de los programas, y aumenta considerablemente la calidad en su documentación interna. | I explain a set of conventions to write Pascal programs, oriented mainly to Turbo Pascal v5.0. The use of this conventions helps to achieve a better readability of programs, and their internal documentation is substantially improved. |
Es frecuente en nuestro medio oir a estudiantes y profesionales de la programación confesar, no sin cierta preocupación, que no están en capacidad de documentar un programa, o de siquiera escribirlo bien. Aunque en algunos casos puede que el interlocutor realmente sea ignorante, en realidad lo que le falta es decorar el programa sistemáticamente, para que el resultado sea un buen programa. Una manera de lograrlo es siguiendo las convenciones de programación aquí descritas. Aunque estas convenciones son para Pascal, un programador ducho puede adaptarlas fácilmente a otros lenguajes.
Estas convenciones de programación son reglas para hacer la documentación interna del programa. El seguirlas no garantiza que todo programa quede correctamente documentado, pero el programador cuidadoso logrará de ellas gran provecho. Después de todo representan la experiencia de muchos programadores, que hemos contribuido con diversos detalles, que juntos forman un todo bastante armonioso y útil.
Estas convenciones no son las más agradables a primera vista. Sin embargo, después de un tiempo de usarlas, nuestro cerebro se acostumbra a ellas, y las acepta. Al tiempo, programas que no siguen las convenciones se ven mal.
Adoptar estas convenciones es difícil, pues requiere mucho trabajo, con la desventaja de tener que sincronizar nuestro gusto con el de otros. Pero después de un corto tiempo se logra, y paradójicamente las convenciones se tornan más agradables de lo que uno quisiera aceptar. Además, si todos las usamos, todos estaremos más contentos compartiendo nuestros programas.
Niklaus Wirth diseñó primero Pascal y luego su
sucesor, Modula-2
[Wir-82]. Lo más importante de
Modula-2 es que incorpora el concepto de módulo, y
elimina la necesidad de usar el BEGIN
para denotar un
bloque. En estas convenciones se ha dado gran importancia a que un
programa Pascal se parezca mucho a su equivalente en
Modula-2, con lo que se logra servir a programadores en ambos
lenguajes (y posiblemente también a quienes prefieran
Oberon
[Wir-88], el sucesor de Modula-2).
Por ejemplo, en estas convenciones las palabras claves deben
escribirse en mayúscula, que es la única forma
aceptable para Modula-2.
Este documento toma en cuenta todas las propuestas para convenciones de programación presentadas por mis estudiantes y también otros criterios encontrados en diferentes libros y artículos. Además, para cada convención doy la justificación que ayudará al programador a aceptarla. La práctica demostrará las debilidades y aciertos de estos criterios para escribir programas, y con el tiempo tendremos un estándar realmente sólido que podremos usar sin temores.
Un sistema es un conjunto de programas integrados en un objetivo común. En general la documentación del sistema depende mucho de la aplicación implementada. Un posible índice para la documentación de un sistema es el siguiente:
Introducción Arquitectura del sistema Objetivos del sistema Detalles de implementación Requerimientos Glosario e índices Guía de uso del sistema
La definición formal de cada uno de estos puntos es extensa, y puede encontrarse en la mayoría de los libros de Ingeniería de Sistemas. Buenos ejemplos de sistemas bien documentados son los manuales de compiladores, como el Turbo Pascal 5.5 de Borland [BI-88].
En este artículo me concentro en la documentación de programas, y no de sistemas, por lo que ahora trato en detalle cómo hacerlo. En la ECCI se han dado varios esfuerzos para mejorar este importante aspecto de la programación, y yo he usado por base los de José Ronald Argüello y Ricardo Villalón.
La documentación de programas tiene dos objetivos claramente definidos: primero lograr que un usuario del programa pueda usarlo, y segundo lograr que entender el funcionamiento del programa sea fácil, de forma que el programa pueda ser cambiado o mejorado con un mínimo de esfuerzo.
A este fin, todos los buenos consejos de nuestros profesores de Redacción y Ortografía de secundaria son válidos: la exposición de ideas en la documentación debe ser clara, concisa, sencilla, válida, exacta. Además, debe usarse el lenguaje adecuadamente, evitando ambigüedades.
Escribir la documentación de un programa es un gran esfuerzo intelectual. Escribir no es fácil, y en general las personas lo hacen mal. Por eso lo usual es producir documentaciones pobres, mediocres o pésimas. Debemos cambiar nuestra actitud hacia la documentación, que en realidad es necesaria para que el producto de nuestro trabajo esté terminado: un programa sin documentación es un auto sin manubrio; un programa sin documentación no es un programa, es una adefesio.
Es en general cierto que los programadores derivan placer del programar, no del documentar. Además, parece que lo contrario es también cierto: a los programadores les duele escribir la documentación, hasta tal punto que la dejan para el final. Lo cierto es que la documentación es parte de la programación, independiente del estado anímico del programador. Cada programador debe aprender a escribir la especificación de su programa (o sea, la documentación), antes de escribir el programa.
Esta actitud negativa hacia el documentar se deriva de un estilo corrupto de programación, promovido por tratar de escribir el programa a la mayor celeridad. Se cree que lo único importante es que el programa funcione, y se relega a un segundo plano todo lo demás. Si al no documentar se logra en algunos casos terminar más rápidamente el programa, en realidad se hace mucho más difícil su mantenimiento posterior, pues la mayoría de los programas deben ser modificados muchas veces, antes de ser desechados.
Si un programa se construye siguiendo las sanas prácticas de diseño modular y programación estructurada, es entonces necesario definir primero qué debe hacer cada parte del programa, mediante una especificación, para luego escribir cómo se logra. Esto es prueba de que el engorro que representa al programador el escribir documentación se deriva de su ignorancia de las técnicas de programación usadas por los buenos profesionales, y no de que sea desagradable escribirla: antes de construir un programa es necesario definir cuál debe ser su comportamiento. Es triste que muchos programadores no diseñen sus programas: el resultado es un sistema "feo".
Además, conforme se codifica cada algoritmo, el programador puede hacer pequeñas anotaciones en el programa, que aumentan su legibilidad. No es necesario hacer más. Quiere esto decir que la documentación (interna) del programa se escribe de manera natural con el programa, en dos pasos: primero al definir las especificaciones de cada procedimiento o tipo abstracto de datos, y luego al concretar cada uno de los algoritmos. Hacer más que esto es innecesario, e irrelevante.
Desde una perspectiva de alto nivel, al no documentar inicialmente un programa adecuadamente, lo que se hace es traspasar el costo de la documentación al futuro, cuando deben hacerse modificaciones. Pero si no existe documentación, para cada modificación debe estudiarse profundamente el funcionamiento del programa, de hecho redescubriendo todo lo no documentado, con la desventaja adicional de generalmente quien modifica el programa no es el mismo que lo escribió en primera instancia. Y cada nueva modificación implica de nuevo un esfuerzo intelectual, que pudo evitarse al documentar bien el programa desde el principio.
O sea, siempre es necesario documentar, y lo mejor (y más barato) es hacerlo cuando se está escribiendo el programa. No después, cuando ya se han olvidado los detalles relevantes, o las decisiones de impacto que se tomaron al concretar la implementación.
Si un programa tiene más de 250 líneas (que es lo usual), el programador deberá trabajar en él varias horas, sino varios días. El documentar su programa mientras lo escribe le evita recordar todos los detalles de su implementación, y le obliga a definir y aclarar sus ideas. Si una persona no puede hablar (escribir) concretamente sobre algo, menos podrá programarlo. La prueba de fuego es lograr que otro entienda lo que uno ha producido: ¿se atreve usted ha tomarla en cada uno de sus programas?
Además, como todo buen escritor, un programador debe revisar el código que ha escrito una y otra vez. En cada nueva revisión encontrará nuevas maneras de documentar su programa, y podrá corregir las imprecisiones en que haya incurrido. La programación es una proceso iterativo, y es necio tratar de lograr un resultado correcto al primer intento.
Pasemos ahora a examinar en detalle lo que debemos incluir en nuestra documentación. La documentación del programa en general se divide en dos partes: interna y externa. La interna es la que acompaña al código fuente, y todo lo demás es la externa. Aunque es posible prescindir de la documentación externa por completo, en general la costumbre es mantener la documentación interna simple y concisa. Cuando es necesario hacer grandes aclaraciones en prosa o por medio de dibujos, es mejor incluirlas en la documentación externa.
Un teórico de la programación podría argumentar que la diferencia entre documentación interna y externa no existe en realidad, pues en la práctica lo que debe hacerse es escribir una jerarquía de documentos que describen un producto de logical. En la práctica cada programa o sistema tiene requerimientos específicos, y no es fácil usar siempre el mismo patrón de documentación.
Por eso, el sagaz programador deberá incluir tantos niveles descriptivos, o de abstracción, como sean necesarios para lograr una buena documentación. No debe poner más de lo suficiente, ni menos de lo necesario. Sobre todo, debe producir un conjunto de documentos que le satisfagan.
Como corolario de lo anterior se desprende que un buen método para evaluar la documentación producida, es contestar la pregunta: ¿Si me entregaran a mí esta documentación para que yo modificara el programa, sería suficiente? Obviamente un mejor método para evaluar la documentación es lograr que un segundo la analice, a la luz de esta misma pregunta. Todo aquello que él no pueda resolver fácilmente a partir de lo documentado merece ser revisado y mejorado, hasta lograr un documento que, valga la redundancia, si documente.
Es bueno escribir los programas para el mundo, no para "yo". Tal
vez el sentirnos expuestos a la crítica de los demás
nos ayude a producir programas de más calidad.
Es redundante decirlo, pero esta documentación debe estar escrita en papel, aunque puede haber sido producida por medio del computador. En ella se describen los siguientes puntos:
Los anteriores diez puntos son, no por casualidad, una copia un poco detallada de los mencionados para documentar sistemas. Esta analogía no es casual, pues en realidad un sistema puede verse como un "programote".
Documentación de programas | |
---|---|
|
|
Ahora ya podemos hablar de cómo debe verse el código del programa. Pero antes de definir en detalle cómo debe escribirse cada estructura del lenguaje, es necesario aclarar tres puntos. Primero, al examinar un programa cualquiera, el lector debe experimentar una sensación agradable, una invitación a estudiar en detalle el código escrito.
O sea, que el programa debe verse bonito. Organizado. Ordenado. Fácil de leer. Claro.
Dicho de otra manera: Si se ve feo, es que está mal hecho.
Segundo, el objetivo de la documentación interna es facilitar el posterior entendimiento del programa. No basta que el código se vea bonito, sino que además debe estar bien escrito: "Aunque la mona se vista de seda, mona se queda".
Tercero, si hay trabajo que el compilador puede hacer, entonces debemos dejarle hacerlo. Esta es la principal razón para justificar la verificación de tipos que Pascal y otros lenguajes hacen. Un programador que siente su expresividad totalmente coartada por esto, debe reflexionar seriamente, pues el costo de correr el compilador es mucho más bajo que el de mantenerle encontrando errores que la máquina pudo detectar.
Todos sabemos que los identificadores usados en el programa deben ser expresivos, claros, definitivamente relevantes a la solución del problema de marras, y por si fuera poco, legibles. Muy legibles.
Los nombres de las constantes, tipos y variables deben ser nombres significativos o nemónicos. Esto con el fin de que el programador los recuerde fácilmente cuando programa, y para que quien lea el programa comprenda la función de determinadas variables, tipos o constantes.
Para lograr estas cualidades el programador debe escoger concienzudamente cada identificador. Cuando no pueda hacerlo, deberá aceptar que todavía no entiende la solución de su problema, pues ni siquiera es capaz de hablar correctamente acerca de él. Usar identificadores inadecuados es prueba contundente de que la implementación no va por buen camino, y en general predice el fracaso del proyecto.
Una costumbre que hemos heredado de los tiempos del Fortran es
usar i
, j
y k
como
índices siempre. Si aceptamos que los nombres de
identificadores deben ser significativos, entonces también
lo deben ser los nombres de índices: en lugar de escribir
semana[i]
, lo correcto es escribir
semana[dia]
.
Una vez escogido un identificador adecuado, puede incorporarse
dentro del nombre elegido información adicional sobre el
uso del identificador. Por ejemplo, si el identificador es un tipo
nuevo definido por el programador, entonces al incluirle el
prefijo T
el lector sabrá de ese hecho. La
siguiente tabla lista algunas de estas convenciones:
Ejemplo Identificador definido, y su uso cont
Variable, pues comienza con minúscula variable_larga
" _
" separa las palabras del identificadorvariableLarga
Otro estilo, que usa un caracter menos vListAlfSymTel
hay que abreviar si es necesario hacerlo Blanco
Constante, pues comienza con una mayúscula TPersona
Tipo usado para definir variables de personas PPersona
Puntero a una variable de tipo PPersona Corrector(a,b)
Un procedimiento comienza en mayúscula
Estas convenciones son consistentes. Se usa un sufijo para determinar la calidad del identificador:
Prefijo Significado T
tipos P
tipo puntero
Hay que destacar que cuando se usan varias palabras para formar un
identificador, éstas deben ser muy legibles:
esto_si_seVeBien
,
peroestocasiquenoseentiende
. ¿O si?
Los únicos identificadores que comienzan con una minúscula son las variables; todos los demás identificadores que no pueden cambiar comienzan con una mayúscula: constantes, tipos, procedimientos, funciones, etc.
Un caso particular de esta regla es que los nombres de los
procedimientos estándar Pascal deben escribirse con una
mayúscula al principio, que es el estilo usado por la
mayoría de los programadores profesionales:
ReadLn()
, WriteLn()
,
ClrScr()
, Halt()
. Recordemos que estos
procedimientos no forman parte del lenguaje, hasta tal punto que
pueden ser redefinidos por el programador; si no fuera esto
así escribiríamos sus nombres en mayúsculas.
Algunos programadores se resisten a usar letras minúsculas en sus programas, y escriben todo en mayúscula (esta práctica era necesaria hace 20 años, cuando no había minúsculas). Con esto lo que logran es hacer menos legibles sus identificadores, pues estamos acostumbrados a leer en minúscula, usando las mayúsculas sólo en casos especiales.
El programador no debe restringir el tamaño de un
identificador, sino más bien debe escogerlo para que sea
significativo. Sin embargo, ante la posibilidad de escoger entre
uno largo y otro corto, deberá escogerse el corto si tiene
la misma expresividad del largo. Si queremos denotar la altura de
una persona, es mejor usar el identificador alt que
altura_persona
, siempre y cuando alt sea suficientemente
claro en el contexto de programación. En general es mejor
usar el caracter "_
"
paraSepararPalabrasEnUn
identificador, pues
resulta_en_identificadores_más_legibles
.
Tal vez sea importante mencionar que la mayoría de los ligadores de eslabonamiento modernos manejan identificadores de hasta 32 caracteres, por lo que ése parece un número que puede usarse como longitud máxima de un identificador. Identificadores muy largos son signo de mala programación.
Algunas variables tienen un significado predefinido en la
literatura, por lo que sus nombres deben usarse en ese mismo
sentido. Por ejemplo, en casi todos los libros de
matemática la letra i
se usa casi siempre como
índice, y lo general es que x
sea el
parámetro de una función de variable real. Es
entonces contraproducente usar una en lugar de la otra:
Lo feo |
Lo bonito | |
sum := 0; I := 6.23194; FOR X := 1 TO y DO BEGIN n := n / F(X*I); sum := sum + a[X] - n; END; |
x := 6.23194; suma := 0; FOR i := 1 TO n DO BEGIN y := y / f(i*x); suma := suma + A[i] - y; END; |
Como puede notarse en este ejemplo, a veces es conveniente no
seguir las convenciones: f(x)
se ve mejor que
F(x)
, pues estamos acostumbrados a leer en los textos
la primera forma. Lo mismo podemos decir de la referencia al
Arreglo A
, pues al escribir su nombre con
mayúscula se nota mejor el sentido del programa. Un
fanático de las convenciones diría que debemos usar
una "a
" minúscula, pero ¿verdad que se ve
mejor con A
"grandota"? El fanatismo documentalista
puede ser contraproducente.
También es una muy buena práctica el usar constantes en lugar de valores literales, pues entonces el programa tiene más sentido para el lector, y si es necesario cambiar el valor del literal puede hacerse en un sólo lugar, en vez de tener que recorrer todo el programa y sus módulos buscando los literales que deben ser cambiados.
Es muy importante usar constantes. Su uso evita que el texto del programa esté plagado de números y tiras que muchas veces no tienen significado. Veamos un ejemplo:
Lo feo |
Lo bonito | |
pago := total * 1.1 * 1.1; |
pago := total * Imp_Ventas * Propina; |
Al usar constantes el texto del programa es más claro.
Además, es posible cambiar el valor de la constante en todo
el programa con sólo modificar una línea del
programa. En el ejemplo anterior, si sucediera que la propina pasa
a ser el 15%, el programador debería examinar con sumo
cuidado cada línea del programa en que el literal
1.1
aparece, para decidir si debe o no cambiarlo. Si
hubiera usado constantes desde el principio, todo ese trabajo se
habría evitado.
Pero además las constantes sirven para relacionar unas estructuras de datos con otras, usualmente al usar arreglos. Veamos:
Lo feo |
Lo bonito | |
VAR a : ARRAY [1..23] OF CHAR b : ARRAY [1..45] OF CHAR |
CONST LgNom = 23; { Largo del nombre } VAR a : ARRAY [1..LgNom] OF CHAR; b : ARRAY [1..2*LgNom-1] OF CHAR; |
Desgraciadamente el usar expresiones en declaraciones no es válido en las versiones ANSI de Pascal, pero sí lo es en Modula-2, C y Turbo Pascal. El uso de constantes en este contexto aclara mucho la relación que existe entre las variables y estructuras de datos de un programa.
Usar constantes es tan necesario para producir buenos programas, que podemos insultar a los malos lenguajes definiéndolos así: "Un lenguaje es malo si no permite usar constantes. Es pésimo si no permite usar procedimientos que tomen argumentos."
Todo lenguaje cuenta con varias palabras clave, que denotan las
diferentes estructuras sintácticas permitidas:
DO
, BEGIN
, WHILE
, etc.
Aunque no es claro cuál es la forma más "mejor" para
escribirlas, nosotros sólo usaremos mayúsculas.
Existen tres convenciones que dividen a la humanidad en este
respecto: los que abogan por usar sólo minúsculas,
los de las mayúsculas, y los indecisos que las escriben con
la primera letra mayúscula, y el resto en minúscula.
El programador experto notará que en el lenguaje Modula-2
las palabras clave se escriben en mayúscula. A diferencia
del Pascal, los compiladores para Modula-2 si son sensitivos a las
mayúsculas y minúsculas, por lo que es buena idea
copiar las convenciones usadas en este otro lenguaje, que es un
super-Pascal. Entonces todos los identificadores que representan
tipos predefinidos deben escribirse también usando
mayúsculas: INTEGER
, REAL
,
BOOLEAN
, CHAR
, etc. De esta manera
nuestros programas Pascal se parecerán mucho a programas
Modula-2, y será más fácil traducirlos a ese
nuevo lenguaje.
La mejor razón para usar mayúsculas es que resaltan cada palabra clave. De hecho, los libros de texto sofisticados usan letra negrilla para destacar las palabras clave. Pero como al listar en la pantalla un programa con un editor de texto no es posible (en la mayoría de los computadores) obtener este gracioso efecto, debemos conformarnos con usar mayúsculas. Esta no es la regla usada en Ada o C, pues en esos lenguajes se usan minúsculas para las palabras reservadas.
La desventaja de usar mayúsculas es que debemos apretar la
tecla de mayúsculas muchas veces, pues las palabras
reservadas son las más en un programa. Esto tiene remedio
si usamos un editor con capacidad para definir plantillas
(macros). Para cada construcción sintáctica del
lenguaje (IF
, CASE
, FOR
,
etc.) podemos definir una plantilla que invocamos con una corta
secuencia de teclas, y como producto obtenemos una plantilla
completa de la instrucción respectiva. Por ejemplo, en una
IBM/pc usando el programa SMACS (o SuperKey, o ProKey), al pulsar
la secuencia Shift-Alt-I
se puede obtener:
IF _ THEN BEGIN END ELSE BEGIN END;
La enorme ventaja de usar plantillas no es sólo el salvar unos cuantos teclazos, sino también obtener siempre construcciones sintácticamente válidas. Su gran desventaja es que es necesario aprender a usar los macros del editor. Pero es un precio pequeño comparado con el beneficio obtenido.
Es difícil indicar exactamente cuándo incluir comentarios en el programa. Sin embargo, todos los comentarios deben cumplir con lo siguiente:
c1) Deben ser completos c4) Deben ser claros c2) Deben ser válidos c5) Deben ser coherentes c3) Deben ser pertinentes c6) etc.
Obviamente, los comentarios deben ser suficientes para permitirle al lector entender lo que el programa hace. A veces es innecesario incluir comentarios, pues el código mismo es suficientemente explicativo, pero en general esto no es cierto. El programador siempre ve unos cuantos detalles que no son obvios, pues necesitan de un poco de razonamiento para ser deducidos del código del programa.
El programador bisoño siempre se pregunta cuál es la ocasión indicada para incluir un comentario. La obvia respuesta es, invariablemente, YA.
Si esperamos a después para incluirlos, nos encontraremos con que hemos olvidado lo que íbamos a escribir. O sea, que debemos escribir el comentario precisamente en el momento en que sospechamos que debemos hacerlo. Dejarlo para después es sinónimo de no documentar nuestro programa. No incluir comentarios en un programa es El Pecado Capital de la Programación.
Cuando nos percatamos de que un comentario sobra, podemos borrarlo. Es mucho más facil borrar lo mal escrito, que escribir lo no escrito.
En el 1250 AC Sócrates dijo: "Un programa sin comentarios no es un programa, sino una secuencia de símbolos inconexos que un compilador traduce a lenguaje de objeto. Un programa sin documentación no es un programa, es un problema. Un programa sin documentación no es un programa, es un buen método para perder y dinero, y tiempo, y hígado, y jugos gástricos, y glucogelamina, y eso, y más. Un programa sin documentación no es un programa, es el paraíso del loco, la doctrina del incompetente, y el relamer del imbécil"[1].
"Un programa sin documentación no es un programa, es un insulto al programador. Un programa sin documentación no es un programa, es una nada, un sinfín de noes, una blasfemia, una declaración de guerra... Un programa sin documentación no es un programa, es[2]...
En cuanto a la forma de los comentarios, es conveniente que
estén alineados unos con otros. Si están dentro de
un bloque, deben estar adecuadamente indentados, y siempre que sea
posible debe dejarse una espacio separando a las llaves del
comentario. Deben usarse (*
y *)
sólo en contadas ocasiones, pues las llaves ocupan menos
espacio. Las llaves que enmarcan a un comentario deben estar
alineadas. Por ejemplo:
VAR a,b : INTEGER; { factores de conversión } d_a : REAL; { diferencial en a }
A veces el programador sobredocumenta sus programas, al decir cosas que ya están dichas:
INC(a); { incrementa a }
En este caso el comentario sobra, y no ayuda a la legibilidad del programa. Debe ser eliminado.
En general es una buena práctica escribir una
instrucción por renglón. De esta manera, puede
usarse el margen derecho remanente para incluir comentarios.
Sólo en casos excepcionales deben incluirse dos o tres
instrucciones en un sólo renglón, si el hacerlo
incrementa mucho la legibilidad del programa. Toda
instrucción debe terminarse con punto y coma
(;
).
Además, la mayoría de los compiladores y depuradores simbólicos se refieren al texto por el número de línea, y no por el de instrucción. Si cada instrucción está en una línea, los mensajes de error serán más claros, y será más sencillo depurar el programa. ¿Para qué ahorrar espacios?
El deseo de atiborrar todas las instrucciones en una sóla línea nace a veces de la necesidad de ver todo el código de un procedimiento en la pantalla. Como esto sucede bastante, es mejor que el programador saque un listado del procedimiento en cuestión. Definitivamente las cosas en papel se ven mejor que en la pantalla, aún cuando la pantalla tenga 100 líneas de capacidad.
En muy pocos casos es más claro poner varias asignaciones
en el mismo renglón, principalmente antes de un ciclo
WHILE
o REPEAT
, o cuando deben
inicializarse vectores o matrices:
{ Define los valores iniciales de a y b } a[1,1] := 1.0; a[1,2] := 4.0; a[1,3] := 23.0; a[2,1] := 0.0; a[2,2] := 6.0; a[2,3] := 2.0; b[1,1] := 1.0; b[1,2] := 0.0; b[1,3] := 12.0; b[2,1] := 0.0; b[2,2] := 2.0; b[2,3] := 4.0; { Ciclo de conversión } i := 0; j := Limite; WHILE (i<=Limite) AND NOT Eof(Mat_F) DO BEGIN i := i+1; j := j-1; ReadLn(Mat_F, d[j,i]); b[j,i] := i*j * d[i,j]; c[i+j,i-j] := (j-i) * d[j,i]; END;
Muchos programadores se ahorran el punto y coma al final de una instrucción, cuando aparece al final de un bloque. Aunque esto reduce mínimamente la cantidad de teclazos necesarios para escribir el programa, cuando se inserta una instrucción debe volverse al renglón anterior a poner el punto y coma. Al hacer pequeños cambios esto no es problema, pero cuando se deben mover grandes bloques, o hay que insertar muchas instrucciones, entonces pueden producirse errores incómodos de encontrar. Es mejor usar el punto y coma como delimitador, y no como separador. Además, con el punto y coma al final, cada instrucción parece más completa.
Por ningún motivo debe escribirse después de la columna 80. Recordemos que los programas son estudiados, depurados y mejorados en pantallas con 80 columnas, y las instrucciones que tienen más caracteres no pueden verse completas. Lo mismo sucede con las impresoras, que imprimen 80 columnas cuando se usan las fuentes no comprimidas.
El sagaz programador agrupará las instrucciones relacionadas en bloques separados por blancos. Los comentarios generales deben aparacer, debidamente indentados, al principio del bloque:
{ Proceso de todos los rangos identificados } FOR i := 2 TO n DO BEGIN { Primero se inicializan las estructuras } Init_rangos(a, b, n*(i-1) ); Init_local (c, a, b[i-1]); { Ahoras procese cada parte } Ampliar_rango(a,b); Incluir_rango(c,b[i]); { sólo el acumulado } END; Convertidor(a, b, Rango_limite, matriz_cambio, c[Rango_limite]);
En muchas ocasiones es necesario usar varias líneas para una sóla instrucción. Por ejemplo, si un procedimiento tiene muchos argumentos, o si se desea destacar que algunos están lógicamente agrupados, pueden necesitarse varias líneas para la instrucción. En estos casos lo adecuado es indentar las líneas de continuación.
Un pragma es una instrucción al compilador, para seleccionar alguna opción. Los pragmas deben incluir un comentario que explique la opción que representa:
{$B- Fuerza evaluación booleana de corto circuito } {$R- No verifica rangos de índices en tiempo de ejecución } {$S+ Verifica que no se produzca un sobrerebase de la pila }
La declaración introduce una variable por primera vez al lector. Lo razonable es que sea en ese momento que se le informe también de su utilidad en el programa. La declaración de una variable no está completa sin un comentario que justifique su existencia.
El programador sagaz también tratará de agrupar las definiciones de variables relacionadas, resaltando el hecho de que deben usarse en conjunto. Este mismo principio debe aplicarse al definir procedimientos, tipos y constantes. ¿No es ésto lo que en realidad hacemos al definir un tipo abstracto de datos?
Nuestro cerebro aprecia la uniformidad, por lo que es bueno alinear en las declaraciones los separadores y signos de puntuación:
Lo feo |
Lo bonito | |
CONST PI = 3.14159; Epsilon = 1.0 e-10; Delta = 12 e -32; LgNom = 23; |
CONST PI = 3.14159; Epsilon = 1.0 e-10; Delta = 12.0 e-32; LgNom = 23; |
Es conveniente declarar cada variable en un renglón aparte, para facilitar el incluirle comentarios. Los dos puntos deben estar alineados:
Lo feo |
Lo bonito | |
VAR alfa: integer; b: real; |
VAR alfa : INTEGER; b : REAL; |
La mayoría de las instrucciones de un programa la constituyen expresiones. Es realmente difícil definir reglas para escribir expresiones, pues las mismas pueden ser arbitrariamente complejas. Lo correcto entonces es usar algunas reglas generales, por no demás ambiguas, para dar formato a las expresiones. Lo importante es que el programador quede satisfecho con la forma en que ha escrito su expresión, y que use paréntesis cuando sea necesario. Las reglas son las siguientes:
- e1) Deben usarse paréntesis en caso de duda
- e2) en expresiones "grandes" deben usarse blancos
- e3) en expresiones "pequeñas" no deben usarse blancos
- e4) no deben usarse blancos alrededor de paréntesis
- e5) en general, deben usarse blancos alrededor de
< > =
- e6) no simpre puede caber todo en un sólo renglón
- e7) si todavía se ve feo, arréglelo de nuevo
Unos ejemplos aclararán estos puntos:
a := (b AND c) OR ((i+j+k > 12) AND (a)) ((b<c) OR (c<d)); h := b < (c+3); c := interes + Inversion(a*b/3, Balance(Mes(hoy), 88));
En general, cuando una variable consta de una sola letra, la expresión se ve mejor si no se usan blancos alrededor de los operadores aritméticos y de comparación. Cuando el identificador tiene muchas letras, el ponerle blancos alrededor lo adorna.
Cuando una expresión ocupa varios renglones, es válido (aunque incómodo) indentar de forma que la continuación esté alineada con el punto de corte sintáctico del nivel anterior. Por ejemplo:
pasada := (Abs(randx[randx[11]]) MOD 128 + 1 + randx[1] / 65536.0) / 128.0; w := y + ((((y * p4 + p3) * y + p2) * y + p1) * y + p0) / ((((y * q4 + q3) * y + q2) * y + q1) * y + q0)
Este tipo de expresiones son comunes en programas científicos. Lo importante en estos casos es que se sepa a qué nivel de anidamiento de paréntesis se corta el renglón. En general es mejor no usar expresiones tan complejas, salvo en los casos que sea realmente necesario.
Es también muy saludable dejar un espacio alrededor de los
símbolos ":=
", ":
" y
"=
", pues se logra una mayor legibilidad. Es
increíble que algunos deseen ahorrar "bytes" no incluyendo
suficientes espacios blancos para separar las partes de una
instrucción.
Al indentar su código, el programador usa espacios para alinear las sentencias del programa de manera que la estructura del programa es fácilmente identificable.
La indentación definitivamente es uno de los factores más importantes para lograr mejores programas. Se han realizado muchos estudios "científicos" al respecto, pero la experiencia del lector es la prueba más convincente de la necesidad de indentar adecuadamente. Si usted no lo cree, trate de entender los programas Basic escritos en muchas revistas de aficionados a la computación.
Al indentar un programa adecuadamente se logra destacar su estructura de control. De hecho, es más fácil entender un programa indentado adecuadamente, que los correspondientes diagramas de flujo. Dado lo difícil que es dibujar un diagrama de flujo aún con herramientas automáticas, y lo poco claros que resultan (pues ocultan el anidamiento de las estructuras de control del programa), es mejor nunca usarlos. La indentación les sustituye completamente en casi todos los casos. Por todo esto, una máxima de todo programador debería ser:
No indentar es Sacrilegio en programación |
Es difícil resaltar la importancia real de la indentación de programas, pero tal vez un ejemplo exagerado logre el impacto necesario:
Lo feo |
Lo bonito | |
FOR i := 1 TO n DO BEGIN Read(a[i]); IF b<c THEN a[i] := 0 ELSE a[i+1] := i-1; END |
FOR i := 1 TO n DO BEGIN Read(a[i]); IF b<c THEN BEGIN a[i] := 0; END ELSE BEGIN a[i+1] := i-1; END; END; |
Debe indentarse dos (2) espacios cada vez que se utilice una
construcción anidada. Siempre debe indentarse una
construcción anidada, y siempre debe indentarse en dos
espacios. La consistencia en la aplicación de esta regla
permite a nuestro cerebro predecir la forma del programa, y a la
postre hace la lectura más sencilla. También debe
indentarse el bloque BEGIN-END
que forma el cuerpo de
un procedimiento o programa.
Tal vez tres sería un factor de indentación mejor que dos. Tal vez lo sería cuatro, o cinco. La verdad es que la mayoría de los programadores profesionales usan sólo dos espacios al indentar. Esto es suficiente, y de paso se lograr ahorrar espacio a la derecha de las instrucciones, que es donde se escriben muchos de los comentarios. Pruebe el lector con diferentes factores de indentación, pero recuerde que todos los demás usaremos consistentemente el mismo número mágico: dos.
Muchas de las construcciones anidadas del Pascal terminan con la
palabra clave END
. Esta deberá quedar a la
misma altura que la palabra inicial del bloque, salvo en los casos
en que explícitamente se diga lo contrario. Por ejemplo,
cuando se define un registro, la palabra clave END
debe estar alineada con el identificador del registro:
CONST Lg_Nombre = 35; { largo del nombre de una persona física } Lg_Matriz = 22; { tamaño de una matriz de transacción } Lg_Tira = 255; TYPE TColor = ( { colores del arcoiris válidos } Azul, Verde, { los colores NO se ordenan por } Amarillo, { su valor espectral } Rosa, Lila, Magenta ); { TColor } { Los nombres están alineados... } TNombre = ARRAY [1..Lg_Nombre] OF Char; PPersona = ^TPersona; TPersona = RECORD { sólo personas físicas } nombre : TNombre; { nombre propio de la persona } edad : INTEGER; { en años cumplidos } sexo : TSexo; cualidades : RECORD fumador, lector, perezoso : BOOLEAN; END; estatura : REAL; { en metros con 3 decimales } ojos : TColor; { color de los ojos } END; { TPersona }
La palabra clave END
sirve para alinear el programa,
y retornar al orden de indentación anterior a la
construcción anidada. Además, cuando se sale del
anidamiento y es relevante hacerlo, debe incluirse el
identificador de la construcción respectiva (como en el
caso del fin del registro TPersona
).
Del ejemplo podemos apreciar que END
no es la
única palabra que termina un bloque de indentación:
el paréntesis cerrado u otras palabras clave también
pueden cumplir ese propósito. En general, cuando una
palabra clave termina un bloque de indentación, debe estar
alineada con el comienzo del bloque, y todo el bloque delimitado
debe estar indentado dos espacios.
Hay que observar que un programador poco exigente se
conformaría con incluir menos comentarios en el ejemplo
anterior, aduciendo, por ejemplo, que el significado del vocablo
"TColor
" es obvio. Sin embargo, al escribirlo le
evitamos al lector tener que pensar, y por ende le ayudamos a
entender más fácilmente, que es nuestro objetivo al
documentar.
Recordemos que lo obvio para el programador no lo es para el lector, quien le está leyendo por primera vez su programa. No está el lector en la misma posición del programador, quien ya es un experto en el código escrito.
Cada construcción Pascal estructurada debe indentarse siguiendo el patrón definido en la sección sobre indentación. Sin embargo, mas adelante definimos el formato para indentar cada una de las instrucciones del lenguaje.
Muy relacionado al tema de la indentación es el de la alineación. Las estructuras del programa que están alineadas se ven mejor:
Lo feo |
Lo bonito | |
cambio := 1; d := 2; rh := 3; delta := 23; |
cambio := 1; d := 2; rh := 3; delta := 23; |
Del ejemplo puede observarse como la alineación de las
asignaciones hace más legible el programa. Es factible
alinear no sólo asignaciones, sino también los tipos
en declaraciones (como se hizo para el registro
TPersona
). En general los comentarios se ven mejor
cuando las llaves que los encierran están alineados
(observe el ejemplo anterior en que se define el registro
TPersona
).
IF
Para el IF
debe escribirse el THEN
en el
mismo renglón, y el ELSE
debe estar alineado
al IF
. Además, el BEGIN
debe
seguir al THEN
o al ELSE
cuando sea
necesario (ahorrando de esta manera un renglón, y dos
espacios de indentación):
IF expr_1 OR expr_2 THEN BEGIN Cabeza(a,b); Cola(b); END ELSE BEGIN WriteLn('Imposible procesar'); Halt; END;
Cuando la expresión es muy grande, entonces el
THEN BEGIN
debe ser claramente visible:
IF expr_1 AND NOT (expr_2 OR (i<j)) AND ( (j>k) OR (i<=l) ) OR (NOT found AND expr_3) THEN BEGIN Cabeza(a,b); Cola(a); END;
Aunque lo anterior se ve un poco raro, la razón principal de ello es que las expresiones booleanas no deben ser muy complejas, pues entonces es muy difícil depurarlas.
Una forma alternativa es alinea el THEN BEGIN
con el IF
:
IF expr_1 AND NOT (expr_2 OR (i<j)) AND ( (j>k) OR (i<=l) ) OR (NOT found AND expr_3) THEN BEGIN Cabeza(a,b); Cola(a); END;
En el IF
existe un caso especial, que se da cuando el
bloque de instrucciones a ejecutar consta de una sóla
instrucción:
Lo malo |
Lo bueno | |
IF a>b THEN Cabeza(a,b) ELSE Cabeza(b,a); |
IF a>b THEN BEGIN Cabeza(a,b); END ELSE BEGIN Cabeza(b,a); END; |
Algunos purista tratarán de ahorrar cuatro renglones al
escribir toda la instrucción sin indentación, pero
el resultado es desagradable. Es mejor preservar la estructura de
indentación en todo el programa. Y aunque parezca un poco
cargado el usar seis líneas para escribir esta secuencia,
lo cierto es que en Modula-2 se usan cinco renglones. Al
establecer la convención de incluir un bloque
BEGIN-END
para el IF
siempre, se logra
acercar más el Pascal a Modula-2, y aunque el precio a
pagar es un poco alto (un renglón por instrucción
IF
), la consistencia en el uso de esta regla produce
programas mucho más legibles, principalmente cuando se usan
bloques IF
muy extensos, o cuando hay mucho
anidamiento.
FOR
Como en caso del IF
, el DO BEGIN
del FOR
debe estar en el mismo renglón:
FOR i := 1 TO n DO BEGIN Read(a[i]); IF a[i] > max THEN BEGIN max := a[i]; END; END;
Como ya se ha dicho, el END
cierra el bloque del
ciclo. Al usar un ciclo FOR
siempre debe incluirse un
bloque BEGIN-END
, pues ayuda mucho a delimitar el
código:
Lo malo |
Lo bueno | |
FOR i := 1 TO n DO FOR j := 1 TO i-1 DO a[i,j] := 0; |
FOR i := 1 TO n DO BEGIN FOR j := 1 TO i-1 DO BEGIN a[i,j] := 0; END; END; |
El incluir los bloques BEGIN-END
ayuda a delimitar el
ámbito de cada ciclo, y evita confusiones. Además,
al incluirlos es fácil incluir nuevas instrucciones en el
bloque, mientras que en el otro caso es posible que al hacerlo
olvidemos crear el bloque, y resulte un programa que, aunque
indentado, funciona diferente a lo esperado. (Cada programador
Pascal sabe que buscar un END
perdido es un grave
problema).
Es una muy buena práctica el hacer el cuerpo completo de cada estructura de bloques, antes de rellenarla. Por ejemplo, el ejemplo de arriba debió ser digitado en la siguiente secuencia:
Bloque primero |
Bloque segundo | |
FOR i := 1 TO n DO BEGIN END; |
FOR i := 1 TO n DO BEGIN FOR j := 1 TO i-1 DO BEGIN END; END; |
Al seguir esta pequeña técnica, el programador mantiene en todo momento una estructura sintáctica válida, y evita tediosos errores de bloques que comienzan y no terminan, o viceversa. (Este mismo resultado se puede lograr usando los macros del editor).
WHILE
El WHILE
debe indentarse siguiendo los mismos
principios usados al indentar el FOR
: debe siempre
incluírsele un bloque BEGIN-END
, y debe
indentarse dos espacios:
i := 1; salir := FALSE; WHILE (i<n) AND (NOT salir) DO BEGIN Read(a[i]); salir := (a[i] = Fin_Archivo); INC(i); END;
REPEAT
El REPEAT
debe indentarse de la misma forma que el
WHILE
y el FOR
. A diferencia de
éstos, no es necesario rodear el bloque de instrucciones a
ejecutar por la pareja BEGIN-END
, pues la palabra
clave UNTIL
sirve de delimitador:
i := 1; REPEAT Read(a[i]); IF a[i] < max THEN BEGIN max := a[i]; END; INC(i); UNTIL i<n ;
CASE
Al escribir el CASE
deben indentarse los bloque
correspondientes a cada escogencia, y los valores de
selección deben estar indentados respecto a la palabra
clave CASE
. El ELSE
del
CASE
no se indenta. Además el END
que cierra el CASE
debe llevar entre llaves la
palabra CASE
:
FOR i := 1 TO n DO BEGIN Read(c); CASE c OF 'a': BEGIN { comando de inclusión } Arreglar(ax,dy); Cambiar(dy,dz); END; 'b': BEGIN { comando de grabación } Botar(dy); Recuperar(dz); END; ELSE BEGIN { recuperar el anterior } Recuperar(dy); Cerrar(dy); END; END; { CASE } END; { FOR }
Lo usual al usar el CASE
es escribir muchas
selecciones relacionadas, por lo que deben usarse muchas
instrucciones, que generalmente no caben en una sóla
pantalla u hoja de papel. Por eso es conveniente que la estructura
de la instrucción se refleje en la indentación. Esa
es la misma razón que justifica el incluir la etiqueta
{ CASE }
con el END
que cierra
el bloque. Por la misma razón se incluye la palabra clave
{ FOR }
.
Finalmente, la convención debe seguirse aún cuando
cada bloque en el CASE
tenga un renglón:
CASE i OF 1: WriteLn('Avanzado'); 2: WriteLn('Intermedio'); 3: Writeln('Novato'); ELSE WriteLn('Inválido'); END; { CASE }
ELSE IF
Una secuencia de IF
s anidados pueden usarse para
implementar una escogencia múltiple, en que las condiciones
no son sencillas. Para estos casos, los IF
s deben
indentarse siguiendo el mismo patrón definido para el
CASE
y el IF
: cada condición no
se indentada, el END
cierra un bloque de
indentación, cada ELSE IF
está en
un nuevo renglón y el END
que cierra el
último ELSE IF
contiene entre corchetes
esas palabras clave:
FOR i := 1 TO n DO BEGIN Read(c); IF NOT c IN [Alfa, Beta] THEN BEGIN { inclusión } Arreglar(ax,dy); Cambiar(dy,dz); END ELSE IF Func_plot(c)-3 < Corte_h THEN BEGIN { grabación } Botar(dy); Recuperar(dz); END ELSE BEGIN { recuperar } Recuperar(dy); Cerrar(dy); END; { ELSE IF } END; { FOR }
WITH
Nicklaus Wirth no incluyó el WITH
en Oberon,
el paso siguiente en la evolución de Pascal, pues esta
instrucción ayuda a confundir mucho en los programas. El
WITH
no ayuda a la legibilidad de un programa. La
convención sobre el uso del WITH
es:
No debe usarse el WITH
|
El problema principal con el WITH
es que no es
fácil para el lector saber si una variable es un campo de
un registro o no. Cuando hay que dar mantenimiento a un programa
un poco complejo, termina el programador saltando de procedimiento
en procedimiento, buscando los campos de los registros. Y lo peor
es que es muy difícil quitar las instrucciones
WITH
, más si están anidadas.
Después de la programación estructurada, el gran principio que permite construir grandes sistemas y programas es el uso de procedimientos (el siguiente paso es la programación por objetos). Es entonces necesario definir el formato en que éstos deben ser escritos.
Un procedimiento siempre debe ser una abstracción, o sea que debe realizar algún trabajo, ocultando el detalle de proceso. En general, es bueno que a cada procedimiento corresponda una tarea específica, y que la cumpla a cabalidad. O sea, un procedimiento no debe ser incompleto, ni debe realizar muchas cosas no conexas, que no tienen gran cohesión en el contexto del programa.
Además, un procedimiento debe ser "corto". Cuando una bloque de código se hace muy largo es difícil entender su funcionamiento, indentarlo y comentarlo. Es incómodo manipular grandes trozos de código, por lo que el código debe dividirse en pedazos coherentes, en que cada pedazo corresponde a un procedimiento.
Un procedimiento está compuesto por varias partes. Las siguientes deben ser descritas:
- p1) Nombre
- p2) Parámetros
- p3) Variables globales
- p4) Especificación
- p5) Implementación
El nombre del procedimiento debe seguir las convenciones sobre identificadores descritas anteriormente.
Para cada parámetro del procedimiento debe describirse si es de entrada, salida o ambos, y el propósito de su existencia. Lo mismo debe hacerse con las variables globales (aunque la costumbre de los buenos programadores es no utilizar jamás variables globales, aún cuando se necesitan).
Cada parámetro debe escribirse en un renglón aparte, y debe estar seguido por un comentario descriptivo. Los parámetros deben agruparse en una secuencia lógica, que facilite el uso e identificación por parte de un usuario del procedimiento.
La especificación del procedimiento describe el objetivo del procedimiento, y las condiciones bajo las que el procedimiento produce resultados correctos. Esta descripción debe ser independiente de la implementación pues de otra manera el procedimiento no sería una abstracción.
Dentro de la especificación del procedimiento deben
describirse bajo la cláusula "RESULTADO
" el
trabajo realizado por el procedimiento. A veces es difícil
decir qué hace el procedimiento; eso sucede cuando el
procedimiento no es una abstracción, por lo que debe ser
revisado y corregido.
En la cláusula "REQUIERE
" las condiciones que
las variables y parámetros deben cumplir para que el
procedimiento funcione. El programador puede incluir otras
cláusulas, como su nombre al principio del procedimiento y
"MODIFICACIONES
", en que se describen la fecha en que
diversas modificaciones al procedimiento se han hecho. Esta
última puede incluirse en el encabezado del procedimiento.
La idea es que la especificación del procedimiento sea la
página del manual de uso del mismo.
Bajo el encabezado de "NOTAS
" el programador puede
describir cualquier detalle relevante que desee. El encabezado
"EJEMPLO
" se usa para dar un ejemplo de uso del
procedimiento, y debe estar al principio del procedimiento.
Pareciera ser que la documentación interna queda muy cargada, pero en realidad lo que sucede es que se está haciendo, para cada procedimiento, un manual de uso. En algunos casos, puede ser más conveniente incluir todas estas cláusulas en la documentación externa; lo erróneo es simplemente omitirlas. Lo cómodo de incluirlas dentro del programa es que se hacen precisamente cuando el procedimiento se define o modifica, con lo que la documentación siempre estará actualizada.
El nombre del procedimiento debe incluirse como un comentario
después del BEGIN
que comienza el
procedimiento, y también después del
END
que lo termina. Esto es particularmente
útil cuando se usan procedimientos anidados. Niklaus Wirth,
en su nuevo lenguaje Modula-2, ha considerado esta práctica
tan importante que la ha hecho obligatoria en el END
que cierra el bloque.
También debe especificarse si el procedimiento es o no
público, mediante la palabra clave "EXPORT
". O
sea, debe definirse cuáles procedimientos estarán
accesibles al usuario.
Mediante las unidades de Turbo Pascal es posible definir una
biblioteca de procedimientos (UNIT
), que contiene dos
partes: interfaz e implementación. En la primera se
incluyen sólo las declaraciones de procedimientos que
pueden ser usados por los usuarios de la biblioteca, mientras que
en la segunda se deben incluir todos los procedimientos, inclusive
los no visibles. Al incluir la declaración
"EXPORT
" para los procedimientos que aparecen en la
parte de interfaz de la unidad se resalta a los procedimientos
públicos, y también permite el automatizar la
creación de la sección de interfaz de la biblioteca.
(En otros lenguajes, como C y Modula-2, existen facilidades
diferentes para lograr este mismo propósito). Veamos un
ejemplo completo:
FUNCTION Busqueda_Binaria( { EXPORT } { Adolfo Di Mare } {+} VAR a : Int_A; { arreglo en el que se busca k } {+} l, { posición desde en a } {+} h : TIndex, { posición hasta en a } {+} k : INTEGER { llave a buscar } ) {>>>>>>>} : TIndex; { posición en la que se encontró k } { RESULTADO Esta función busca en el arreglo a la llave k. Si k está entonces retorna un índice p tal que a[p] := k. Si no es así, retorna Indice_Invalido } { REQUIERE a esta ordenado: a[i] <= a[j] siempre que i < j. El arreglo a tiene valores válidos desde a[l] hasta a[h] } { NOTAS Tomado del algoritmo de Niklaus Wirth: Algoritmos y Estructuras de datos, pag 64. } { EJEMPLO i := Busqueda_Binaria( arreglo, 1,n, llave) } { MODIFICACIONES 12-May-88: Ahora encuentra la última llave igual a k } VAR i,p : TIndex; BEGIN { Busqueda_Binaria } p := Indice_Invalido; { inicializa } FOR i := l TO h DO BEGIN IF a[i] = k THEN BEGIN { PARCHAR } p := i; { es secuencial.... NO binaria!!! } END; END; Busqueda_Binaria := p; { Después será programada la búsqueda binaria, pues ahora no tengo tiempo: ADH 12-May-88 } END; { Busqueda_Binaria }
De este ejemplo pueden desprenderse varias cosas. La implementación no es una búsqueda binaria, como se había declarado originalmente. Sin embargo, la implementación escogida tiene todas las propiedades de la búsqueda binaria, salvo que es (mucho) más lenta. Esto es válido hacerlo, principalmente cuando se está desarrollando el programa. Nótese el comentario al respecto.
Los comentarios se incluyen a todo lo largo del procedimiento, en aquellos lugares en que son pertinentes. Las llaves que encierran a los comentarios están alineadas, aunque esto implique "desperdiciar" algunos espacios en blanco. Esto es particularmente importante, pues da una sensación de orden a todo el programa, y lo hace agradable a la lectura.
Las palabras "REQUIERE
",
"MODIFICACIONES
", etc están cada una en un
renglón aparte, y comienzan con mayúscula.
Además, la llave que cierra el comentario de cada una de
estas cláusulas está alineado, y se encuentra en el
último renglón de la cláusula. Los
comentarios que les siguen están alineados a cada palabra,
e indentados. El tipo retornado por la función está
indicado con el símbolo
{>>>>>>}
, que lo resalta.
A la par de la declaración del procedimiento también se define si el procedimiento es o no público, y el nombre del programador.
Cada
parámetro aparece en un renglón, con una
anotación: el {+}
indica que el
parámetro es de entrada, por lo que no puede ser modificado
(o sea, que no puede estar a la izquierda de asignación
alguna, ni puede aparecer como argumento modificable en
procedimiento alguno). La anotación {-}
indica
que el parámetro es de salida. Un parámetro de
salida debe siempre estar acompañado de la
declaración VAR
, pues de lo contrario es
pasado por valor. Finalmente, la anotación {?}
indica que el parámetro es de entrada y salida, o sea, que
el procedimiento lo modifica. La tabla siguiente resume estas
anotaciones:
Anotación | uso del parámetro | declaración VAR |
---|---|---|
{+} |
entrada | opcional |
{-} |
salida | obligatoria |
{?} |
entrada/salida | obligatoria |
Cabe aclarar que hay muchas otras cosas que pueden mencionarse al definir un procedimiento, aunque nuestras convenciones son bastante completas. Por ejemplo, los manuales de Borland usan un formato parecido al aquí definido, y siempre incluyen un ejemplo de uso del procedimiento.
Al invocar a un procedimiento, en general es mejor separar los argumentos con un espacio en blanco, y no sólo con una coma. Pero si el procedimiento tiene pocos argumentos cortos, puede omitirse el espacio entre ellos:
Corrector(examen_final, nota, promedio); a := f(x,y,z);
Para implementar la facilidad de compilación separada, el Turbo Pascal hace uso de unidades. La documentación de éstas es simple, pues únicamente deben incluirse algunos comentarios en el encabezado que describan el propósito de la unidad, el nombre del programador, y algunas otras dependencias o detalles de implementación relevantes.
Debe copiarse en la sección de implementación el encabezado completo de cada procedimiento exportado. De esta manera es fácil examinar el encabezado de cada procedimiento en esta sección de la unidad.
En algunas ocaciones pueden ser beneficioso usar alguna convención para nombrar unidadades, pero en general lo que realmente resulta útil es escoger para nombres de unidades identificadores que sean muy significativos.
Para generar la sección de interfaz de una unidad
automáticamente puede usarse el siguiente truco: escribir
un programa que lea la sección de implementación de
una unidad, y que copie en un archivo de salida solamente aquellos
encabezados que tienen la anotación "EXPORT
",
descrita en el aparte de procedimientos. Usando tal programa el
programador no debe preocuparse de que la interfaz y la
implementación coincidan, con lo que su trabajo se
facilita.
Tal vez es importante acotar que el programador debe crear la sección de interfaz primero, especificando así cada parte de su programa. El método mecánico sugerido en el párrafo anterior simplemente evita digitar nuevamente la declaración de procedimientos. O sea, que el programador puede trabajar desde el principio en la sección de implementación de su programa, pero debe primero definir la especificación de cada procedimiento, y luego puede generar automáticamente la sección de interfaz de la unidad. Cada programador aprende trucos que le ayudan en su trabajo, y éste es sólo uno más de ellos. (No hay que jalarle mucho el chancho a la raba).
Un ADT es un tipo abstracto de datos, esto es, un tipo de datos
que tienen varias operaciones asociadas. El acceso a una variable
de ese tipo se permite únicamente a través de sus
operaciones. El ejemplo clásico de ADT es la pila, con sus
operaciones Push()
, Pop()
y
Empty()
:
UNIT Stack; INTERFACE USES Elem; TYPE Rep = {...} { representación privada del ADT } TStack = ARRAY[1..Sizeof(Rep)] OF BYTE; PROCEDURE Push ( {?} VAR s: Stack_ADT; {+} VAR e: Elem_ADT); PROCEDURE Pop ( {?} VAR s: Stack_ADT; {-} VAR e: Elem_ADT); FUNCTION Empty ( {+} VAR s: Stack_ADT) : {>>>>>>} BOOLEAN; {...} END.
En una publicación aparte, se discute en detalle cómo implementar ADTs en Pascal [DiM-89]. Baste decir lo siguiente:
El uso de globales en general es una mala práctica, pues cambiando esas variables es posible hacer que un mismo procedimiento tenga comportarmientos diferentes cuando se le invoca con los mismos argumentos. O sea, al usar globales se quiebra el principio de que un procedimiento es una abstracción.
Además, al usar globales se inhibe la verificación de tipos del compilador, por lo que errores que pudieron descubrirse al compilar el programa deben ser detectados manualmente por el programador.
Pero en la práctica es en muchas ocasiones necesario usar variables globales, por lo que ofrecemos dos trucos para controlar su uso. De esta manera no las excluímos de nuestras técnicas, pero las usamos de forma que sepamos en todo momento que una variable global está en uso, evitando inhibir la verificación de tipos que tanto nos ayuda al programar.
El primero y más seguro método es evitar el usarlas del todo, creando un procedimiento principal que sustituye al programa principal, en el que se definen todas las variables a usar en el programa. Al hacer esto es necesario enviar como argumentos todos los valores a los procedimientos usados en el programa, que puede resultar ineficiente en algunos casos. De todas formas, lo correcto es primero lograr la eficacia, y luego la eficiencia:
PROGRAM No_Globales; PROCEDURE Uno( {+} a,b: INTEGER ); BEGIN { Uno } END; { Uno } PROCEDURE TRABAJE; { Aquí comienza el programa principal } VAR v_1, v_2 : INTEGER; BEGIN { TRABAJE } Uno(v_1, v_2); END; { TRABAJE } BEGIN TRABAJE END. { No_Globales }
Al usar este pequeño truco, nos obligamos a pasar a todo procedimiento todos los argumentos que necesite para producir resultados, de hecho desencadenando el poderoso mecanismo de chequeo de tipos con que Pascal cuenta. Así el compilador encontrará en tiempo de compilación errores sintácticos que de otra manera deberíamos descubrir como errores de lógica. Este es un ejemplo en que el compilador trabaja para nosotros, y no nosotros para el compilador.
El segundo truco es declarar todas las variables globales como
campos de un registro llamado GLOBAL
. En el siguiente
ejemplo, todavía usamos el procedimiento
TRABAJE()
para no definir globales, pero ahora
explícitamente debemos cualificar todas las variables
globales con el nombre del registro que les contiene.
PROGRAM No_Globales; VAR GLOBAL : RECORD hora_hoy : INTEGER; archivo_entrada : FILE; END; { GLOBAL } PROCEDURE TRABAJE; { Aquí comienza el programa principal } VAR v_1, v_2 : INTEGER; f : TEXT; BEGIN { TRABAJE } v_1 := GLOBAL.hora_hoy; Assign(f, GLOBAL.archivo_entrada); . . . . . . END; { TRABAJE } BEGIN TRABAJE END. { No_Globales }
Realmente el programa principal es un procedimiento, por lo que debe describirse de la manera en se describe un procedimiento. Además, es necesario incluir una descripción general del objetivo del programa, su forma de uso, restricciones, y requerimientos para entrada y salida. Es preocupante que la mayoría de los programadores ni siquiera escriban su nombre en el código fuente del programa principal.
En el siguiente programa se ilustran muchas de las convenciones discutidas. También se violan algunas, pero siempre para dar mayor legibilidad al código.
{$B+ Fuerza evaluación completa de expresiones booleanas } {$R+ Fuerza verificación de rangos } PROGRAM Hash (INPUT,OUTPUT); { Este programa lee el archivo 'enteros' que contiene números enteros, y cuenta cuantas veces aparece cada número. - El programa procesa un número por línea. } { REQUIERE El archivo de entrada NO debe contener más de H_tamano números diferentes. } { METODO Se usa una tabla de dispersión para almacenar los diferentes números leídos, y luego se imprime la tabla secuencialmente } { PROGRAMADOR Adolfo Di Mare <adolfo@di-mare.com> } USES Crt, Dos; CONST H_tamano = 100; { Número máximo de números de entrada } TYPE TTuple = RECORD { Una entrada en la tabla de dispersión } key, { entero llave } cont : INTEGER; { número de veces que está en el archivo } END; { TTuple } TTabla = ARRAY[1..H_tamano] OF TTuple; { tabla de dispersión } FUNCTION H( {+} k : INTEGER { llave a insertar en la tabla de dispersión } ) {>>>} : INTEGER; { valor de la función de dispersión para k } BEGIN { H } H := (k MOD H_tamano) + 1; END; { H } PROCEDURE Int_set( {?} VAR tabla : TTabla; { tabla hash } {+} key : INTEGER { llave a insertar en tabla } ); { RESULTADO Mediante la función de dispersión h se incluye en la tabla la llave key. Si ya estaba ahí, entonces se incrementa el contador respectivo } { REQUIERE Acepta hasta H_tamano llaves diferentes. Ignora las demás } { NOTAS Los sinónimos se manejan usando direccionamiento abierto } { MODIFICACIONES 25-may-88 : ADH logre que fuera el último, y no el 1ero } VAR comienzo, { lugar en donde se empieza a buscar key } i : 1..H_tamano; cont : INTEGER; BEGIN { Int_set } comienzo := H(key); { calcula el lugar para almacenar key } i := comienzo; cont := 0; { para romper el ciclo de busqueda } REPEAT IF (tabla[i].key = 0) OR (tabla[i].key = key) THEN BEGIN tabla[i].key := key; { key ya está en la tabla } tabla[i].cont := tabla[i].cont + 1; cont := tabla[i].cont; END ELSE BEGIN { busque en donde poner key } i := (i + 1) MOD H_tamano; END; UNTIL (cont <> 0) OR (i = comienzo); END; { Int_set } PROCEDURE TRABAJE; { Programa principal } VAR i : INTEGER; tabla : TTabla; { de dispersión } g : TEXT; { archivo de entrada } key, cont : INTEGER; BEGIN { TRABAJE } FOR i := 1 TO H_tamano DO BEGIN tabla[i].cont := 0; { inicializa la tabla en ceros } END; Assign(g,'enteros'); Reset(g); WHILE NOT Eof(g) DO BEGIN ReadLn(g, key); { Cada entero viene en un renglón } Int_set(tabla,key); { inserta lo leído en la tabla } END; { Imprime aquellos valores del archivo de entrada } FOR i := 1 TO H_tamano DO BEGIN IF tabla[i].cont <> 0 THEN BEGIN WriteLn(tabla[i].key:6, tabla[i].cont:6); END; END; END; { TRABAJE } BEGIN TRABAJE; END. { Hash }
Una buena documentación sólo se logra si se escribe con el programa, lo que requiere de una modesta cantidad de disciplina. Esta se logra con la práctica, y con el deseo de lograr buenos resultados en nuestro trabajo. Es muy importante que antes de escribir un procedimiento, el programador escriba su especificación. El ver la documentación como algo "extra" al programa es un error, que generalemnte tiene un alto valor.
La documentación debe escribirse cuando se escribe el programa. Al seguir estas convenciones el resultado será, en todo momento, un programa sintácticamente correcto, y claramente escrito.
Al documentar correctamente lograremos un mejor programa, simplemente porque un programa sin documentación no es un programa completo.
Cada programador debe ser el juez de su propio trabajo. Estas convenciones serán agradables a unos, y malas para otros. Lo importante es que se usen convenciones; no hacerlo incide en la calidad de los programas producidos.
Mis colegas en la UCR me han apoyado en el uso de estas convenciones, en especial mi colega y esposa Norma Ortega. Agradezco también el apoyo de la profesora Josefina Pujol, quien continúa usando mis convenciones en sus cursos.
Esta investigación se realizó dentro del proyecto de investigación 326-86-053 "Conversión automática de programas después de reestructurar una base de datos", inscrito ante la Vicerrectoría de Investigación de la Universidad de Costa Rica. La Escuela de Ciencias de la Computación e Informática también ha aportado fondos para este trabajo.
[1] | En realidad Sócrates el Griego nunca dijo eso, pero si en su tiempo hubiera habido computadores, no dudo que habría dicho algo parecido. |
[2] | En realidad en programa sin documentación simplemente no es programa. |
[BM-85]
|
Berry, R.E. & Meekings, B.A.E: A Style Analysis of C Programs, Communications of the ACM, Vol.28 No.1, pp [80-88], Enero 1985. |
[BI-88] | Borland International: Turbo Pascal Reference Manual version 5.5, Borland International, California (U.S.A.), 1988. |
[Bou-91] | Boundy, David: A taxonomy of programmers, ACM SigSoft, Vol.16 No.4, pp [23-30], Octubre 1991. |
[DiM-89] | Di Mare, Adolfo: Abstracción de Datos en Pascal, Reporte técnico ECCI-01-89, ECCI-UCR, 1989. |
[JJ-87] | Jacky, Jonathan P. & Kalet, Ira J.: An Object-Oriented Programming Discipline for Standard Pascal, Communications of the ACM, Vol.30 No.9, pp [772-776], Setiembre 1987. |
[KP-78] | Kernighan, Brian W. & Plauger, P. J.: The Elements of Programming Style, second edition, McGraw-Hill Book Company, New York, 1978. |
[LEc-87] | L'Ecuyer, Pierre: Formal Formatting Rules for Pascal Programs, The Journal of Systems and Software, Vol.7, pg 311-322, 1987. |
[LG-86] | Liskov, Barbara & Guttag, John: Abstraction and Specification in Program Development, McGraw-Hill, 1986. |
[Ret-91] | Rettig, Marc: Nobody Reads Documentation, Communications of the ACM, Vol.34 No.7, pp [19-24], Julio 1991. |
[Sie-2002] | Sieler, Stan:
How To Code: Pascal ,
2000-05-03.
http://www.allegro.com/papers/htpp.html
|
[Str-86a] | Stroustrup, Bjarne: The C++ Programming Language, Addison-Wesley, 1986. |
[Str-86b] | Stroustrup, Bjarne: An Overview of C++, Sigplan Notices, Octubre 1986. |
[Sun-99] | Hommel, Scott et al:
Code Conventions for the
Java Programming Laguage,
Sun Microsystems, Inc.,
Abril 20, 1999.
http://java.sun.com/docs/codeconv/CodeConventions.pdf
|
[Wir-82] | Wirth, Niklaus: Programming in Modula-2, Second Edition, R.R. Donnelley & Sons, Harrisonburg, Virginia U.S.A., 1982. |
[Wir-88] | Wirth, Niklaus: From Modula-2 to Oberon, Software Practice and Experience, Vol.23 No.7, pp 661-670, Julio 1988. |
[-] | Resumen | ||
[1] | Introducción | [15] | Indentación del IF
|
[2] | Documentación de sistemas | [16] | Indentación del FOR
|
[3] | Documentación de programas | [17] | Indentación del WHILE
|
[4] | Documentación externa | [18] | Indentación del REPEAT
|
[5] | Documentación interna | [19] | Indentación del CASE
|
[6] | Identificadores | [20] | Selecciones Múltiples ELSE IF
|
[7] | Constantes | [21] | Indentación del WITH
|
[8] | Palabras Reservadas | [22] | Procedimientos |
[9] | Comentarios | [23] | Unidades |
[10] | Instrucciones | [24] | ADTs |
[11] | Pragmas | [25] | Uso de Variables Globales |
[12] | Declaraciones | [26] | El programa principal |
[13] | Expresiones | [27] | Ejemplo |
[14] | Indentación | [28] | Conclusiones |
|
|
||
Notas de pie de página | [-] | Agradecimientos | |
Bibliografía | [-] | Reconocimientos | |
Indice | |||
Acerca del autor | |||
Acerca de este documento | |||
Principio Indice Final |
Adolfo Di Mare <adolfo@di-mare.com>
Referencia: | Di Mare, Adolfo: Convenciones de Programación para Pascal, Reporte Técnico ECCI-01-88, Proyecto 326-86-053, Escuela de Ciencias de la Computación e Informática, Universidad de Costa Rica, 1988. |
Internet: |
http://www.di-mare.com/adolfo/p/convpas.htm
|
Autor: | Adolfo Di Mare <adolfo@di-mare.com> |
Contacto: | Apdo 4249-1000, San José Costa Rica Tel: (506) 207-4020 Fax: (506) 438-0139 |
Revisión: | ECCI-UCR, Marzo 1998 |
Visitantes: |