Buenas prácticas de estilo en C y lectura efectiva de código

Capítulo 9

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Convenciones de formato: que el código “se lea solo”

El estilo no cambia lo que hace el programa, pero sí cambia cuánto tardas en entenderlo, corregirlo y ampliarlo. La meta es reducir la carga mental: que la estructura sea visible a simple vista y que los detalles importantes destaquen.

Indentación consistente

Elige un tamaño (por ejemplo, 4 espacios) y úsalo siempre. Evita mezclar tabuladores y espacios en el mismo proyecto, porque se ven distinto según el editor.

/* Bien: indentación uniforme y bloques claros */
if (condicion) {
    accion();
} else {
    otra_accion();
}

Llaves: siempre explícitas en bloques

En C es legal omitir llaves si el bloque tiene una sola sentencia, pero es una fuente común de errores al modificar el código. Una práctica segura es usar llaves siempre en if, else, for y while.

/* Riesgoso: al agregar una línea, se rompe la intención */
if (x > 0)
    y++;
    z++; /* Parece dentro del if, pero no lo está */
/* Mejor: intención inequívoca */
if (x > 0) {
    y++;
    z++;
}

Espacios: separa ideas, no caracteres

  • Un espacio después de comas: f(a, b, c).
  • Espacios alrededor de operadores binarios: a + b, x == y.
  • No pegues operadores a paréntesis: if (x > 0), no if(x>0).
  • Evita líneas excesivamente largas; si una expresión crece, considera extraer variables intermedias con nombres.
/* Difícil de escanear */
if((a+b*2>c)&&(d!=0||e<f)){
    g=h+i*j;
}

/* Más legible */
int umbral = a + b * 2;
int condicion_1 = (umbral > c);
int condicion_2 = (d != 0) || (e < f);

if (condicion_1 && condicion_2) {
    g = h + i * j;
}

Nombres: la primera capa de documentación

Un buen nombre reduce la necesidad de comentarios y evita malentendidos. Debe contestar: “¿qué representa?” y, si aplica, “¿en qué unidad o formato?”

Reglas prácticas para variables

  • Evita abreviaturas crípticas: cnt puede ser “count”, pero count es más claro.
  • Incluye unidad o contexto cuando ayude: timeout_ms, size_bytes, index, last_pos.
  • Prefiere nombres concretos: total ¿total de qué? Mejor total_items.
  • Evita nombres que se confundan con funciones: print como variable es mala idea.

Reglas prácticas para funciones

  • Verbo + objeto: parse_line, compute_checksum, find_max.
  • Describe efectos: si modifica un buffer, que se note: trim_in_place.
  • Evita funciones “multiusos” con nombres vagos: process, handle, do_stuff.

Convención de estilo de nombres

En C son comunes snake_case para funciones y variables, y UPPER_SNAKE_CASE para constantes simbólicas. Lo importante es la consistencia dentro del proyecto.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

/* Ejemplo de consistencia */
int max_retries = 3;
int retry_count = 0;

int compute_total_items(const int *items, int n);

#define BUFFER_SIZE 256

Comentarios útiles: explica el “por qué”, no el “qué”

El código ya dice “qué hace” (si está bien escrito). Los comentarios valiosos explican decisiones, supuestos, restricciones, casos borde y razones de diseño.

Malos comentarios (redundantes)

i++; /* incrementa i */
if (x == 0) { /* si x es 0 */
    ...
}

Buenos comentarios (contexto y decisiones)

/* Usamos >= para aceptar el límite exacto: el protocolo permite tamaño máximo inclusive. */
if (payload_size >= MAX_PAYLOAD) {
    return -1;
}

/* Evitamos división por cero: en este módulo, rate puede ser 0 cuando el sensor está desconectado. */
if (rate == 0) {
    return 0;
}

Comentario de cabecera de función (contrato)

Una plantilla simple que ayuda mucho es documentar: propósito, entradas, salidas, efectos secundarios, errores y supuestos.

