guiaestilocpp

12
Guía de estilo de programación en C++ Pequeños consejos sobre cómo escribir programas más legibles.

Upload: jhonnatthan-flores

Post on 26-Jul-2015

60 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: Guiaestilocpp

Guía de estilo de programación en C++Pequeños consejos sobre cómo escribir programas más legibles.

Page 2: Guiaestilocpp

Índice1Introducción........................................................................................................................................3

1.1Nomenclatura..............................................................................................................................32Indicaciones generales........................................................................................................................33Identificadores....................................................................................................................................4

3.1Identificadores de clases.............................................................................................................43.2Identificadores de dato miembros...............................................................................................53.3Identificadores de objetos...........................................................................................................53.4Identificadores de funciones miembro........................................................................................53.5Identificadores de variables locales en funciones miembro.......................................................6

3.5.1Declaración de variables.....................................................................................................64Comentarios........................................................................................................................................7

4.1Comentarios de inicio de bloque.................................................................................................74.2Comentarios aclaratorios............................................................................................................84.3Comentarios sobre funciones miembro o funciones...................................................................8

5Disposición de elementos...................................................................................................................95.1Clases..........................................................................................................................................9

5.1.1Unidades de traducción........................................................................................................95.2Funciones..................................................................................................................................10

5.2.1Estructuras de decisión y repetición..................................................................................115.2.1.1Cuerpos de instrucciones de una sola línea................................................................11

5.3Espaciado..................................................................................................................................12

Page 3: Guiaestilocpp

1 IntroducciónEn este documento se explica brevemente, y con ejemplos, cómo escribir programas más legibles. A lo largo de su vida profesional, cualquier programador se plantea la pregunta de cómo escribir sus programas de manera que sean más legibles, y por tanto que puedan ser mantenidos (corrección de errores,   ampliación   de   funcionalidad)   con   facilidad,   tanto   por   él   mismo   como   por   otros programadores (una situación típica en cualquier empresa).

Dado  que   la  misma   situación   en   cuanto   a   legibilidad  de   código   suele   ser   resuelta   por distintos   programadores   de   la   misma   forma,   pero   con   distintos   matices   de   diferenciación,   es interesante seguir una guía de estilo que nos explique como otros resolvieron ese mismo problema, hasta que dicha solución se convirtió en un estándar.

1.1 NomenclaturaEn este documento se sigue la nomenclatura típica de C++, según la cual las operaciones incluidas dentro de las clases se conocen como funciones miembro, y las variables, también incluidas, como datos miembro.

2 Indicaciones generalesEl propósito de seguir una norma de estilo es hacer que el código fuente de un programa sea tan legible como sea posible. Así, hay tres puntos básicos que se deben cuidar: el espaciado horizontal, el espaciado vertical y la indentación. El espaciado horizontal consiste en que las líneas en las que esté  dividido el  código fuente deben ser de una longitud máxima lo más próxima posible a 80 caracteres, ya que al imprimir el código en papel cualquier línea superior a 80 columnas se descarta automáticamente. 

Uno de los problemas que se puede encontrar al codificar es la manera más correcta para dividir una línea muy larga. Respuestas comunes a esta pregunta son: antes de una subexpresión, antes de un operador, o antes de un paréntesis. Por ejemplo:

int x = ( (a * b + c ) / ( c * d * d ) )         + ( a / ( b * c ) )        + ( ( 3.1451927 * b ) + d );

Los operadores deben separarse mediante espacios para mayor claridad, a excepción hecha de operadores unarios como el incremento o decremento, tanto postfijo como prefijo, que suelen colocarse pegados al operando al que modifican (lo más típico, por claridad), pero también pueden colocarse espaciados, a elección del programador.

int x = ++ i;int y = j++ + ++ x;

