Fundamentos de programación con Módula-2 (10)

 

4.4.3 Elección de nombres
Otro aspecto de estilo, fundamental para la claridad de un programa, es la elección correcta de los nombres o identificadores utilizados para designar sus diferentes elementos. Los nombres que haya de inventar el programador deben ser elegidos con un criterio nemotécnico, de manera que recuerden fácilmente el significado de los elementos nombrados.

Para evidenciar la importancia de usar nombres adecuados, compararemos dos redacciones de un mismo programa. La primera, con nombres carentes de significado sería:

MODULE xyz;
   FROM InOut IMPORT
      ReadInt, WriteInt, WriteString, WriteLn;
   VAR
      x, y, z: INTEGER;
BEGIN
   ReadInt( x );
   ReadInt( y );
   z := x * y;
   WriteString( "Area = " );
   WriteInt( z, 10 )
END xyz.

La segunda redacción del programa, con nombres significativos, podría se:

MODULE CalcularAreaRectangulo;
   FROM InOut IMPORT
      ReadInt, WriteInt, WriteString, WriteLn;
   VAR
      base, altura, area: INTEGER;
BEGIN
   ReadInt( base );
   ReadInt( altura );
   area := base * altura;
   WriteString( "Area = " );
   WriteInt( area, 10 )
END CalcularAreaRectangulo.

Comprando ambas redacciones es muy fácil darse cuenta de la ventaja de la segunda, cuyo significado puede adivinarse fácilmente incluso con la total ausencia de comentarios explicativos en el programa.

Para que los nombres o identificadores resulten significativos hay que procurar que tengan la categoría gramatical adecuada al elemento nombrado. En concreto:

  • Los valores (constantes, variables, etc.) deben ser designados mediante sustantivos.
  • Las acciones (procedimientos, etc.) deben ser designadas mediante verbos.
  • Los tipos deben ser designados mediante nombres genéricos.

En programas largos con muchos identificadores resulta a veces difícil inventar nombres significativos que distingan fácilmente entre valores y tipos. Un recurso de estilo puede ser construir los identificadores de tipo usando sistemáticamente el prefijo Tipo o similar en todos ellos; por ejemplo TipoLongitud.

Al usar verbos, conviene emplearlos de manera uniforme utilizando siempre el mismo tiempo gramatical. En inglés suelen aparecer en imperativo. En español resulta quizá más eficaz emplear sistemáticamente el infinitivo. Por ejemplo, el equivalente en español de los nombres de los procedimientos de lectura y escritura podría ser:

         WriteString                EscribirRistra
         WriteLn                     EscribirFinDeLinea
         WriteInt                     EscribirEntero
         ReadInt                     LeerEntero
            …                               …

A continuación se presenta un ejemplo de programa completo, incluyendo identificadores de diferentes clases elegidos según las recomendaciones anteriores. En este ejemplo se ha supuesto que existe un módulo llamado Fechas que define procedimientos para manipular datos de tipo fecha. El estilo seguido para elegir los nombres inventados por el programador ha sido:

  • Los nombres de operaciones (procedimientos y programa principal) son verbos en infinitivo:
                   CalcularDias        LeerFecha              EscribirFecha
  • Los nombres de valores (funciones y variables) son sustantivos:
                   DiasEntre             Hoy                fechaCumple
                   fechaHoy              dias
  • Los nombres de tipo empiezan por el prefijo Tipo:
                    TipoFecha

(**********************************************************
*
*    Programa: CalcularDias
*
*    Descripción:
*      Este programa calcula los días que faltan para el
*      cumpleaños de una persona.
***********************************************************)

MODULE CalcularDias;