/*
 * compute_average
 * Calcula el promedio entero de un arreglo.
 * - items: puntero válido a n enteros.
 * - n: cantidad de elementos (n >= 0).
 * Devuelve: promedio (0 si n == 0).
 * Nota: usa división entera (trunca).
 */
int compute_average(const int *items, int n) {
    ...
}

Patrones para leer código en C de forma efectiva

Leer código es una habilidad activa: haces hipótesis y las verificas. Estos patrones te ayudan a orientarte rápido, incluso en código ajeno.

1) Identifica entradas, salidas y efectos secundarios

Haz un mapa mental de:

  • Entradas: parámetros, variables globales usadas, lectura de archivos, lectura de stdin.
  • Salidas: valor de retorno, parámetros por puntero, escritura en buffers, escritura a archivos, impresión.
  • Efectos secundarios: modifica memoria apuntada, cambia estado global, reserva/libera memoria, altera un contador.

Guía paso a paso:

  • Lee la firma de la función y marca parámetros que son punteros (*) como posibles salidas.
  • Busca return para entender qué significa el valor devuelto.
  • Escanea asignaciones a *ptr, array[i] y variables globales.
int parse_int(const char *s, int *out_value) {
    /* Entrada: s; Salida: *out_value; Retorno: 0 ok, -1 error */
    ...
}

2) Sigue variables clave (las que “transportan” la lógica)

En muchos algoritmos hay 2–4 variables que explican casi todo: índices, acumuladores, punteros de recorrido, estados. Identifícalas y síguelas.

Guía paso a paso:

  • Localiza inicializaciones (dónde nacen).
  • Localiza actualizaciones (dónde cambian).
  • Localiza usos críticos (en condiciones, en accesos a arrays, en retornos).
int i = 0;          /* índice clave */
int sum = 0;        /* acumulador clave */
while (i < n) {
    sum += a[i];
    i++;
}

3) Localiza condiciones de borde

Los bugs suelen vivir en: cero elementos, uno solo, límites máximos, valores negativos, overflow, punteros nulos, cadenas vacías, fin de archivo.

Guía paso a paso:

  • Busca comparaciones con 0: n == 0, len == 0, ptr == NULL.
  • Busca límites: i < n vs i <= n, size - 1, MAX_*.
  • Busca retornos tempranos (return) y qué casos cubren.
if (n == 0) {
    return 0; /* borde: arreglo vacío */
}

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

4) Verifica invariantes del bucle

Un invariante es algo que debe ser cierto en cada iteración. Pensar en invariantes te permite detectar errores de lógica sin ejecutar el programa.

Ejemplo: recorrer un arreglo sin salirte.

/* Invariante: 0 <= i && i <= n */
for (i = 0; i < n; i++) {
    /* Antes de usar a[i], verifica mentalmente que i < n */
    sum += a[i];
}

Guía paso a paso para revisar un bucle:

  • Identifica el rango esperado de la variable de control (por ejemplo, i).
  • Comprueba que la condición del bucle mantiene ese rango.
  • Comprueba que la actualización acerca el bucle al fin (evita bucles infinitos).
  • Comprueba que los accesos a memoria usan índices/punteros dentro del rango.

Cómo pequeñas mejoras cambian la comprensión (antes/después)

Ejemplo 1: nombres y variables intermedias

Antes: se entiende, pero cuesta escanear y es fácil equivocarse al modificar.

int f(int *a, int n) {
    int i, s = 0;
    for (i = 0; i < n; i++) {
        if (a[i] > 0 && a[i] % 2 == 0) s += a[i];
    }
    return s;
}

Después: el propósito aparece en los nombres, y la condición se lee como una frase.

int sum_positive_even(const int *values, int count) {
    int i;
    int sum = 0;

    for (i = 0; i < count; i++) {
        int v = values[i];
        int is_positive = (v > 0);
        int is_even = (v % 2 == 0);

        if (is_positive && is_even) {
            sum += v;
        }
    }

    return sum;
}

Ejemplo 2: funciones cortas y responsabilidad única

