0% encontró este documento útil (0 votos)
60 vistas13 páginas

Guía C++

Descargar como pdf o txt
Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1/ 13

Guía de estilo para programar en C++

Actualizada en Enero de 2017

Esta es una guía de estilo para programar en C++. En una traducción adaptada de la guía de estilo del
Curso CS 106B: Programming Abstractions (C++) de los estudios de Computer Science de la Stanford
University (https://stanford.edu/class/archive/cs/cs106b/cs106b.1158/styleguide.shtml). En este
documento se han respetado buena parte de los ejemplos ilustrativos contenidos de la guía original.

Esta guía contiene una colección de reglas de estilo que conviene tener en cuenta al escribir código C++.
La calificación de los trabajos y exámenes de las asignaturas que recomienden el seguimiento de esta
guía se verá condicionada por esta circunstancia.

El objetivo de las reglas de estilo es favorecer la calidad del código diseñado y su legibilidad. Esta
colección no es exhaustiva y podrá verse enriquecida con nuevas reglas en un futuro. Las reglas tampoco
son totalmente rígidas. Su aplicación debe estar condicionada por el sentido común y las circunstancias
del programa que se está escribiendo.

En la mayoría de los entornos de trabajo profesional se sigue un estándar de estilo. Aprender a obedecer
cuidadosamente una guía de estilo y escribir código en equipo, junto con otros desarrolladores que
obedecen el mismo estándar de estilo, constituye una excelente práctica profesional que conviene
ejercitar desde la Universidad.

Índice de esta guía de estilo:

1. Espacios en blanco y sangrado del código


2. Identificadores y variables
3. Instrucciones básicas de C++
4. Redundancias
5. Eficiencia
6. Documentación y comentarios
7. Diseño de funciones y sus parámetros
8. Diseño de clases
1. Espacios en blanco y sangrado del código

Sangrado: Incrementar en una unidad el nivel de sangrado después de cada llave izquierda, {, y
disminuirlo en una unidad después de cada llave derecha, }. Un nivel de sangrado puede constar de 3 ó 4
caracteres o de un carácter tabulador.

Instrucciones por línea: No escriba más de una instrucción en cada línea.

// mal
int x = 3, y = 7; double z = 4.25; x++;
if (a == b) { foo(); }

// bien
int x = 3;
int y = 7;
double z = 4.25;

x++;
if (a == b) {
foo();
}

Longitud de las líneas: Ninguna línea debe superar los 100 caracteres. En tal caso la línea debe
descomponerse en dos o más y la segunda y, en su caso, las líneas sucesivas deben estar sangradas dos
niveles respecto a la primera. Por ejemplo:

int result = reallyLongFunctionOne() + reallyLongFunctionTwo() +


reallyLongFunctionThree() + reallyLongFunctionFour();

int result2 = reallyLongFunction(parameterOne, parameterTwo, parameterThree,


parameterFour, parameterFive, parameterSix);

Expresiones: Se debe escribir un espacio en blanco entre cada operador y sus operandos.

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

Líneas en blanco: Escribir una línea en blanco de separación entre dos funciones consecutivas.

void foo() {
...
}
// aquí hay una línea en blanco
void bar() {
...
}
2. Identificadores y variables

Identificadores: Deben darse a las variables nombres significativos, tales como firstName o
homeworkScore. Evitar identificadores de una sola letra tales como x o c, excepto para variables que
sean índices de bucles tales como i.

Mayúsculas: Los nombres de variables y de funciones han de comenzar con una letra minúscula,
likeThis, los nombres de clases con una letra mayúscula, LikeThis, y los nombres de constantes se
escriben todo en mayúsculas, sin ninguna minúscula, LIKE_THIS.

Ámbito: Cada variable hay que declararla en el ámbito más restringido posible. Si, por ejemplo, una
variable solo es usada dentro de una determinada instrucción if o while hay que declararla dentro de
dicha instrucción if o while y no al comienzo del código de la función o al comienzo del fichero.

Tipos: Elegir el tipo de dato apropiado para cada variable. Si una variable solo ha de almacenar valores
enteros, hay que declararla de tipo int y no de tipo double.

Priorizar el estilo C++ al escribir cadenas de caracteres respecto al estilo C: En la medida de lo


posible, para escribir cadenas de caracteres hacer uso del "estilo C++" y no del "estilo C".

// mal: estilo C
char* str = "Hello there";
// bien: estilo C++
char str[] = "Hello there";

Constantes: Un determinado valor se ha de declarar como una constante const si es utilizado


frecuentemente en el código o si conviene destacar su significado, y siempre se ha de hacer referencia a
dicho valor mediante el nombre de la constante.

const int VOTING_AGE = 18;

Evitar variables globales: No declarar nunca variables globales. Los únicos datos globales que se
pueden definir son los de ciertas constantes const. En vez de utilizar datos globales para comunicar
información entre funciones, se ha de pasar dicha información a través de sus parámetros y de los
valores que devuelven al ser invocadas.

// mal
int count; // variable global; mal!

void func1() {
count = 42;
}

void func2() {
count++;
}

int main() {
func1();
func2();
}
// mucho mejor
int func1() {
return 42;
}

void func2(int& count) {


count++;
}

int main() {
int count = func1();
func2(count);
}

3. Instrucciones básicas de C++

Priorizar el estilo C++ sobre el estilo C: Aunque C++ está basado en C, puede hablarse de un "estilo
C++ " y de un "estilo C" al utilizar determinadas construcciones. Por ejemplo, para escribir por la
consola del sistema, el "estilo C++ " hace uso del flujo de salida cout, mientras que el "estilo C " invoca
funciones tales como printf. Deberemos hacer uso del estilo "C++” siempre que sea posible.

// mal
printf("Hello, world!\n");

// bien
cout << "Hello, world!" << endl;

for vs while: Programaremos un bucle for cuando el número de repeticiones sea conocido a priori y
programaremos un bucle while cuando el número de repeticiones sea desconocido a priori.

// repetir exactamente 'size' veces


for (int i = 0; i < size; i++) {
...
}

// repetir hasta que no haya mas lineas


string str;
while (input >> str) {
...
}

break and continue: Evitaremos utilizar las instrucciones break or continue en bucles, excepto en los
casos en que ello sea imprescindible.

exit(0): Nunca haremos uso en nuestros programas de la función exit(0) que da por concluida de
forma inmediata la ejecución de un programa C++. Nuestros programas siempre concluirán su ejecución
al alcanzar el final de su función main.

Escribir siempre entre {} el código a ejecutar en instrucciones de control: Cuando programemos


instrucciones condicionales e iterativas tales como if/else, for, while, etc., escribiremos siempre el
código a ejecutar en cada caso entre un par de llaves {}, incluso si el código a ejecutar se limita a una
sola línea.

// mal
if (size == 0) return 0;
else
for (int i = 0; i < 10; i++) cout << "ok" << endl;
// bien
if (size == 0) {
return 0;
}
else {
for (int i = 0; i < 10; i++) {
cout << "ok" << endl;
}
}

Condiciones de una instrucción if/else: Debemos evitar una secuencia de instrucciones condicionales
if/else innecesarias y sustituirla por una única instrucción if/else. De este modo reduciremos el
número de condiciones a evaluar.

// mal (tres instrucciones condicionales)


if (grade >= 90) {
cout << "You got an A!";
}
if (grade >= 80 && grade < 90) {
cout << "You got a B!";
}
if (grade >= 70 && grade < 80) {
cout << "You got a C!";
}
...

// bien (una sola instrucción condicional)


if (grade >= 90) {
cout << "You got an A!";
}
else if (grade >= 80) {
cout << "You got a B!";
}
else if (grade >= 70) {
cout << "You got a C!";
}
...

Evitar cláusulas sin código en una instrucción if/else: Debemos evitar que la cualquiera de las
cláusulas de una instrucción if/else esté vacía de código a ejecutar.

// mal
if (a >= 0) {
// no hacer nada
}
else {
a = -a;
}
...

// bien
if (a < 0) {
a = -a;
}
...
Cálculo zen de valores booleanos (1): Debe evitarse programar una instrucción condicional if/else
que devuelva o asigne a una variable un valor de tipo bool en función del resultado de una determinada
condición. En tal caso se debe programar directamente la devolución o asignación del valor resultante de
evaluar la condición.

// mal
if (score1 == score2) {
return true; // Asignación de valor: v = true;
}
else {
return false; // Asignación de valor: v = false;
}

// bien
return score1 == score2; // Asignación de valor: v = score1 == score2;

Cálculo zen de valores booleanos (2): No escribir nunca una expresión booleana cuyo resultado sea
igual (==) o distinto (!=) a true o false.

// mal
if (x == true) {
...
}
else if (x != true) {
...
}

// bien
if (x) {
...
}
else {
...
}

4. Redundancias

Minimizar el código redundante: Si escribes el mismo código dos o más veces debes encontrar el
modo de que el código solo aparezca una vez y sea eliminado el código redundante. Se puede lograr, por
ejemplo, escribiéndolo en una función auxiliar que sea invocada cuantas veces sea necesario. Si el
código repetido es muy semejante, pero no idéntico, se puede intentar diseñar una función auxiliar con
parámetros que acepten los datos diferentes de cada porción de código repetida.

// mal
int aux = x;
x = y;
y = aux;
foo();
...
x = 10;
y++;
...
int temp = a;
a = b;
b = temp;
foo();
...
// bien
helper(x, y);
...
helper(a, b);
...

void helper (int& p, int& q) {


int r = p;
p = q;
q = r;
foo();
}

Factorización de una instrucción if/else: Reubicar el código común de las diferentes cláusulas de
una instrucción if/else de forma que se evite su repetición.

// mal
if (x < y) {
foo();
x++;
cout << "hi";
}
else {
foo();
y++;
cout << "hi";
}

// bien
foo();
if (x < y) {
x++;
}
else {
y++;
}
cout << "hi";

Estructura de las funciones: Una función cuyo código fuera a resultar muy largo, conviene
descomponerla en varias subfunciones. Aunque la definición de "muy largo " es imprecisa, diremos que
una función de más de 40 ó 50 líneas ya lo es. Estas 40 o 50 líneas constituyen un límite superior. No
obstante, si al especificar una función hay que utilizar muchas veces la conjunción “y” al describir sus
objetivos, es muy posible que la función deba hacer demasiadas cosas y convenga descomponerla en
subfunciones.
5. Eficiencia

Almacenar en variables los resultados de invocaciones a funciones costosas: Si se ha de invocar una


función y se ha de utilizar su resultado varias veces, conviene invocarla una sola vez, almacenar su
resultado en una variable y utilizar este valor cuantas veces sea preciso. Respetar esta recomendación es
esencial cuando el coste de ejecutar la función es elevado.

// mal
if (reallySlowSearchForIndex("abc") >= 0) {
remove(reallySlowSearchForIndex("abc"));
}

// bien
int index = reallySlowSearchForIndex("abc");
if (index >= 0) {
remove(index);
}

6. Documentación y comentarios

Comentarios en la cabecera de cada fichero: Escriba un comentario descriptivo en la parte superior de


cada fichero que describa el propósito del código de ese fichero, el nombre de sus programadores y la
fecha de la última actualización.

Citar fuentes: Si se han utilizado fuentes externas en el diseño de un programa (un libro, una página
web, otro programa, etc.), se deben citar dichas fuentes en el comentario del comienzo del fichero. Es
importante citar todas las fuentes relevantes utilizadas y reconocer el trabajo de sus correspondientes
autores.

Comentarios en la cabecera de cada función: Escriba, precediendo al nombre de cada función, un


comentario que describa su comportamiento y el papel que en él desempeñan, en su caso, sus parámetros
y, también en su caso, el valor que debe devolver la función tras ser ejecutada. Si una función debe
invocarse bajo determinadas condiciones, éstas deben quedar claramente descritas como precondición de
la función.

Comentarios en el código de una función. Si el código de una función presenta secciones largas,
complejas o no triviales, conviene incluir en estos puntos algún comentario que ayude a comprender qué
hace esa sección de código.

Detalles de la implementación. Los comentarios de cabecera de una función describen su


comportamiento y no deben entrar a detallar como está programada la función. No se deben mencionar
detalles a nivel del código como, por ejemplo, qué datos locales se van a declarar o el uso de
instrucciones condicionales if/else o de bucles while o for.
7. Diseño de funciones y sus parámetros

Buen diseño de una función: Un función bien diseñada presenta propiedades tales como las siguientes:

 Ejecuta una tarea única y coherente


 No se encarga de resolver más que una parte del trabajo, no debe pretende resolverlo todo
 Su diseño es independiente de otras funciones
 Almacena cada dato en el ámbito más restringido posible
 Contribuye a comprender y subdividir la estructura del programa global
 Contribuye a eliminar redundancias que, de otra forma, se presentarían en el programa global

Parámetros por valor vs parámetros por referencia: Los parámetros por referencia se han de utilizar
en los siguientes casos:

 siempre que sean parámetros de ‘salida’, es decir, cuando representen resultados de la función y
 cuando la función deba modificar el valor de la variable asociada al parámetro y
 cuando la función tenga que devolver varios resultados

No se deben definir parámetros por referencia cuando ello no sea necesario ni beneficioso. En el ejemplo
que sigue a, b, y c no son parámetros por referencia porque no es necesario que lo sean.

/*
* Solves a quadratic equation ax^2 + bx + c = 0,
* storing the results in output parameters root1 and root2.
* Assumes that the given equation has two real roots.
*/
void quadratic(double a, double b, double c,
double& root1, double& root2) {
double d = sqrt(b * b - 4 * a * c);
root1 = (-b + d) / (2 * a);
root2 = (-b - d) / (2 * a);
}

Parámetros de salida por referencia vs devolución de un resultado: Cuando el resultado de una


función es un valor simple, puede ser devuelto por ella mediante una instrucción return o asociado a
un parámetro definido por referencia. En general, se recomienda que sea devuelto por ella mediante una
instrucción return.

// mal
void max (int a, int b, int& result) {
if (a > b) {
result = a;
}
else {
result = b;
}
}

// bien
int max (int a, int b) {
if (a > b) {
return a;
}
else {
return b;
}
}
Paso de datos estructurados y de objetos por referencia: Cuando se transmite un dato estructurado
(definido como un registro struct) o un objeto no conviene hacerlo mediante un parámetro por valor,
sino mediante un parámetro por referencia, para evitar que el dato estructurado o el objeto deba ser
copiado en la función, lo cual resulta ineficiente:

// mal
void process(BankAccount account) {
...
}

void computeSomething(Vector<Foo> data) {


...
}

// bien
void process(BankAccount& account) {
...
}

void computeSomething(Vector<Foo>& data) {


...
}

Parámetros constantes (const) por referencia: Si hay que pasarle un dato estructurado (o un
objeto) a una función y su código no va a modificar su valor (su estado), entonces se recomienda pasarlo
como un parámetro constante (const) por referencia.

// mal
void display(BankAccount& account) {
...
}

// bien
void display(const BankAccount& account) {
...
}

Parámetros por referencias vs. punteros: Aunque se conozca el uso de punteros en C/C++, se
recomienda priorizar la definición de parámetros por referencia en lugar de definirlos como punteros.
Una razón de ello es porque un parámetro por referencia, a diferencia de un puntero, no puede tener el
valor NULL.

// mal
// accepts a pointer to an account
void process(BankAccount* account) {
...
}

// bien
// accepts a reference to an account
void process(BankAccount& account) {
...
}
Evitar llamadas ‘encadenadas’ donde cada función invocan a otra en una larga cadena sin devolver el
control de la ejecución a la función main. Conviene asegurarse que la función main es un resumen
conciso de la estructura global del programa. Esta idea se ilustra con los dos esquemas de invocaciones
que se presentan a continuación. En el primero cada función invoca a una función auxiliar antes de
finalizar su ejecución, mientras que en el segundo esquema se limita el ‘encadenamiento’ de llamadas
anidadas.

// bad
main
|
+-- function1
|
+-- function2
|
+-- function3
|
+-- function4
|
+-- function5
|
+-- function6

// good
main
|
+-- function1
|
+-- function2
| |
| +-- function3
| |
| +-- function4
|
+-- function5
| |
| +-- function6
8. Diseño de clases

Encapsulación: Deben encapsularse correctamente los objetos haciendo que cualquier campo de datos
de su clase sea definido en la sección private.

class Student {
private:
int homeworkScore;
...

Ficheros .h vs ficheros .cpp: Escribir siempre la declaración de una clase y de sus funciones en su
correspondiente fichero ClassName.h. Escribir la implementación de sus funciones en su
correspondiente fichero ClassName.cpp. Escribir siempre la declaración de una clase en su fichero .h
dentro de un bloque #ifndef/define/endif para evitar múltiples declaraciones de la misma clase en
un programa.

// Point.h
#ifndef _point_h
#define _point_h

class Point {
public:
Point (int x, int y);
int getX() const;
int getY() const;
void translate(int dx, int dy);

private:
int m_x;
int m_y;
};

#endif

// Point.cpp
#include "Point.h"

Point::Point(int x, int y) {
m_x = x;
m_y = y;
}

void Point::translate(int dx, int dy) {


m_x += dx;
m_y += dy;
}
...

Evitar atributos inecesarios; se debe hacer uso de los atributos de un objeto para almacenar datos del
estado del objeto pero no para almacenar valores temporales como, por ejemplo, los utilizados
únicamente en una única llamada a una función.

Funciones auxiliares: Cualquier función adicional de una clase que no forme parte de su especificación
se definirá como private para ocultarla y para que no pueda ser invocada desde una porción de código
exterior a la propia clase.

class Student {
...
private:
double computeTuitionHelper();
Funciones const: Una función de clase que no modifica el estado del objeto sobre el que es invocada
se ha de declarar como const.

class Student {
public:
int getID() const;
double getGPA(int year) const;
void payTuition(Course& course);
string toString() const;
...
 

También podría gustarte