(*=========================================================
    IMPORTACIÓN Y DECLARACIONES DEL PROGRAMA
=========================================================*)
  FROM InOut IMPORT WriteString, WriteLn;
  FROM Fechas IMPORT
    TipoFecha,                            (* Tipo de valor FECHA *)
    LeerFecha,                            (* Leer una fecha *)
    EscribirFecha,                        (* Escribir una fecha *)
    Diasentre,                            (* Dias entre dos fechas *)
    Hoy;                            (* Fecha de hoy *)

  VAR
    fechaCumple: TipoFecha;                    (* Cumpleaños *)
    fechaHoy: TipoFecha;                    (* Fecha de hoy *)
    dias: INTEGER;                        (* Días que faltan *)

BEGIN
(*=========================================================
    PARTE EJECUTABLE DEL PROGRAMA
=========================================================*)
  (*– Obtener la fecha de cumpleaños –*)
    WriteString( "¿Cuál es tu próximo cumpleaños? ");
    LeerFecha( fechaCumple );
  (*– Obtener la fecha de hoy –*)
    fechaHoy := Hoy;
  (*– Calcular los días que faltan para el cumpleaños –*)
    dias := DiasEntre( fechaHoy, fechaCumple );
  (*– Imprimir resultado –*)
    WriteLn; WriteString( "Faltan" );
    WriteInt( dias, 4);
    WriteString( " días para tu cumpleaños" );
END CalcularDias.

4.4.4 Uso de letras mayúsculas y minúsculas
Los lenguajes de programación que permiten distinguir entre letras mayúsculas y minúsculas facilitan la construcción de nombres en programas largos, en que es preciso inventar un gran número de ellos.

Por ejemplo, para marcar claramente a qué clase de elemento del programa se está refiriendo un nombre, se puede adoptar el criterio de que los identificadores de ciertas clases de elementos empiecen siempre con mayúscula, y otros siempre con minúscula, o que algunos muy especiales se escriban todo en mayúsculas.

Por otra parte, al elegir nombres compuestos se pueden usar mayúsculas intercaladas para marcar el comienzo de cada parte, como ya se ha visto para los módulos de lectura y escritura "estándar" InOut y RealInOut. Así se ha hecho también en el ejemplo del apartado anterior, donde además se han empleado nombres de tipos y de procedimientos que siempre empiezan con mayúsculas, y nombres de variables que siempre empiezan con minúscula.

En el ejemplo del apartado anterior se han seguido esas mismas reglas de estilo para escribir los identificadores inventados por el programados:

  • Los nombres de tipos, procedimientos, funciones y programas empiezan por mayúsculas:
              CalcularDias            LeerFecha             EscribirFecha
              DiasEntre                Hoy
  • Los nombres de variables empiezan por minúscula:
               fechaCumple            fechaHoy             dias
  • Los nombres que son palabras compuestas usan mayúsculas intercaladas al comienzo de cada siguiente palabra componente:
               CalcularDias             LeerFecha              EscribirFecha
               DiasEntre                 fechaCumple           fechaHoy

En cualquier caso hay que limitar mucho el empleo de nombres escritos totalmente en mayúsculas, ya que en general hacen más pesada la lectura del texto del programa. Sólo deberían escribirse así elementos que han de destacar entre los demás, como ya ocurre con las palabras clave del lenguaje Modula-2.

Compárese el siguiente programa, escrito todo en mayúsculas, con el ejemplo correspondiente del apartado 4.4.3. La lectura resulta ahora mucho más difícil.

MODULE CALCULARAREARECTANGULO;
   FROM INOUT IMPORT
      READINT, WRITEINT, WRITESTRING, WRITELN;
   VAR
      BASE, ALTURA, AREA: INTEGER;
BEGIN
   READINT( BASE );
   READINT( ALTURA );
   AREA := BASE * ALTURA;
   WRITESTRING( "AREA = " );
   WRITEINT( AREA, 10 )
END CALCULARAREARECTANGULO.

4.4.5 Constantes con nombre
La posibilidad de declarar constantes con nombres simbólicos puede aprovecharse para mejorar la claridad del programa. En lugar de usar directamente valores numéricos en las expresiones de algunos cálculos, puede resultar ventajoso definir determinados coeficientes o factores de conversión con un nombre simbólico que tenga un buen significado nemotécnico, y usar la constante con ese nombre en los cálculos. Por ejemplo, para transformar una longitud de pulgadas a centímetros, en lugar de escribir

                longitudCm := longitudPul * 2.54