El espaciado vertical consiste en cuántas líneas ocupa una función miembro, o una clase, en el código fuente. En general, es útil autoimponerse un límite de una hoja por cada función miembro o   función.   Es   extraño,   siempre   que   la   función   miembro   no   consista   en   multitud   de   acciones repetitivas (como escribir en un archivo), que una función miembro bien diseñada ocupe más allá de un   folio.   En   caso   contrario,   será   conveniente   considerar   que   posiblemente   sea   interesante subdividirlo en varias subfunciones miembro (probablemente de acceso privado).

Además,   en   el   espaciado  vertical   intervienen   las   llaves  que   se   emplean  para  marcar   el comienzo y fin del cuerpo de una función, de un bucle, ... cada una de esas llaves puede llegar a 

Page 4: Guiaestilocpp

consumir una línea por sí  sola, incluso dos. Así,  para salvar algo de espacio vertical,  se suelen mantener las llaves de apertura y cierre, cada una en una línea, para el cuerpo de las funciones miembro y funciones, y en cambio colocar la llave de apertura en la misma línea para bucles y estructuras de decisión. Por ejemplo:

int dividir(int a, int b){

if ( b != 0 ) {return a / b;

}

return 0;}

... si bien en un programa real será mucho más adecuado responder a la contingencia que supone dividir un número entre cero, que responder con cero “silenciosamente”, lo cual supone un error no detectado.

La indentación es vital para poder comprender rápidamente los elementos funcionales de un programa. Así, el cuerpo (las instrucciones) de una función o función miembro debe estar alineado en su margen izquierdo más a la derecha que la cabecera de una función, de la misma forma que un bucle dentro de esa función y así sucesivamente. Por ejemplo:

int elevarA(int x, unsigned int y){

int toret = x;

for(; y > 1; ­­y) {toret *= x;

}

return toret;}

3 IdentificadoresLos identificadores se utilizan en varias (innumerables) ocasiones en programación: clases, datos miembro,   funciones  miembro,   constantes,   variables   locales   ...,   y   son   claves  para  entender   qué valores puede contener una variable, qué hace una función miembro o a qué objetos representa una clase. Por eso, hay que poner especial cuidado en su elección. También será interesante seguir unas pequeñas reglas a la hora de escribir un identificador que hagan que a su vez podamos obtener el máximo significado del mismo si se está leyendo en un listado.

En general,   los identificadores deben ser  tan cortos como sea posible,  pero a  la vez tan informativos como sea posible. Muchas veces, además, es imposible utilizar un solo sustantivo para nombrar una variable, función miembro o clase; en ese caso, se concatenarán todos para formar el identificador  final,  poniendo cada inicial  en mayúscula.  Si bien algunos  lenguajes modernos  lo permiten (como Java), a  través del soporte  unicode,  evítense los acentos,   las diéresis   ...  en los identificadores.

3.1 Identificadores de clasesLos identificadores de clases deberían corresponderse con sustantivos de la vida real o del concepto que se está modelando con el programa. Así, identificadores de clases pueden ser:  Casa,  Coche, Barco, Cuenta ...

El identificador de la clase, en caso de estar compuesto por más de una palabra, se construye 

Page 5: Guiaestilocpp

concatenando todas las palabras, y poniendo la inicial de cada una de estas palabras en mayúsculas. Por ejemplo: CuentaCorriente, VehiculoConMotor, VehiculoSinMotor.

class Rectangulo {// más cosas ...

};

class CuentaCorriente {// más cosas ...

};

3.2 Identificadores de dato miembrosLos identificadores de datos miembro siguen las mismas normas que los de las clases, pero con la primera inicial en minúscula. Así, por ejemplo, identificadores válidos son identificadorCompleto, precioEuros, ...

class CuentaCorriente {public:

// más cosas ...private:

double saldoEuros;};

3.3 Identificadores de objetosLos   identificadores   de   objetos   siguen   las   mismas   reglas   que   los   identificadores   de   los   datos miembro.