Antes: una función hace demasiadas cosas; para entenderla hay que retener mucho estado mental.

int process(const char *s, int *out) {
    int i = 0;
    int sign = 1;
    int value = 0;

    if (!s || !out) return -1;

    while (s[i] == ' ' || s[i] == '\t') i++;
    if (s[i] == '-') { sign = -1; i++; }

    if (s[i] < '0' || s[i] > '9') return -1;

    while (s[i] >= '0' && s[i] <= '9') {
        value = value * 10 + (s[i] - '0');
        i++;
    }

    *out = sign * value;
    return 0;
}

Después: se separan pasos, se nombran decisiones y se facilita probar cada parte mentalmente.

static int is_space(char c) {
    return (c == ' ' || c == '\t');
}

static int is_digit(char c) {
    return (c >= '0' && c <= '9');
}

static int skip_spaces(const char *s, int i) {
    while (s[i] && is_space(s[i])) {
        i++;
    }
    return i;
}

int parse_signed_int(const char *s, int *out_value) {
    int i;
    int sign = 1;
    int value = 0;

    if (s == NULL || out_value == NULL) {
        return -1;
    }

    i = skip_spaces(s, 0);

    if (s[i] == '-') {
        sign = -1;
        i++;
    }

    if (!is_digit(s[i])) {
        return -1;
    }

    while (is_digit(s[i])) {
        value = value * 10 + (s[i] - '0');
        i++;
    }

    *out_value = sign * value;
    return 0;
}

Observa el cambio al leer: ahora puedes entender el flujo como una secuencia de pasos con nombres. Además, si aparece un bug, sabes dónde mirar: espacios, signo, dígitos, acumulación.

Lista de verificación de claridad (sin herramientas externas)

Usa esta lista como revisión manual antes de dar por “terminado” un archivo o una función.

Formato y estructura

  • ¿La indentación es consistente en todo el archivo?
  • ¿Todas las estructuras de control usan llaves?
  • ¿Hay espacios alrededor de operadores y después de comas?
  • ¿Las líneas largas se pueden simplificar con variables intermedias o funciones?

Nombres

  • ¿Los nombres de funciones describen una acción concreta (verbo + objeto)?
  • ¿Los nombres de variables indican significado y, si aplica, unidad/formato?
  • ¿Evitas nombres genéricos como tmp, data, val cuando hay alternativas mejores?
  • ¿Las constantes simbólicas tienen nombres claros y consistentes?

Comentarios

  • ¿Los comentarios explican decisiones y supuestos (el “por qué”)?
  • ¿Evitas comentar lo obvio (el “qué” literal)?
  • ¿Las funciones no triviales tienen un mini-contrato (entradas, salidas, errores)?

Lectura y razonamiento

  • ¿Puedes identificar rápidamente entradas, salidas y efectos secundarios?
  • ¿Las variables clave están claras y su actualización es fácil de seguir?
  • ¿Los casos borde están tratados de forma visible (retornos tempranos, validaciones)?
  • ¿En cada bucle puedes enunciar un invariante simple y verificar que se mantiene?
  • ¿La función hace una sola cosa? Si no, ¿puedes dividirla en funciones más pequeñas con nombres?

Ahora responde el ejercicio sobre el contenido:

Al revisar una función en C para entenderla rápidamente, ¿cuál estrategia ayuda más a mapear su comportamiento y posibles cambios en el estado del programa?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Una lectura efectiva comienza por ubicar entradas, salidas y efectos secundarios, y luego seguir variables que transportan la lógica. Esto reduce la carga mental y permite detectar errores (por ejemplo, en punteros, retornos y modificaciones de memoria) sin depender de suposiciones.

Siguiente capítulo

Errores típicos en C: warnings, desbordamientos y depuración básica

Arrow Right Icon
Portada de libro electrónico gratuitaFundamentos de programación en C desde cero: variables, control de flujo y funciones
82%

Fundamentos de programación en C desde cero: variables, control de flujo y funciones

Nuevo curso

11 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.