se podría poner

                CONST cmPorPulgada = 2.54;
                 …
                longitudCm := longitudPul * cmPorPulgada

Esta segunda forma resultará más significativa para los que no recuerden de memoria ese factor de conversión. Además se tiene una ventaja adicional en el caso de que un mismo valor constante se use en varios puntos del programa; al definirlo como constante con nombre el valor numérico particular se escribe sólo una vez, en la definición, en lugar de hacerlo tantas veces como se use, y así se reducen las posibilidades de cometer errores de escritura.

Otra forma ventajosa de usar el mecanismo de definición de constantes con nombre se da en el caso de que el comportamiento de un programa venga dado en función de ciertos valores generales, fijos, pero que quizá fuera interesante cambiarlos en el futuro. Este tipo de valores se denominan a veces parámetros del programa, y es conveniente que su valor aparezca escrito sólo una vez en lugar destacado del programa. Por ejemplo, un programa para reformar un texto antes de enviarlo a la impresora puede tener como parámetros generales el ancho de la línea de escritura, o el tamaño de la página. Podríamos escribir, al comienzo del programa:

(*=============================================
               PARÁMETROS GENERALES
=============================================*)
   CONST
      AnchoLinea = 72;
      LineasPagina = 60;

De esta manera queda perfectamente destacada la parte del programa que hay que modificar si se quieren cambiar las dimensiones útiles de la hoja impresa.

4.5 Ejemplos de programas

A continuación se desarrollan algunos programas sencillos mediante refinamientos sucesivos.

4.5.1 Ejemplo: Imprimir la figura de un árbol de navidad
Se trata de escribir un programa que imprima la silueta de un árbol de navidad convencional, según aparece en el siguiente listado resultado de la impresión:

       *
     * * *
   * * * * *
     * * *
   * * * * *
 * * * * * * *
   * * * * *
 * * * * * * *
* * * * * * * * *
        *
        *
        *
   * * * * *

Para estructurar el programa trataremos de identificar las diferentes partes de la figura del árbol. Podemos reconocer la copa, formada por tres pisos de ramas, el tronco, y la base. Teniendo en cuenta que las distintas partes han de imprimirse de arriba a abajo, organizaremos los primeros pasos de refinamiento:

       Imprimir árbol —>
           
Imprimir copa
            Imprimir tronco
            Imprimir base

      Imprimir copa —>
           
Imprimir primeras ramas
            Imprimir segundas ramas
            Imprimir terceras ramas

La impresión de cada parte se consigue directamente con sentencias de escrituras sencillas. Podemos pasar ya a escribir el programa en Modula-2, documentándolo de acuerdo con las reglas de estilo propuestas. El programa completo aparece a continuación.

(********************************************************
*
*    Programa: ARBOL
*
*    Autor: M. Collado
*
*    Descripción:
*       Este programa imprime la silueta de un árbol
*      de navidad, hecha con asteriscos
*
********************************************************)

MODULE Arbol;

(*=======================================================
    IMPORTACIÓN Y DECLARACIONES DEL PROGRAMA
=======================================================*)
  FROM InOut IMPORT WriteString, WriteLn;

BEGIN
(*=======================================================
    PARTE EJECUTABLE DEL PROGRAMA
=======================================================*)
  (*– Imprimir copa –*)
    (*– Imprimir primeras ramas –*)
      WriteString( "    *"); WriteLn;
      WriteString( "   ***"); WriteLn;
      WriteString( "  *****"); WriteLn;
    (*– Imprimir segundas ramas –*)
      WriteString( "   ***"); WriteLn;
      WriteString( "  *****"); WriteLn;
      WriteString( " *******"); WriteLn;
    (*– Imprimir terceras ramas –*)
      WriteString( "  *****"); WriteLn;
      WriteString( " *******"); WriteLn;
      WriteString( "*********"); WriteLn;
    (*– Imprimir tronco –*)
      WriteString( "    *"); WriteLn;
      WriteString( "    *"); WriteLn;
      WriteString( "    *"); WriteLn;
    (*– Imprimir base — *)
      WriteString( "  *****"); WriteLn;