int main(void){

Circulo miCirculoGrande( 100 );

cout << miCirculoGrande.calcularArea() << endl;

return 0;}

3.4 Identificadores de funciones miembroLos identificadores de funciones miembro siguen  las  mismas reglas que para datos miembro y objetos. Sin embargo, deben escogerse de manera que sugieran de manera intuitiva qué hacen. Así, el identificador debe ser un verbo o al menos contener uno.

int getEdad(Persona);bool esPalindromo(const string &);

Evítensen identificadores como los siguientes:

int procesar(const ifstream &); // Mal: identificador no intuitivostring pasoAuxiliar(const string &); // Mal: identificador simplemente erróneovoid procesaYCuenta(); // Mal: Dividir en dos funciones

Cuando un identificador contiene una conjunción como  y, es signo inequívoco de que la función que  nombra   realiza más  de  una   tarea  y debe  ser  por   tanto  separada  en  dos   funciones separadas.

Los mejores identificadores son aquellos que describen con un identificador más corto lo 

Page 6: Guiaestilocpp

que hace la función miembro. Además, es interesante seguir ciertas pautas: en el caso de funciones miembro que devuelven un booleano (un valor verdadero o falso), es interesante nombrarlos con un prefijo formado por los verbos ser o estar, como:

esPalindromo(const string &);esPar(int);fueModificada(const Persona &) const;

3.5 Identificadores de variables locales en funciones miembroEn el caso de variables locales de funciones miembro, existen varias particularidades. Por ejemplo, a las variables locales empleadas en bucles se les suele asignar identificadores de una letra tipo 'i' y 'j',   también en el caso de algunos argumentos simples (si bien este caso es mejor, sin embargo, evitarlo, cuando sea posible, y asignar identificadores descriptivos) de funciones.

string cnvtMayusculas(string &s){

for(unsigned int i = 0; i < numCaracteres; ++i) {s[ i ] = toupper( s[ i ] );

}

return s;}

Los  identificadores  de  variables   también pueden  informar  sobre  para  qué   se  utiliza  esa variable,  y no restringirse a tan solo información sobre qué  valores alberga.  Por ejemplo,  en el código siguiente toret (a retornar) es una variable que se utiliza en todas las funciones para devolver un valor.

string obtenerConsonantes(const string &s){

static const string vocales = “aeiou”;string toret;

for(unsigned int i = 0; i < s.length(); ++i) {if ( vocales.find( s[ i ] ) == string::npos ) {

// No encontrado en vocales, añadirtoret += s[i];

}}

return toret;}

3.5.1 Declaración de variablesSe debe colocar cada variable en una línea, incluso siendo del mismo tipo. Se debe evitar especialmente declarar punteros y variables de un mismo tipo en la misma línea:

int x, y; // Es necesaria una segunda mirada para fijarse en yint x, *p;  // definitivamente desaconsejado

int main(){

int x;int y;char c;string toret;

// más cosas ...

Page 7: Guiaestilocpp

return 0;}

C++ permite  minimizar  muchísimo  ciertas   expresiones.  Por   ejemplo,   el   siguiente  bucle copiaría una cadena de caracteres de C (un vector de char), en otra de destino:

char *copiaCadena(char *ptrDestino, char *ptrOrigen){

while( *ptrDestino++ = *ptrOrigen++ );

return ptrDestino;}

La siguiente función realiza la misma tarea que la anterior. Sin embargo, está perfectamente claro cuándo el bucle termina (al llegar a la marca de fin de cadena de la cadena de origen), cuándo se hace explícitamente una copia y también cuando después se incrementan ambos contadores. Es una   función   que   se   puede   comprender   de   un   vistazo,   mientras   que   la   anterior,   aún   para   un programador con experiencia, supondrá invertir unos cuantos segundos. Finalmente, la calidad del código máquina generada es la misma en ambos casos, si  bien es cierto que el  primer ejemplo genera un número de instrucciones algo menor.

