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; } } }