END Arbol.

4.5.2 Ejemplo: Calcular el costo de las baldosas
Se trata de calcular el costo total de las baldosas necesarias para cubrir el suelo de una habitación rectangular. Se supone que las baldosas son cuadradas. El programa lee como dato el lado de las baldosas, en centímetros, y las dimensiones de la habitación rectangular en metros. También se suministra como dato el precio unitario de cada baldosa.

El programa calcula cuántas baldosas hay que colocar a lo largo de cada dimensión de la habitación, incluyendo contar una baldosa más si no es un número entero, y hay que romper algunas baldosas para cubrir exactamente hasta el borde. A continuación se calcula el número total de baldosas y se multiplica por el precio de cada una.

En forma  abreviada, los primeros pasos de descomposición conducen a:

         Calcular el costo de baldosas —>
               
Leer los datos
                Calcular el número de baldosas
                Calcular el coste total
                Imprimir el resultado

         Calcular el número de baldosas —>
               
Calcular las baldosas a lo largo
                Calcular las baldosas a lo ancho
                Calcular el número total de baldosas

El progra completo, debidamente documentado, aparece en el siguiente listado:

(**************************************************************
*
*    Programa: BALDOSAS
*
*    Autor: S.R. Gómez
*
*    Implementación: Ernesto Rodas (THC_CAOS)
*
*    Descripción:
*      Este programa calcula el costo de las baldosas
*      necesarias para cubrir una habitación rectangular.
*
**************************************************************)
MODULE Baldosas;

(*============================================================
    IMPORTACIÓN Y DECLARACIONES DEL PROGRAMA
============================================================*)
  FROM InOut IMPORT WriteString, WriteLn, WriteInt, ReadInt;

  VAR
    largo, ancho: INTEGER;    (* Dimensiones de la habitación *)
    lado: INTEGER;        (* Lado de la baldosa, en cm. *)
    nLargo: INTEGER;        (* Núm. de baldosas a lo largo *)
    nAncho: INTEGER;        (* Núm. de baldosas a lo ancho *)
    baldosas: INTEGER;        (* Número total de baldosas *)
    precio: INTEGER;          (* Precio de cada baldosas *)
    coste: INTEGER;        (* Coste total *)

BEGIN
(*==========================================================
    PARTE EJECUTABLE DEL PROGRAMA
==========================================================*)
 (*– Leer datos –*)
    WriteString( "Dar el tamaño de la habitación, en m." );
    WriteLn;
    WriteString( "¿Largo, ancho? " );
    ReadInt( largo ); ReadInt ( ancho ); WriteLn;
    WriteString( "¿Lado de la baldosa, en cm.? " );
    ReadInt( lado ); WriteLn;
    WriteString( "¿Precio de cada baldosa, en pts.? " );
    ReadInt( precio ); WriteLn;

  (*– Calcular el número de baldosas –*)

    (*– Calcular las baldosas a lo largo, por exceso –*)
    nLargo := ( largo*100+lado-1 ) DIV lado;

    (*– Calcular las baldosas a lo ancho, por exceso –*)
    nAncho := ( ancho*100+lado-1) DIV lado;

    (*– Calcular el número total de baldosas –*)
      baldosas := nLargo * nAncho;

    (*– Calcular el coste total –*)
    coste := baldosas * precio;

    (*– Imprimir el resultado –*)
    WriteString( "Total" ); WriteInt( baldosas, 5 );
     WriteString( " baldosas"); WriteLn;
    WriteString( "Coste" ); WriteInt( coste, 6 );
    WriteString( " pts." ); WriteLn