char *copiaCadena(char *ptrDestino, char *ptrOrigen){

while( *ptrOrigen != 0 ) {*ptrDestino = *ptrOrigen;++ptrOrigen;++ptrDestino;

}

return ptrDestino;}

4 ComentariosUn comentario   debe   ser   siempre   clarificador,  útil,   y,   en   cambio,   cuanto  más   corto  mejor.  En particular,   debe   cuidarse   en   no   insultar   la   inteligencia   del   lector   en   determinadas   ocasiones, comentando   secuencias   de   código   obvias   y   desesperarlo   al   encontrarse   con   construcciones complejas que no tienen ningún comentario.

int areaRectangulo = lado1 * lado2;  // calcula áreaareaCirculo = PI * r * r; // calcula área del círculo

      // PI es 3.1415927

En el   contexto  del  ejemplo  anterior,   el   tercer  comentario   es   absolutamente   innecesario, mientras   que   los   dos   primeros   son   cuestionables,   siempre   que   los   identificadores   hayan   sido escogidos cuidadosamente, como es el caso.

4.1 Comentarios de inicio de bloqueExisten dos tipos tipos básicos de comentarios, los que podríamos denominar comentarios encima, y   los   que   podríamos   denominar  comentarios   a   la   derecha.   Los   más   recomendables   son   los primeros, pues suelen explicar un bloque de código, a modo de párrafo, aclarando mucho la lectura. 

Page 8: Guiaestilocpp

string ListaObjetos::toString() const{

string toret;

// Obtener las descripciones de los objetosfor(unsigned int i = 0; i < v.size(); ++i) {

toret += v[ i ]­>toString() + '\n';}

// FormatearStringMan::trimCnvt( toret );StringMan::pasarMaysCnvt( toret );

return toret;}

4.2 Comentarios aclaratoriosLos comentarios  a la derecha  deben emplearse como mensajes aclaratorios, intentando mantener especialmente en ellos  la concisión, pues es fácil  que se alcancen rápidamente más de ochenta caracteres en esa línea. Agravando aún más este último problema, deben colocarse alejados del código que aclaran para que sean visibles.

// Cálculos previos al renderingareaRectangulo = lado1 * lado2  // en cms

4.3 Comentarios sobre funciones miembro o funcionesDesde   la   llegada   del   lenguaje   Java,   y   su   herramienta   de   documentación,  Javadoc,   se   han generalizado   los   comentarios   formateados   con   un   determinado   estilo,   y   aceptando   unos determinados   parámetros   de   documentación   embebidos   en   ellos.   Para   el   lenguaje   C++,   existe también un equivalente a Javadoc, la herramienta Doxygen1, de gran aceptación.

Así, cuando un comentario, en lugar de empezar por /*, comienza por /**, o un comentario de   línea,   en   lugar  de  empezar  por   //,   comienza  por   ///,   al  pasar   el   código  por   la  herramienta Doxygen, ésta genera la documentación recogida en varios formatos (incluyendo el HTML, que lo hace ideal para las referencias cruzadas). Estos comentarios también son útiles al desnudo, por lo que deben colocarse en las cabeceras (los ficheros con extensión .h, donde se declaran las clases; consúltese la sección sobre unidades de traducción), inmediatamente antes de cada clase, función miembro, o dato miembro. Son las cabeceras las que serán consultadas por el programador que utilice la clase para conocer su interfaz pública.

// rectangulo.h

/**La clase que representa a los rectángulos

*/class Rectangulo {public:

/** * Constructor de rectángulos * @param b La base del futuro rectángulo * @param a La altura del futuro rectángulo */Rectangulo(double b, double a);

1 Doxygen puede encontrarse en: http://www.doxygen.org

Page 9: Guiaestilocpp

/** * Calcula el área del rectángulo * @return El área del rectángulo, según sus lafos */double calcularArea();

private:/// La información sobre la base del rectángulodouble base;/// La información sobre la altura del rectángulodouble altura;

};

