Lenguaje C: Guía de Estilo Resumida

Anuncio
Lenguaje C: Guía de Estilo Resumida
Antón Gómez, octubre 2010
Introducción
Esta guía propone una serie de reglas básicas, estudiadas para favorecer la escritura de código
limpio y fácil de leer.
Para entender por qué es necesario observar ciertas normas a la hora de escribir software, es
importante ser consciente de las dimensiones que tienen los proyectos de software más o menos
complejos. Veamos algunos ejemplos cotidianos:
Aplicación
Descripción
Líneas de código
Windows XP
Sistema operativo completo
40 millones
Kernel de Linux
Núcleo de sistema operativo
8.4 millones
Subversion
Sistema de control de versiones
417 K
Google Chrome
Navegador web
1.5 millones (C++) 1.4 millones (C)
PHP
Motor de scripting para páginas web activas 800 K
The Gimp
Edición gráfica
VLC Media Player Reproductor multimedia
675 K
341 K (C), 93 K (C++)
Cuando un programa está compuesto por varios cientos de miles de líneas, o incluso varios
millones, es de suma importancia escribir el código de forma que sea muy sencillo de leer. De lo
contrario, es necesario invertir una enorme cantidad de esfuerzo (quien dice esfuerzo dice dinero)
para hacer cualquier modificación, por simple que parezca.
También hay que entender que el código normalmente se escribe una vez, pero se lee decenas de
veces: para buscar problemas, para entender cómo funciona antes de modificarlo, o para escribir
otros módulos que interactúan con él. La norma en la industria es que el código lo va a estar
leyendo constantemente gente que no participó en su escritura.
Organización del código
•
El código en C se organiza en ficheros .c y .h. Para cada fichero .c debe existir un .h con el
mismo nombre (list.c, list.h). A esta pareja de ficheros se la denomina módulo.
Los ficheros .c no son sacos de funciones. La clave para organizar el código en varios ficheros y no tener
problemas de dependencias cruzadas, es entender que lo que en lenguajes más modernos se maneja de manera
natural como "objetos", en C son los módulos.
Cada módulo (u "objeto" si se quiere) tiene una interfaz pública definida en su .h, y una serie de variables y
funciones privadas escondidas dentro del .c. Las declaraciones públicas se ponen en el .h para que otros
módulos puedan usarlas haciendo #include, y las privadas se quedan dentro del .c definidas como static para
que nadie las pueda usar desde fuera.
•
Cada fichero .c debe incluir a su propio .h (list.c haría #include de list.h).
•
Los ficheros .h deben contener sólo declaraciones públicas: tipos, constantes, variables
globales y prototipos de funciones diseñados para ser usados desde fuera del módulo. Todo
lo demás se mete en el .c
•
Todo fichero .h debe contener guardas para evitar inclusiones múltiples.
#ifndef _LIST_H_
#define _LIST_H_
... interfaz pública del módulo...
#endif /* _LIST_H_ */
•
Las variables globales de un módulo, deben definirse juntas al comienzo del fichero para
poder identificarlas de un vistazo. En ningún caso deben estar desperdigadas por el medio
del fichero.
Bien
Mal!
#include “param_db.h”
#include “param_db.h”
struct param_entry
{
char param[PARAM_MAX_LEN];
char value[VALUE_MAX_LEN];
char default[VALUE_MAX_LEN];
};
struct param_entry
{
char param[PARAM_MAX_LEN];
char value[VALUE_MAX_LEN];
char default[VALUE_MAX_LEN];
};
struct param_list
{
struct param_entry param;
struct list_node * next;
};
static struct param_list
{
struct param_entry param;
struct list_node * next;
} param_map;
/* Globals */
int error_code;
static struct param_list * param_map;
int error_code;
Identificadores
•
Los identificadores de variables y funciones deben ser cortos, descriptivos y concretos. Los
nombres de los ficheros también.
Bien
Mal!
struct tcp_header header;
bool is_enabled;
struct tcp_header b;
bool tmp;
int parse_xml_file(FILE * file);
void init_user_interface(void);
int open_xml_file_and_get_content(FILE * f);
void ui(void);
list.c
xml_parser.c
math.c
types.h
utils.c
code3.c
•
Los identificadores de variables y funciones deben escribirse en minúsculas y, si se
componen de varias palabras, cada palabra debe separarse mediante el caracter '_'.
•
Las macros y constantes deben escribirse en mayúsculas para distinguirlas de variables y
funciones.
Formato
•
El código se debe indentar para representar la estructura lógica del programa. Deben usarse
tabuladores para indentar, nunca espacios. Se recomienda configurar el editor de código para
visualizar el tabulador con 4 espacios.
•
Las llaves deben colocarse según el estándar Allman, también conocido como BSD, es decir,
en la línea siguiente a un if, o a un while, por ejemplo. Ver ejemplos al final de la sección.
•
El contenido de las funciones debe caber entero en pantalla. No debería ser necesario hacer
scroll para ver el contenido completo, aunque en ocasiones puede ser necesario extenderse.
En cualquier caso, nunca se deben escribir funciones que excedan el espacio de dos
pantallas.
•
Las líneas no deben pasar de la columna 80.
Ejemplo código bien formateado:
int db_sync(void)
{
int i, retval = 0, result = 0;
for (i = 0; i < P_SIZE; i++)
{
if (param_info[i].dirty && param_info[i].sync_cb)
{
retval = param_info[i].sync_cb(i, param_db[i]);
result |= retval;
}
if (retval == 0)
param_info[i].dirty = false;
else
{
LOG_WARNING(“No callback for param %d”, i);
}
}
}
return result;
Ejemplo de código mal formateado:
int db_sync(void)
{
int i, retval = 0, result = 0;
for (i = 0; i < P_SIZE; i++){
if (param_info[i].dirty && param_info[i].sync_cb) {
retval = param_info[i].sync_cb(i, param_db[i]);
result |= retval;
if (retval == 0)
param_info[i].dirty = false;
} else {
LOG_WARNING(“No callback for param %d”, i);
}
}
return result;
}
Uso del preprocesador
•
Deben usarse macros para definir tamaños de arrays de forma que sean fáciles de leer y
modificar. Es frecuente usarlas también para otro tipo de constantes en el código.
#define TIMEOUT_SECS 120
#define MAX_LINE_SIZE 80
…
char input_line[MAX_LINE_SIZE];
timer = set_timer(TIMEOUT_SECS);
Comentarios
•
Todas las funciones definidas en un fichero .c, ya sean públicas o privadas (static) deben
llevar un comentario encima que diga en una línea o dos qué es lo que hacen. También se
pueden añadir en este encabezado comentarios sobre particularidades del funcionamiento
general de la función.
/*
* db_sync()
* Synchronizes the internal database with the firmware files by storing
* modified parameters on permanent storage.
*
* Any parameter marked as "dirty" will be dumped by calling its associated
* sync callback.
*/
int db_sync(void)
{
•
Deben añadirse comentarios a partes no triviales del código. En muy raras ocasiones es
necesario comentar líneas individuales, pues si el código está bien escrito, no es necesario
repetir en un comentario lo que ya está escrito en C.
Bien
…
/* Call synchronization callback for parameters
* marked “dirty”. Clear dirty flag if callback
* succeedes. */
Mal!
…
if (param_info[i].dirty && param_info[i].sync_cb)
{
/* Call sync_cb */
retval = param_info[i].sync_cb(i, param_db[i]);
if (param_info[i].dirty && param_info[i].sync_cb)
result |= retval;
{
retval = param_info[i].sync_cb(i, param_db[i]);
if (retval == 0)
result |= retval;
{
/* Set dirty flag to false ª/
if (retval == 0)
param_info[i].dirty = false;
param_info[i].dirty = false;
}
}
}
Descargar