END Baldosas.

El resultado de una posible ejecución es el siguiente:

Dar el tamaño de la habitación, en m.
¿Largo, ancho? 4 6
¿Lado de la baldosa, en cm.? 30
¿Precio de cada baldosa, en pts.? 56
Total  280 baldosas
Coste 15680 pts.

4.5.3 Ejemplo: Calcular los días entre dos fechas
Este ejemplo consiste en calcular la diferencia en días entre dos fechas. Para simplificar, el cálculo se hace en forma aproximada, contando todos los meses a razón de 30 días cada uno, y los años completos siempre con 365 días.

El cálculo se hace en dos partes. Primero se calculan para cada fecha los días transcurridos desde el comienzo de su año. Luego se calcula la diferencia entre fechas, pasando la diferencia en años a días y acumulando la diferencia en días dentro del año.

Los pasos de descomposición son relativamente sencillos:

           calcular la diferencia de fechas —>
                    leer las fechas
                    calcular la diferencia
                    imprimir el resultado

           calcular la diferencia —>
                    calcular los días desde principio del año
                    calcular la diferencia total en días

El programa completo aparece en el siguiente listado:

(***************************************************************************
*
*    Programa: DIFERENCIA EN DIAS
*
*    Autor: J.F. Estívariz
*
*    Implementación: Ernesto Rodas (THC_CAOS)
*
*    Descripción:
*      Este programa calcula los días entre dos
*      fechas, en forma aproximada, contando todos
*      los meses de 30 días, y los años de 365.
*
***************************************************************************)
MODULE DiferenciaEnDias;

(*==========================================================================
    IMPORTACIÓN Y DECLARACIONES DEL PROGRAMA
==========================================================================*)
  FROM InOut IMPORT WriteString, WriteLn, WriteInt, ReadInt;

  VAR
    dia1, mes1, anno1: INTEGER;        (* primera fecha *)
    dia2, mes2, anno2: INTEGER;        (* segunda fecha *)
    diasFecha1, diasFecha2: INTEGER;    (* días desde comienzo el año *)
    diferencia: INTEGER;        (* diferencia en días *)

BEGIN
(*==========================================================================
    PARTE EJECUTABLE DEL PROGRAMA
===========================================================================*)
  (*– Leer las fechas –*)
    WriteString( "¿Primera fecha (dd mm aa)? " );
    ReadInt( dia1 ); ReadInt( mes1 ); ReadInt( anno1 ); WriteLn;

    WriteString( "¿Segunda fecha (dd mm aa)? " );
    ReadInt( dia2 ); ReadInt( mes2 ); ReadInt( anno2 ); WriteLn;

  (*– Calcular la diferencia –*)
    (*– Calcular los días desde principio del año –*)
    diasFecha1 := (mes1 – 1)*30 + dia1;
    diasFecha2 := (mes2 – 1)*30 + dia2;

    (*– Calcular la diferencia total en días –*)
    diferencia := (anno2 – anno1)*365 + diasFecha2 – diasFecha1;

  (*– Imprimir el resultado –*)
    WriteString( "Desde " ); WriteInt( dia1, 2 );
    WriteString( "/" ); WriteInt( mes1, 2 );
    WriteString( "/" ); WriteInt( anno1, 4 ); WriteLn;

    WriteString( "hasta "); WriteInt( dia2, 2 );
    WriteString( "/" ); WriteInt( mes2, 2 );
    WriteString( "/" ); WriteInt( anno2, 4 ); WriteLn;

    WriteString( "hay" ); WriteInt( diferencia, 6 );
    WriteString( " días" ); WriteLn
END DiferenciaEnDias.

El resultado de una posible ejecución del programa es el siguiente:

¿Primera fecha (dd mm aa)? 20 3 1993
¿Segunda fecha (dd mm aa)? 14 1 1995
Desde 20/ 3/1993
hasta 14/ 1/1995
hay   664 días

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s