De los parámetros que se pueden utilizar en este tipo de comentarios, destacan @param y @return. El primero sirve para documentar un parámetro de una función miembro. tal y como se ve en el constructor de la clase Rectángulo de ejemplo, más arriba. El segundo sirve para documentar el   valor   de   retorno   de   una   función   miembro,   tal   y   como   se   aprecia   en   la   función   miembro calcularArea() de la clase del mismo ejemplo.

5 Disposición de elementos

5.1 ClasesDebe  escogerse  una   forma  de  disponer   los  miembros  en   la   clase,  y   tratar  de  mantenerla.  Por ejemplo, la más extendida consiste en colocar la parte pública de la clase lo más cercana a la parte superior. Ésto tiene sentido porque cuando trate de leer el código, al programador le interesa la parte pública de la clase, que es la que tendrá que manejar, y no la parte privada o protegida, de la que sólo tendrá que preocuparse si es el que la mantiene. Por tanto, un buen esquema es colocar primero los miembros públicos, después los protegidos, y finalmente los privados.

/// La clase que representa a los puntos de dos dimensionesclass Punto {public:

/// @return la coordenada horizontalint getX() const;

/// @return la coordenada verticalint getY() const;

private:int x;int y;

};

5.1.1 Unidades de traducciónEn terminología C++, el compilador traduce a código máquina unidades de traducción, que en el caso más habitual son ficheros con extensión .cpp (a veces, .cc), también conocidos como ficheros de implementación, ya que en ellos se definen las funciones y funciones miembro. Estos ficheros normalmente tienen asociados unos ficheros de cabecera (con extensión .h), donde se declaran los miembros públicos. Es muy importante la disposición de los prototipos de las funciones públicas y las   declaraciones   de   las   clases   públicas   en   las   cabeceras,   mientras   que   las   respectivas implementaciones deben aparecer en los ficheros de implementación. 

// punto.h

#ifndef _PUNTO_H_#define _PUNTO_H_

/// La clase que representa a los puntos de dos dimensionesclass Punto {public:

Page 10: Guiaestilocpp

/**  * Constructor de objetos punto  * @param a La coordenada horizontal  * @param b La coordenada vertical  */Punto(int a = 0, int b = 0)

{ x = a; y = b; }

/// @return la coordenada horizontalint getX(void) const;

/// @return la coordenada verticalint getY(void) const;

private:int x;int y;

};

extern const Punto puntoOrigen; // el origen de coordenadas

#endif

// punto.cpp#include “punto.h” 

const Punto PuntoOrigen;

int Punto::getX() const{

return x;}

int Punto::getY() const{

return y;}

La línea #ifndef _PUNTO_H_ y la siguiente lo que hacen es evitar que se compile la cabecera punto.h  más de una vez. Ésto puede suceder inadvertidamente en un proyecto complejo, cuando más de una cabecera incluye la primera. El diagnóstico de este problema es sencillo, pues en ese caso el compilador se quejaría de una redeclaración de la clase Punto.

Nótese que se ha incluido un objeto constante. Para poder ser utilizado debe ser declarado en el fichero de cabecera, si bien nunca definido (la cabecera puede ser incluida en varias unidades de traducción: poner la definición en la cabecera significaría que todas ellas tendrían un objeto puntoOrigen distinto, aunque con el mismo identificador, lo que provocaría un error de enlace). La definición, como puede verse, aparece en el fichero de implementación, mientras que en el fichero de   cabecera   aparece  una  declaración   con  extern,   de  manera  que   el   compilador   sepa  que   está definida en algún módulo (pero sólo uno).

Si, en cambio, se desea que un objeto no sea público, debe omitirse cualquier mención sobre él en la cabecera, y en el momento de la definición en el archivo de implementación debe añadirse el modificador static (que en este caso de aplicación significa privado, y no tiene, explícitamente, el mismo significado que cuando se aplica a una función miembro o a un dato miembro).

5.2 FuncionesEn   las   funciones,   se   debe   adoptar   como   esquema   básico   la   típica   estructura   inicialización­desarrollo­limpieza, es decir el comienzo de la tarea, inicializando (y creando, cuando sea oportuno) las variables necesarias, el desarrollo del problema, como el bucle del ejemplo situado más abajo,y finalmente, cuando es necesario, la limpieza de los recursos utilizados.

Page 11: Guiaestilocpp

int sumaFicheroDatos(const string &idFichero){

int num;int toret = 0;ifstream f( idFichero.c_str() );

if ( f.isopen() ) {f >> num;while( !f.eof() {

toret += num;f >> num;

}

f.close();}

return toret;}

5.2.1 Estructuras de decisión y repeticiónEs   típico   encontrarse   con   la   necesidad   de   tener   que   crear   estructuras   condicionales   (if)   o   de repetición   (while),   que   se   refieren   a   condiciones   complejas.  Debe   tratarse,   en   estos   casos,   de disponer una subcondición por línea, comenzando por el   juntor (and  (&&), or(||), y not(!)). Si es necesario,   una   subcondición   puede   llevar   un   comentario   "a   la   derecha".   Si   existen   varias subexpresiones condicionales, se deben indentar respecto a la expresión principal.

       if ( buffer != NULL         && f.isopen()         && tamMaxBufferDestino > 0 )

{while( !f.eof() ) {

// más cosas ...}

        if ( f.eof()            && bytesLeidos > 0 )

{memcpy( dest, buffer, tamMaxBufferDestino );

}}

Cuando se crean estructuras de decisión con más de una expresión condicional, no se debe utilizar la posibilidad de crear los cuerpos de instrucciones de una sola línea sin las llaves de inicio y fin de bloque ( { y } ), ya que la sentencia no sería visible.

if ( f.eof()            && bytesLeidos > 0 )

f.close();

5.2.1.1 Cuerpos de instrucciones de una sola línea

En general, es más legible evitar esta posibilidad. Las sentencias de un bloque sin llaves casi pasan desapercibidas, y por tanto es mejor cambiarlas por bloques siempre que sea posible. Recuérdese que   los   bloques  de  una   sola   línea   sin   llaves   de   comienzo   y   fin   son   una   posibilidad,   no  una obligación. En cuanto al espacio vertical, colocar la llave inicio en la misma línea de una sentencia de repetición o de decisión (siempre que no haya múltiples subexpresiones condicionales), es una medida que puede ser muy útil.

En el caso de una sentencia if­then­else con sólo una expresión condicional, se puede utilizar la posibilidad de los bloques sin llaves de comienzo y fin, por ejemplo:

Page 12: Guiaestilocpp

Persona * VectorPersonas::buscarPersona(const string &identificador){

unsigned int i;

for(i = 0; i < vPersonas.size();  ++i) {if ( vPersonas[ i ]­>getidentificador() == identificador ) {

break;}

}

if ( i < vPersonas.size() )return vPersonas[ i ];

else return NULL;}

5.3 EspaciadoEl espaciado se refiere a separar ciertos elementos (operadores y paréntesis, principalmente), de los argumentos para que el conjunto sea más legible. El uso típico consiste en separar con un espacio los argumentos en las listas de parámetros formales y de argumentos, los corchetes del operador de subíndice, y los operadores como =, *. /, + y ­. Por ejemplo, compárense los siguientes ejemplos:

int x=a+b;int x = a + b;

numRegistros=v.size()+registrosCabecera[i];numRegistros = v.size() + registrosCabecera[ i ];

int z=elevarA(x,y);int z = elevarA( x, y );

Estas normas de espaciado se han seguido a lo largo del presente documento.