Prerrequisitos y Entorno Requerido¶
Para abordar y verificar las soluciones de este módulo, se requiere:
Entorno de Compilación: Compilador GCC 9+ o Clang bajo estándar estricto ISO C11 (
-std=c11 -Wall -Wextra -Werror -pedantic).Encapsulamiento y Tipos Opacos: Tipos incompletos (
typedef struct tipo tipo_t;) en cabeceras públicas, ocultando estructuras de representación en archivos fuente (.c).Contratos y Ciclo de Vida: Nomenclatura coherente con prefijo de módulo (
modulo_accion), ciclo de vida simétrico (crear/destruir), constantes inmutables (const) y códigos de estado de error tipificados.
Objetivos Pedagógicos y Competencias (Taxonomía de Bloom)¶
Nivel Cognitivo: Nivel 5 (Evaluar) a Nivel 6 (Crear).
Andamiaje Pedagógico Progresivo: Aprendizaje guiado desde la crítica de nombres y cohesión funcional hasta el diseño arquitectónico de bibliotecas en C11.
Competencias de Arquitectura: Diseño de interfaces minimalistas y cohesivas, patrón Handle con descriptores opacos e iteradores internos con contexto de usuario.
Rigor Técnico y Estabilidad: Garantía de encapsulamiento estricto, gestión de memoria sin fugas y verificación sistemática mediante suites ejecutables
assert().
Acerca de¶
Estos ejercicios abordan los criterios profesionales para el diseño y construcción de interfaces de programación de aplicaciones (APIs) modulares, seguras y extensibles en el lenguaje C.
Capítulos de Apunte Correspondientes¶
Principios de Nomenclatura y Claridad¶
Ejercicio 4.10.01 - Crítica de Nombres ⭐⭐☆☆☆¶
```{code-block} c :linenos: #include <stdbool.h> #include <stddef.h> // Definición de tipos de soporte para la API refactorizada typedef struct img img_t; typedef struct rgb_color { unsigned char r; unsigned char g; unsigned char b; } rgb_color_t; // Declaraciones públicas con prefijo coherente y const-correctness bool img_aplicar_filtro_grises(img_t *img); int img_obtener_luminancia_pixel(const img_t *img, size_t x, size_t y); bool img_actualizar_paleta(img_t *img, const rgb_color_t *paleta, size_t n); bool img_es_valida(const img_t *img); bool img_reparar_encabezado_corrupto(img_t *img); ```Ejercicio 4.10.02 - Diseño de Prefijos ⭐⭐☆☆☆¶
Diseñar esquemas de prefijos para estas librerías:
a) Librería de gestión de base de datos
b) Librería de compresión de archivos
c) Librería de validación de entrada
d) Librería de parseo JSON
Para cada una:
Proponer un prefijo corto pero descriptivo
Listar 5 funciones típicas con el prefijo aplicado
Justificar la elección del prefijo
Ejemplo:
// Librería de parseo JSON - prefijo: json_
json_parser_t *json_parser_crear(void);
json_value_t *json_parser_parse_string(json_parser_t *parser, const char *str);
bool json_value_es_objeto(const json_value_t *value);Ejercicio 4.10.03 - Consistencia en Familias de Funciones ⭐⭐☆☆☆¶
Completar estas familias de funciones con nombres consistentes:
1 2 3 4 5 6 7 8 9// Familia de funciones para manejo de archivos de configuración config_t *config_crear_vacio(void); config_t *config_ ? ? ? _desde_archivo(const char *ruta); // Cargar bool config_ ? ? ? _a_archivo(const config_t *cfg, const char *ruta); // Guardar ? ? ? // Obtener valor de clave ? ? ? // Establecer valor de clave ? ? ? // Verificar si existe una clave void config_destruir(config_t **ptr_config);
Ejercicio 4.10.04 - Nombres que Reflejan Comportamiento ⭐⭐☆☆☆¶
Renombrar estas funciones para reflejar mejor su comportamiento real:
1 2 3 4 5 6 7 8// La función busca y reemplaza, no solo busca char *buscar_texto(char *str, const char *patron, const char *reemplazo); // La función no solo valida, también normaliza bool validar_email(char *email); // La función no solo obtiene, calcula en tiempo real double obtener_promedio(const int *calificaciones, size_t n); // La función no agrega, inserta ordenadamente void agregar_elemento(lista_t *lista, int elemento);
Tipos Opacos y Encapsulamiento¶
Ejercicio 4.10.05 - Implementación de Tipo Opaco Básico ⭐☆☆☆☆¶
### 2.2: Ventajas del Tipo Opaco
Comparar dos versiones de una estructura:
**Versión A: Estructura expuesta**
```{code-block} c
:linenos:
// vector.h
typedef struct
{
int *datos;
size_t tamano;
size_t capacidad;
} vector_t;
void vector_init(vector_t *vec);
```
<!-- {code-block} c -->
**Versión B: Tipo opaco**
``` c
// vector.h
typedef struct vector vector_t;
vector_t *vector_crear(void);
```
<!-- c -->
**Preguntas conceptuales:**
1. ¿Qué puede hacer el usuario con la versión A que no puede con la B?
2. ¿Qué ventajas tiene B para el mantenedor de la librería?
3. Si querés agregar un campo `version` a la estructura, ¿cuál versión permite
hacerlo sin romper compatibilidad?
4. ¿Cuál es más eficiente en memoria? ¿Por qué?
### 2.3: Patrón Handle
Implementar un tipo opaco usando el patrón handle (identificador):
```{code-block} c
:linenos:
// archivo.h
typedef int archivo_handle_t;
#define ARCHIVO_HANDLE_INVALIDO (-1)
archivo_handle_t archivo_abrir(const char *ruta, const char *modo);
bool archivo_escribir(archivo_handle_t handle, const void *datos,
size_t bytes);
bool archivo_leer(archivo_handle_t handle, void *buffer, size_t bytes);
void archivo_cerrar(archivo_handle_t handle);
```
<!-- {code-block} c -->
**Tareas:**
1. Implementar la tabla interna de handles
2. Validar handles en cada operación
3. Manejar reutilización de handles cerrados
(ej_b4_c10_07)=
### Ejercicio 4.10.07 - Getters Inmutables y Encapsulamiento de Producto ⭐⭐☆☆☆
:::{exercise}
:label: ej_b4_c10_07_getters_producto
Implementá el acceso controlado mediante getters inmutables para un tipo opaco de producto:
```c
typedef struct producto producto_t;
producto_t *producto_crear_inmutable(const char *nombre, double precio, int stock);
void producto_destruir_inmutable(producto_t *p);
const char *producto_obtener_nombre(const producto_t *p);
double producto_obtener_precio(const producto_t *p);
int producto_obtener_stock(const producto_t *p);
```
Garantizá que `producto_obtener_nombre` retorne un puntero de solo lectura (`const char *`) y maneje punteros nulos de forma defensiva.
**Tabla de Vectores de Prueba:**
| Caso de Prueba | Parámetros Iniciales | Función Getter | Retorno Esperado |
| :--- | :--- | :--- | :--- |
| Producto estándar | `("Teclado", 4500.0, 10)` | `obtener_nombre` | `"Teclado"` |
| Producto estándar | `("Teclado", 4500.0, 10)` | `obtener_precio` | `4500.0` |
| Producto estándar | `("Teclado", 4500.0, 10)` | `obtener_stock` | `10` |
| Puntero nulo | `NULL` | Todos los getters | `""`, `0.0`, `0` |
::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
struct producto {
char nombre[64];
double precio;
int stock;
};
typedef struct producto producto_t;
producto_t *producto_crear_inmutable(const char *nombre, double precio, int stock) {
if (nombre == NULL) {
return NULL;
}
producto_t *p = malloc(sizeof(producto_t));
if (p == NULL) {
return NULL;
}
snprintf(p->nombre, sizeof(p->nombre), "%s", nombre);
p->precio = precio;
p->stock = stock;
return p;
}
void producto_destruir_inmutable(producto_t *p) {
free(p);
}
const char *producto_obtener_nombre(const producto_t *p) {
return (p != NULL) ? p->nombre : "";
}
double producto_obtener_precio(const producto_t *p) {
return (p != NULL) ? p->precio : 0.0;
}
int producto_obtener_stock(const producto_t *p) {
return (p != NULL) ? p->stock : 0;
}
int main(void) {
producto_t *prod = producto_crear_inmutable("Teclado Mecanico", 4500.5, 12);
assert(prod != NULL);
assert(strcmp(producto_obtener_nombre(prod), "Teclado Mecanico") == 0);
assert(producto_obtener_precio(prod) == 4500.5);
assert(producto_obtener_stock(prod) == 12);
/* Casos con puntero nulo */
assert(strcmp(producto_obtener_nombre(NULL), "") == 0);
assert(producto_obtener_precio(NULL) == 0.0);
assert(producto_obtener_stock(NULL) == 0);
producto_destruir_inmutable(prod);
producto_destruir_inmutable(NULL);
return 0;
}
```
---
(ej_b4_c10_08)=
### Ejercicio 4.10.08 - Iterador Interno con Contexto de Usuario en Tipo Opaco ⭐⭐⭐⭐☆
:::{exercise}
:label: ej_b4_c10_08_iterador_interno
:enumerator: diseno-api-8
En el diseño de bibliotecas en C, exponer arreglos internos rompe el encapsulamiento.
El patrón **Iterador Interno** (o *Visitor*) permite a los usuarios procesar cada elemento de una estructura opaca sin conocer su representación subyacente, recibiendo un puntero de contexto arbitrario (`void *contexto`) y permitiendo detener la iteración prematuramente retornando `false`:
```c
typedef struct conjunto conjunto_t;
typedef bool (*iterador_fn)(int elemento, void *contexto);
conjunto_t *conjunto_crear(void);
bool conjunto_agregar(conjunto_t *c, int elemento);
size_t conjunto_iterar(const conjunto_t *c, iterador_fn visitar, void *contexto);
void conjunto_destruir(conjunto_t *c);
```
Implementá la función `conjunto_iterar`, que invoca `visitar(elem, contexto)` para cada elemento. Si el callback retorna `false`, detiene la iteración de inmediato. Retorna la cantidad de elementos procesados.
**Nivel de Bloom:** Nivel 4 (Análisis) y Nivel 5 (Evaluación).
**Conceptos requeridos:** Punteros a función con clausura/contexto `void *`, tipos opacos, cortocircuito de recorrido.
**Techo conceptual:** Prohibido exponer la estructura interna o usar variables globales para compartir estado con el callback.
#### Contrato de la Función
- **Firma:** `size_t conjunto_iterar(const conjunto_t *c, iterador_fn visitar, void *contexto);`
- **Precondiciones:** `visitar != NULL`. Si `c == NULL`, retorna `0`.
- **Postcondiciones:** Retorna el número de elementos visitados antes de la detención o el final de la colección.
#### Tabla de Vectores de Prueba Obligatorios
| Colección | Elementos | Callback / Acción | Retorno `conjunto_iterar` | Estado del Contexto |
| :--- | :--- | :--- | :--- | :--- |
| `conjunto_t` | `{10, 20, 30}` | Sumar acumulador en `contexto` | `3` | Acumulador = `60` |
| `conjunto_t` | `{10, 20, 30, 40}` | Detener si `elemento == 20` | `2` | Cortocircuito ejecutado |
| `NULL` | - | Sumar acumulador | `0` | Acumulador intacto |
Solution to ej_b4_c10_08_iterador_interno
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125#include <assert.h> #include <stdbool.h> #include <stddef.h> #include <stdlib.h> typedef bool (*iterador_fn)(int elemento, void *contexto); struct conjunto { int *datos; size_t cantidad; size_t capacidad; }; typedef struct conjunto conjunto_t; conjunto_t *conjunto_crear(void) { conjunto_t *c = (conjunto_t *)malloc(sizeof(conjunto_t)); if (c == NULL) { return NULL; } c->datos = (int *)malloc(8 * sizeof(int)); if (c->datos == NULL) { free(c); return NULL; } c->cantidad = 0; c->capacidad = 8; return c; } bool conjunto_agregar(conjunto_t *c, int elemento) { if (c == NULL) { return false; } if (c->cantidad >= c->capacidad) { size_t nueva_cap = c->capacidad * 2; int *nuevos = (int *)realloc(c->datos, nueva_cap * sizeof(int)); if (nuevos == NULL) { return false; } c->datos = nuevos; c->capacidad = nueva_cap; } c->datos[c->cantidad++] = elemento; return true; } size_t conjunto_iterar(const conjunto_t *c, iterador_fn visitar, void *contexto) { if (c == NULL || visitar == NULL) { return 0; } size_t procesados = 0; for (size_t i = 0; i < c->cantidad; i++) { procesados++; if (!visitar(c->datos[i], contexto)) { break; } } return procesados; } void conjunto_destruir(conjunto_t *c) { if (c != NULL) { free(c->datos); free(c); } } static bool acumular_suma(int elemento, void *contexto) { int *total = (int *)contexto; *total += elemento; return true; } static bool detener_en_veinte(int elemento, void *contexto) { int *contador = (int *)contexto; (*contador)++; return (elemento != 20); } int main(void) { conjunto_t *c = conjunto_crear(); assert(c != NULL); conjunto_agregar(c, 10); conjunto_agregar(c, 20); conjunto_agregar(c, 30); // Iteración completa acumulando suma int suma = 0; size_t visitados = conjunto_iterar(c, acumular_suma, &suma); assert(visitados == 3); assert(suma == 60); // Iteración con cortocircuito conjunto_agregar(c, 40); int cuenta = 0; size_t cortados = conjunto_iterar(c, detener_en_veinte, &cuenta); assert(cortados == 2); assert(cuenta == 2); // Manejo defensivo assert(conjunto_iterar(NULL, acumular_suma, &suma) == 0); conjunto_destruir(c); return 0; }
Ejercicio 4.10.09 - Patrón Handle con Tabla Interna de Descriptores Opacos ⭐⭐⭐⭐☆¶
Solution to Exercise api-09
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94#include <stdio.h> #include <stdbool.h> #include <string.h> #include <assert.h> #define MAX_SESIONES 4 #define MAX_NOMBRE 32 #define HANDLE_INVALIDO (-1) typedef int handle_sesion_t; typedef struct { bool ocupado; char usuario[MAX_NOMBRE]; } ranura_sesion_t; static ranura_sesion_t tabla_sesiones[MAX_SESIONES] = {0}; handle_sesion_t sesion_abrir(const char *usuario) { if (usuario == NULL || strlen(usuario) == 0 || strlen(usuario) >= MAX_NOMBRE) { return HANDLE_INVALIDO; } for (int i = 0; i < MAX_SESIONES; i++) { if (!tabla_sesiones[i].ocupado) { tabla_sesiones[i].ocupado = true; strncpy(tabla_sesiones[i].usuario, usuario, MAX_NOMBRE - 1); tabla_sesiones[i].usuario[MAX_NOMBRE - 1] = '\0'; return i; } } return HANDLE_INVALIDO; } bool sesion_es_valida(handle_sesion_t h) { if (h < 0 || h >= MAX_SESIONES) { return false; } return tabla_sesiones[h].ocupado; } const char *sesion_obtener_usuario(handle_sesion_t h) { if (!sesion_es_valida(h)) { return NULL; } return tabla_sesiones[h].usuario; } bool sesion_cerrar(handle_sesion_t h) { if (!sesion_es_valida(h)) { return false; } tabla_sesiones[h].ocupado = false; tabla_sesiones[h].usuario[0] = '\0'; return true; } int main(void) { // 1. Apertura exitosa handle_sesion_t h1 = sesion_abrir("alice"); assert(h1 != HANDLE_INVALIDO); assert(sesion_es_valida(h1) == true); assert(strcmp(sesion_obtener_usuario(h1), "alice") == 0); handle_sesion_t h2 = sesion_abrir("bob"); handle_sesion_t h3 = sesion_abrir("charlie"); handle_sesion_t h4 = sesion_abrir("david"); assert(h2 != HANDLE_INVALIDO && h3 != HANDLE_INVALIDO && h4 != HANDLE_INVALIDO); // 2. Tabla saturada (capacidad 4) handle_sesion_t h5 = sesion_abrir("eve"); assert(h5 == HANDLE_INVALIDO); // 3. Cierre y reutilización de slot assert(sesion_cerrar(h2) == true); assert(sesion_es_valida(h2) == false); assert(sesion_obtener_usuario(h2) == NULL); // Re-cierre debe fallar (idempotencia y detección de use-after-free) assert(sesion_cerrar(h2) == false); // Ahora eve puede ingresar en el slot liberado handle_sesion_t h_eve = sesion_abrir("eve"); assert(h_eve == h2); assert(strcmp(sesion_obtener_usuario(h_eve), "eve") == 0); // Limpieza final sesion_cerrar(h1); sesion_cerrar(h3); sesion_cerrar(h4); sesion_cerrar(h_eve); return 0; }
3: Contratos de Interfaz y Precondiciones¶
3.1: Documentación de Contratos¶
Documentar completamente el contrato de estas funciones:
1 2 3 4 5 6 7 8 9 10 11 12 13/** * [COMPLETAR DOCUMENTACIÓN] */ double vector_producto_punto(const double *v1, const double *v2, size_t n); /** * [COMPLETAR DOCUMENTACIÓN] */ int *arreglo_buscar_binario(int *arr, size_t n, int target); /** * [COMPLETAR DOCUMENTACIÓN] */ void cadena_dividir(const char *str, char delim, char ***resultado, size_t *count);
Para cada función, especificar:
Propósito: ¿Qué hace la función?
Precondiciones: ¿Qué debe garantizar el llamador?
Poscondiciones: ¿Qué garantiza la función al retornar?
Parámetros: Descripción de cada parámetro
Retorno: Qué representa el valor de retorno
Notas: Responsabilidad de memoria, complejidad, etc.
3.2: Validación de Precondiciones¶
Para cada función, decidir si debe validar precondiciones o asumirlas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21// Opción 1: Asumir precondiciones (más rápido, menos seguro) double promedio(const int *arr, size_t n) { // ¿Verificar arr != NULL y n > 0? int suma = 0; for (size_t i = 0; i < n; i++) { suma += arr[i]; } return (double)suma / n; } // Opción 2: Validar precondiciones (más lento, más seguro) double promedio_seguro(const int *arr, size_t n, double *resultado) { if (arr == NULL || n == 0) { return false; } // ... return true; }
Preguntas:
¿Cuándo elegir una sobre la otra?
¿Cómo documentar la decisión?
¿Qué hacer en funciones críticas de performance?
3.3: Invariantes de Estructura¶
Definir e implementar una función de verificación de invariantes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14typedef struct { int *datos; size_t tamano; size_t capacidad; } vector_t; /** * Verifica los invariantes del vector. * Invariantes: * 1. Si capacidad > 0, entonces datos != NULL * 2. tamano <= capacidad * 3. [AGREGAR MÁS] */ bool vector_verificar_invariantes(const vector_t *vec);
Usar esta función en modo debug:
#ifdef DEBUG
#define VERIFICAR_INVARIANTES(vec) assert(vector_verificar_invariantes(vec))
#else
#define VERIFICAR_INVARIANTES(vec) ((void)0)
#endif3.4: Contratos con Aliasing¶
Analizar problemas de aliasing en contratos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14/** * Copia n bytes de src a dest. * * PRECONDICIÓN: ??? * ¿Qué pasa si src y dest se solapan? */ void mi_memcpy(void *dest, const void *src, size_t n); /** * Mueve n bytes de src a dest, manejando solapamiento. * * PRECONDICIÓN: ??? * ¿Cómo especificar que puede manejar solapamiento? */ void mi_memmove(void *dest, const void *src, size_t n);
4: Manejo de Errores¶
4.1: Comparación de Estrategias¶
Implementar la misma funcionalidad con tres estrategias de error:
Estrategia 1: Valor especial de retorno
int lista_buscar_indice(const lista_t *lista, int valor)
{
// Retornar -1 si no se encuentra
// ¿Qué pasa si la búsqueda falla por lista == NULL?
}Estrategia 2: Código de error + parámetro de salida
1 2 3 4 5 6 7 8typedef enum { LISTA_OK, LISTA_ERROR_NULL, LISTA_ERROR_NO_ENCONTRADO } lista_error_t; lista_error_t lista_buscar_indice_v2(const lista_t *lista, int valor, size_t *indice);
Estrategia 3: errno global
extern int lista_errno;
#define LISTA_EXITO 0
#define LISTA_EPTR 1
#define LISTA_ENOTFOUND 2
int lista_buscar_indice_v3(const lista_t *lista, int valor);Análisis:
Ventajas y desventajas de cada enfoque
¿Cuál es thread-safe?
¿Cuál es más compatible con código existente?
Ejercicio 4.10.06 - Conversión de Códigos de Error a Cadenas ⭐⭐☆☆☆¶
### 4.3: Propagación de Errores
Implementar una función que llama a otras y debe propagar errores:
```{code-block} c
:linenos:
typedef enum
{
DB_OK = 0,
DB_ERROR_CONEXION,
DB_ERROR_CONSULTA,
DB_ERROR_NO_ENCONTRADO,
DB_ERROR_MEMORIA
} db_error_t;
/**
* Busca un usuario por ID, actualiza su email y guarda cambios.
*
* Esta función puede fallar en múltiples puntos:
* 1. Buscar el usuario
* 2. Validar el email
* 3. Guardar cambios
*
* ¿Cómo propagar cada error al caller?
*/
db_error_t db_usuario_actualizar_email(db_t *db, int user_id,
const char *nuevo_email);
```
<!-- {code-block} c -->
### 4.4: Logging de Errores
Implementar un sistema de logging interno que registre errores sin afectar la
API pública:
```{code-block} c
:linenos:
// Función interna, no expuesta en el .h
static void log_error_interno(const char *funcion, const char *mensaje);
// Uso:
lista_error_t lista_agregar(lista_t *lista, int valor)
{
if (lista == NULL)
{
log_error_interno(__func__, "puntero NULL recibido");
return LISTA_ERROR_NULL;
}
// ...
}
```
<!-- {code-block} c -->
**Pregunta:** ¿Cómo permitir que el usuario configure el nivel de logging sin
modificar la API?
## 5: Gestión de Memoria en APIs
### 5.1: Quién Libera Qué
Analizar y documentar la responsabilidad de memoria en estas funciones:
```{code-block} c
:linenos:
// Caso 1: ¿Quién libera el string retornado?
char *persona_obtener_nombre_completo(const persona_t *p);
// Caso 2: ¿Quién libera el arreglo? ¿Y los strings dentro?
char **archivo_leer_lineas(const char *ruta, size_t *num_lineas);
// Caso 3: ¿La función toma ownership del buffer?
void logger_set_buffer(logger_t *log, char *buffer, size_t tamano);
// Caso 4: ¿El caller debe pre-alojar? ¿Cuánto espacio necesita?
bool fecha_formatear(const fecha_t *f, char *buffer, size_t buffer_size);
```
<!-- {code-block} c -->
Para cada caso, especificar claramente en la documentación quién es responsable
de liberar memoria.
### 5.2: Patrón de Constructor/Destructor
Implementar el par constructor/destructor para una estructura compleja:
```{code-block} c
:linenos:
typedef struct biblioteca biblioteca_t;
/**
* Crea una biblioteca vacía.
*
* @return Puntero a biblioteca, o NULL si falla la asignación.
*
* RESPONSABILIDAD: El caller debe liberar con biblioteca_destruir().
*/
biblioteca_t *biblioteca_crear(void);
/**
* Destruye una biblioteca y libera toda la memoria asociada.
*
* Incluye:
* - Todos los libros agregados
* - Todas las categorías
* - Índices internos
*
* @param ptr_bib Puntero a puntero. Se pondrá en NULL después de liberar.
*
* PRECONDICIÓN: ptr_bib != NULL
* POSCONDICIÓN: *ptr_bib == NULL
*/
void biblioteca_destruir(biblioteca_t **ptr_bib);
```
<!-- {code-block} c -->
**Implementación:** Crear un módulo completo con varios niveles de memoria
dinámica.
### 5.3: Buffers Pre-alocados vs. Asignación Interna
Comparar dos diseños para una función de serialización:
**Diseño A: Caller provee buffer**
```{code-block} c
:linenos:
/**
* Serializa un objeto a JSON en el buffer provisto.
*
* @param buffer Buffer pre-alocado por el caller
* @param buffer_size Tamaño del buffer
* @param bytes_escritos Número de bytes escritos (puede ser NULL)
* @return true si exitoso, false si el buffer es muy pequeño
*/
bool objeto_a_json(const objeto_t *obj, char *buffer, size_t buffer_size,
size_t *bytes_escritos);
```
<!-- {code-block} c -->
**Diseño B: Función aloja memoria**
```{code-block} c
:linenos:
/**
* Serializa un objeto a JSON, asignando memoria dinámicamente.
*
* @return String JSON asignado dinámicamente, o NULL si falla.
*
* RESPONSABILIDAD: El caller debe liberar con free().
*/
char *objeto_a_json_alloc(const objeto_t *obj);
```
<!-- {code-block} c -->
**Análisis:**
- ¿Cuándo usar cada diseño?
- ¿Impacto en performance?
- ¿Facilidad de uso?
### 5.4: Pool de Memoria
Diseñar una API que use un pool de memoria personalizado:
```{code-block} c
:linenos:
typedef struct arena arena_t;
// API del arena
arena_t *arena_crear(size_t tamano_inicial);
void *arena_alloc(arena_t *arena, size_t bytes);
void arena_reset(arena_t *arena); // Libera todo de golpe
void arena_destruir(arena_t *arena);
// API que usa el arena
typedef struct parser parser_t;
parser_t *parser_crear_con_arena(arena_t *arena);
nodo_ast_t *parser_parse(parser_t *p, const char *codigo);
// Todos los nodos del AST se alojan en el arena
```
<!-- {code-block} c -->
**Ventaja:** Liberación masiva en $O(1)$ sin memory leaks.
## 6: Evolución y Versionado de APIs
### 6.1: Versionado Semántico
Clasificar estos cambios según SemVer (mayor.menor.parche):
a) Agregar un nuevo parámetro opcional al final de una función
b) Cambiar el tipo de retorno de `int` a `long`
c) Corregir un bug que causaba crash
d) Agregar una nueva función a la API
e) Cambiar el significado de un código de error
f) Optimizar una función sin cambiar comportamiento
g) Renombrar un parámetro (solo en el .h)
h) Deprecar una función pero mantenerla funcional
**Respuestas:**
- ¿Cuáles requieren incrementar versión MAJOR?
- ¿Cuáles solo MINOR?
- ¿Cuáles solo PATCH?
### 6.2: Compatibilidad Binaria (ABI)
Analizar qué cambios rompen compatibilidad binaria (ABI):
```{code-block} c
:linenos:
// Versión 1.0
typedef struct
{
int x;
int y;
} punto_t;
void punto_imprimir(punto_t p);
```
<!-- {code-block} c -->
**Cambios propuestos:**
```{code-block} c
:linenos:
// Cambio A: Agregar campo al final
typedef struct
{
int x;
int y;
int z; // NUEVO
} punto_t;
// Cambio B: Cambiar orden de campos
typedef struct
{
int y; // Intercambiado
int x; // Intercambiado
} punto_t;
// Cambio C: Cambiar firma de función
void punto_imprimir(punto_t p, bool con_formato); // Parámetro nuevo
// Cambio D: Cambiar de valor a puntero
void punto_imprimir(punto_t *p); // Ahora puntero
```
<!-- {code-block} c -->
**Pregunta:** ¿Cuáles rompen ABI? ¿Por qué?
### 6.3: Deprecación Gradual
Implementar la deprecación de una función antigua:
```{code-block} c
:linenos:
// viejo_api.h (versión 1.x)
void config_load(const char *filename); // Versión antigua
// nuevo_api.h (versión 2.x)
typedef enum
{
CONFIG_OK,
CONFIG_ERROR_IO,
CONFIG_ERROR_PARSE
} config_error_t;
config_error_t config_cargar_desde_archivo(const char *ruta);
// Wrapper de compatibilidad
#ifdef CONFIG_DEPRECATION_WARNINGS
#warning "config_load está deprecada, usar config_cargar_desde_archivo"
#endif
static inline void config_load(const char *filename)
{
config_cargar_desde_archivo(filename);
// Ignora errores para mantener comportamiento original
}
```
<!-- {code-block} c -->
### 6.4: Feature Flags
Diseñar un sistema de feature flags para habilitar funcionalidad experimental:
```{code-block} c
:linenos:
// compilar con -DCONFIG_FEATURE_X para habilitar
#ifdef CONFIG_FEATURE_EXPERIMENTAL_CACHE
/**
* [EXPERIMENTAL] Habilita caché de resultados.
* Esta función puede cambiar o ser removida en futuras versiones.
*/
void lista_habilitar_cache(lista_t *lista);
#endif
```
<!-- {code-block} c -->
## 7: Testing de APIs
### 7.1: Diseñar para Testabilidad
Refactorizar este código difícil de testear:
```{code-block} c
:linenos:
// Difícil de testear: lee directamente de stdin
void procesar_comandos(void)
{
char buffer[100];
while (fgets(buffer, sizeof(buffer), stdin))
{
ejecutar_comando(buffer);
}
}
```
<!-- {code-block} c -->
**Refactorización testeable:**
```{code-block} c
:linenos:
// Inyección de dependencia: acepta FILE*
void procesar_comandos_desde(FILE *stream)
{
char buffer[100];
while (fgets(buffer, sizeof(buffer), stream))
{
ejecutar_comando(buffer);
}
}
// Wrapper conveniente para el caso común
static inline void procesar_comandos(void)
{
procesar_comandos_desde(stdin);
}
```
<!-- {code-block} c -->
**Test:**
```{code-block} c
:linenos:
void test_procesar_comandos(void)
{
FILE *test_input = fopen("test_input.txt", "r");
procesar_comandos_desde(test_input);
fclose(test_input);
// Verificar resultados
}
```
<!-- {code-block} c -->
### 7.2: Mock Objects
Diseñar una interfaz que permita inyectar mocks para testing:
```{code-block} c
:linenos:
// Interfaz de operaciones de archivo
typedef struct
{
void *(*abrir)(const char *ruta);
int (*leer)(void *handle, void *buffer, size_t bytes);
void (*cerrar)(void *handle);
} archivo_ops_t;
// API principal acepta operaciones personalizadas
typedef struct procesador procesador_t;
procesador_t *procesador_crear(const archivo_ops_t *ops);
// Implementación real
extern const archivo_ops_t archivo_ops_real;
// Mock para testing
extern const archivo_ops_t archivo_ops_mock;
```
<!-- {code-block} c -->
### 7.3: Tests de Contrato
Escribir tests que verifiquen el contrato de esta función:
```{code-block} c
:linenos:
/**
* Busca un elemento en un arreglo ordenado.
*
* PRECONDICIÓN: arr ordenado ascendentemente
* POSCONDICIÓN: Si retorna i >= 0, entonces arr[i] == target
*
* @return Índice del elemento, o -1 si no se encuentra
*/
int buscar_binario(const int *arr, size_t n, int target);
```
<!-- {code-block} c -->
**Tests a implementar:**
```{code-block} c
:linenos:
void test_buscar_binario_elemento_presente(void);
void test_buscar_binario_elemento_ausente(void);
void test_buscar_binario_arreglo_vacio(void);
void test_buscar_binario_primer_elemento(void);
void test_buscar_binario_ultimo_elemento(void);
// ¿Qué otros casos?
```
<!-- {code-block} c -->
### 7.4: Property-Based Testing
Definir propiedades que deben cumplirse siempre:
```{code-block} c
:linenos:
// Propiedad: ordenar es idempotente
void property_ordenar_idempotente(void)
{
int arr[100];
generar_arreglo_aleatorio(arr, 100);
int copia1[100], copia2[100];
memcpy(copia1, arr, sizeof(arr));
memcpy(copia2, arr, sizeof(arr));
ordenar(copia1, 100);
ordenar(copia2, 100);
// Propiedad: ordenar dos veces da mismo resultado que una vez
assert(memcmp(copia1, copia2, sizeof(arr)) == 0);
}
// Propiedad: buscar después de insertar debe encontrar el elemento
void property_insertar_buscar(void)
{
lista_t *lista = lista_crear();
int valor_aleatorio = rand();
lista_insertar(lista, valor_aleatorio);
assert(lista_contiene(lista, valor_aleatorio));
lista_destruir(&lista);
}
```
<!-- {code-block} c -->
## 8: Patrones de Diseño de APIs
### 8.1: Patrón Builder
Implementar el patrón builder para configuración compleja:
```{code-block} c
:linenos:
typedef struct http_request_builder http_request_builder_t;
typedef struct http_request http_request_t;
// API fluida (cada función retorna el builder)
http_request_builder_t *http_request_builder_crear(void);
http_request_builder_t *http_request_builder_metodo(http_request_builder_t *b,
const char *metodo);
http_request_builder_t *http_request_builder_url(http_request_builder_t *b,
const char *url);
http_request_builder_t *http_request_builder_header(http_request_builder_t *b,
const char *clave,
const char *valor);
http_request_builder_t *http_request_builder_body(http_request_builder_t *b,
const char *body);
// Construir el request final
http_request_t *http_request_builder_build(http_request_builder_t *b);
// Uso:
// http_request_t* req = http_request_builder_crear()
// ->metodo("POST")
// ->url("http://example.com")
// ->header("Content-Type", "application/json")
// ->build();
```
<!-- {code-block} c -->
### 8.2: Patrón Iterator
Diseñar un iterador genérico para una colección:
```{code-block} c
:linenos:
typedef struct lista lista_t;
typedef struct lista_iter lista_iter_t;
// API del iterador
lista_iter_t *lista_iter_crear(const lista_t *lista);
bool lista_iter_tiene_siguiente(const lista_iter_t *iter);
int lista_iter_siguiente(lista_iter_t *iter);
void lista_iter_destruir(lista_iter_t *iter);
// Uso:
lista_iter_t *iter = lista_iter_crear(lista);
while (lista_iter_tiene_siguiente(iter))
{
int valor = lista_iter_siguiente(iter);
printf("%d\n", valor);
}
lista_iter_destruir(iter);
```
<!-- {code-block} c -->
**Pregunta:** ¿Qué pasa si la lista se modifica durante la iteración?
### 8.3: Patrón Factory
Implementar una factory para crear diferentes tipos de objetos:
```{code-block} c
:linenos:
typedef enum
{
PARSER_JSON,
PARSER_XML,
PARSER_YAML
} parser_tipo_t;
typedef struct parser parser_t;
// Factory
parser_t *parser_crear(parser_tipo_t tipo);
// Interfaz común
bool parser_parse_string(parser_t *p, const char *str);
valor_t *parser_obtener_valor(parser_t *p, const char *clave);
void parser_destruir(parser_t *p);
```
<!-- {code-block} c -->
**Implementación:** Usar punteros a funciones internamente para polimorfismo.
### 8.4: Patrón Callback
Diseñar una API con callbacks para procesamiento asíncrono:
```{code-block} c
:linenos:
typedef void (*evento_callback_t)(void *contexto, const evento_t *evento);
typedef struct event_loop event_loop_t;
event_loop_t *event_loop_crear(void);
void event_loop_registrar_callback(event_loop_t *loop, evento_tipo_t tipo,
evento_callback_t callback, void *contexto);
void event_loop_ejecutar(event_loop_t *loop);
void event_loop_detener(event_loop_t *loop);
void event_loop_destruir(event_loop_t *loop);
```
<!-- {code-block} c -->
## 9: APIs Thread-Safe
### 9.1: Análisis de Thread-Safety
Analizar si estas funciones son thread-safe:
```{code-block} c
:linenos:
// Función 1: Variable estática
int contador_global(void)
{
static int count = 0;
return count++;
}
// Función 2: errno global
int archivo_leer_linea(FILE *f, char *buffer, size_t size)
{
if (fgets(buffer, size, f) == NULL)
{
archivo_errno = ARCHIVO_ERROR_IO;
return -1;
}
return 0;
}
// Función 3: Solo lectura
int arreglo_buscar(const int *arr, size_t n, int target)
{
for (size_t i = 0; i < n; i++)
{
if (arr[i] == target)
return i;
}
return -1;
}
// Función 4: Mutex interno
void log_mensaje(const char *msg)
{
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
fprintf(log_file, "%s\n", msg);
pthread_mutex_unlock(&mutex);
}
```
<!-- {code-block} c -->
### 9.2: Diseño Thread-Safe
Convertir esta API no thread-safe a thread-safe:
```{code-block} c
:linenos:
// Versión NO thread-safe
typedef struct cache cache_t;
cache_t *cache_crear(void);
void cache_insertar(cache_t *c, const char *clave, const char *valor);
const char *cache_buscar(cache_t *c, const char *clave);
void cache_destruir(cache_t *c);
```
<!-- {code-block} c -->
**Opciones de diseño:**
1. Mutex interno (transparente pero menos flexible)
2. Lock/unlock explícito (más control para el usuario)
3. Estructuras inmutables (lock-free)
### 9.3: Thread-Local Storage
Usar TLS para error handling thread-safe:
```{code-block} c
:linenos:
// En lugar de errno global
__thread int mi_lib_errno = 0;
#define mi_lib_get_error() (mi_lib_errno)
#define mi_lib_set_error(err) (mi_lib_errno = (err))
// Uso en funciones
int mi_funcion(void)
{
if (error)
{
mi_lib_set_error(MI_LIB_ERROR_XYZ);
return -1;
}
return 0;
}
```
<!-- {code-block} c -->
### 9.4: Documentar Garantías de Concurrencia
Documentar las garantías de thread-safety:
```{code-block} c
:linenos:
/**
* [THREAD-SAFE] Puede ser llamada concurrentemente desde múltiples threads.
* Usa locking interno.
*/
void logger_log(logger_t *logger, const char *mensaje);
/**
* [NOT THREAD-SAFE] El caller debe sincronizar accesos.
* Esta función modifica estado compartido sin locking.
*/
void cache_limpiar(cache_t *cache);
/**
* [REENTRANT] Puede ser llamada recursivamente de forma segura.
* No usa variables estáticas ni globales.
*/
int factorial(int n);
```
<!-- {code-block} c -->
## 10: Performance y Optimización
### 10.1: API Zero-Copy
Diseñar una API que minimice copias de datos:
```{code-block} c
:linenos:
// Malo: múltiples copias
char *archivo_leer_todo(const char *ruta); // Copia 1: archivo → buffer interno
char *procesar_texto(const char *texto); // Copia 2: buffer → nuevo buffer
// Bueno: zero-copy con buffer compartido
typedef struct buffer buffer_t;
buffer_t *buffer_desde_archivo(const char *ruta); // Mapea archivo
const char *
buffer_obtener_datos(const buffer_t *b); // Acceso directo, sin copia
size_t buffer_obtener_tamano(const buffer_t *b);
void buffer_liberar(buffer_t *b);
```
<!-- {code-block} c -->
### 10.2: API para Operaciones Batch
Diseñar API que permita operaciones en lote para mejor performance:
```{code-block} c
:linenos:
// Ineficiente: múltiples llamadas
for (int i = 0; i < n; i++)
{
db_insertar(db, ®istros[i]); // Overhead por llamada
}
// Eficiente: operación batch
db_insertar_batch(db, registros, n); // Una sola transacción
```
<!-- {code-block} c -->
Implementar:
```{code-block} c
:linenos:
typedef struct db db_t;
typedef struct registro registro_t;
// Operaciones individuales
bool db_insertar(db_t *db, const registro_t *reg);
bool db_actualizar(db_t *db, int id, const registro_t *reg);
bool db_eliminar(db_t *db, int id);
// Operaciones batch
bool db_insertar_batch(db_t *db, const registro_t *registros, size_t count);
bool db_eliminar_batch(db_t *db, const int *ids, size_t count);
```
<!-- {code-block} c -->
### 10.3: Lazy Evaluation
Implementar evaluación perezosa en una API:
```{code-block} c
:linenos:
typedef struct consulta consulta_t;
typedef struct resultado resultado_t;
// Construir consulta (sin ejecutar)
consulta_t *consulta_crear(db_t *db, const char *tabla);
consulta_t *consulta_filtrar(consulta_t *q, const char *campo,
const char *valor);
consulta_t *consulta_ordenar(consulta_t *q, const char *campo);
consulta_t *consulta_limitar(consulta_t *q, size_t limite);
// Ejecutar solo cuando se necesita
resultado_t *consulta_ejecutar(consulta_t *q); // Aquí ocurre el trabajo real
// Uso:
resultado_t *res = consulta_crear(db, "usuarios")
->filtrar("edad", "> 18")
->ordenar("nombre")
->limitar(10)
->ejecutar(); // Solo aquí se ejecuta la consulta
```
<!-- {code-block} c -->
### 10.4: Profiling-Friendly API
Diseñar puntos de instrumentación en la API:
```{code-block} c
:linenos:
typedef struct stats stats_t;
// API con estadísticas opcionales
cache_t *cache_crear_con_stats(size_t capacidad, stats_t *stats);
// Si stats != NULL, la cache registra métricas
typedef struct
{
uint64_t hits;
uint64_t misses;
uint64_t evictions;
double hit_rate;
} cache_stats_t;
void cache_obtener_stats(const cache_t *c, cache_stats_t *stats);
void cache_resetear_stats(cache_t *c);
```
<!-- {code-block} c -->
## 11: Internacionalización y Localización
### 11.1: Mensajes de Error Localizables
Diseñar un sistema de mensajes de error localizables:
```{code-block} c
:linenos:
typedef enum
{
MSG_ERROR_MEMORIA,
MSG_ERROR_ARCHIVO_NO_ENCONTRADO,
MSG_ERROR_PERMISO_DENEGADO,
// ...
} mensaje_id_t;
// Configurar idioma
void set_locale(const char *locale); // "es_AR", "en_US", etc.
// Obtener mensaje traducido
const char *obtener_mensaje(mensaje_id_t id);
// Uso en API
int archivo_abrir(const char *ruta)
{
if (acceso_denegado)
{
fprintf(stderr, "%s: %s\n",
obtener_mensaje(MSG_ERROR_PERMISO_DENEGADO), ruta);
return -1;
}
}
```
<!-- {code-block} c -->
### 11.2: Separación de Lógica y Presentación
Refactorizar para separar lógica de UI:
```{code-block} c
:linenos:
// Malo: mezcla lógica con presentación
void procesar_pedido(pedido_t *p)
{
if (validar_pedido(p))
{
printf("Pedido válido!\n"); // ¡Hardcoded en español!
guardar_pedido(p);
}
else
{
printf("Error: pedido inválido\n");
}
}
// Bueno: retornar código, caller decide cómo presentar
typedef enum
{
PEDIDO_OK,
PEDIDO_ERROR_VALIDACION,
PEDIDO_ERROR_BD
} pedido_resultado_t;
pedido_resultado_t procesar_pedido(pedido_t *p);
// Caller decide presentación
pedido_resultado_t res = procesar_pedido(pedido);
if (res == PEDIDO_OK)
{
ui_mostrar_mensaje(MSG_PEDIDO_EXITOSO);
}
else
{
ui_mostrar_error(mensaje_para_resultado(res));
}
```
<!-- {code-block} c -->
## 12: Documentación de APIs
### 12.1: Documentación Completa
Escribir documentación completa para este módulo:
```{code-block} c
:linenos:
/**
* @file cola.h
* @brief [COMPLETAR]
*
* [COMPLETAR: Descripción general del módulo]
*
* Ejemplo de uso:
* @code
* [AGREGAR EJEMPLO]
* @endcode
*
* @author [Tu nombre]
* @version 1.0
* @date 2024-10-24
*/
#ifndef COLA_H
#define COLA_H
/**
* @brief [COMPLETAR]
*/
typedef struct cola cola_t;
/**
* @brief [COMPLETAR]
*
* @return [COMPLETAR]
*
* @pre [COMPLETAR]
* @post [COMPLETAR]
*
* @note [COMPLETAR]
*/
cola_t *cola_crear(void);
// [COMPLETAR DOCUMENTACIÓN DE TODAS LAS FUNCIONES]
#endif
```
<!-- {code-block} c -->
### 12.2: README y Guía de Uso
Escribir un README completo para tu librería:
**Secciones a incluir:**
1. Descripción y propósito
2. Instalación
3. Quick start con ejemplo mínimo
4. Referencia completa de API
5. Ejemplos avanzados
6. FAQ
7. Changelog
8. Licencia
### 12.3: Documentación Inline
Agregar comentarios inline útiles (no obvios):
```{code-block} c
:linenos:
// Malo: comentario obvio
i++; // Incrementar i
// Bueno: explica decisión no obvia
// Usamos búsqueda lineal en lugar de binaria porque
// el arreglo tiene < 10 elementos típicamente, y
// benchmark muestra que es más rápido por overhead reducido
for (int i = 0; i < n; i++)
{
if (arr[i] == target)
return i;
}
// Bueno: advierte sobre caso especial
// IMPORTANTE: buffer debe tener al menos MAX_PATH + 1 bytes
// para incluir el null terminator
obtener_ruta_absoluta(archivo, buffer);
```
<!-- {code-block} c -->
## 13: Casos de Estudio
### 13.1: Análisis de API Real - `fopen`
Analizar el diseño de `fopen`:
``` c
FILE *fopen(const char *filename, const char *mode);
```
<!-- c -->
**Preguntas:**
1. ¿Por qué retornar puntero opaco en lugar de estructura?
2. ¿Por qué `mode` es string y no enum?
3. ¿Cómo reporta errores?
4. ¿Es thread-safe?
5. ¿Qué ventajas tiene este diseño?
6. ¿Qué desventajas?
7. Si pudieras rediseñarla hoy, ¿qué cambiarías?
### 13.2: Análisis de API Real - `malloc`
Analizar el diseño de `malloc`:
``` c
void *malloc(size_t size);
void free(void *ptr);
```
<!-- c -->
**Preguntas:**
1. ¿Por qué retornar `void*`?
2. ¿Por qué `free` no retorna código de error?
3. ¿Qué pasa si llamo `free(NULL)`?
4. ¿Por qué no hay `malloc_safe` que inicializa a cero?
5. Comparar con `calloc` y `realloc`
### 13.3: Rediseño de API Problemática
Esta API tiene varios problemas. Identificarlos y proponer mejoras:
```{code-block} c
:linenos:
// api_problematica.h
struct datos
{ // ¿Por qué no typedef?
int *array;
int size;
int capacity;
};
void init(struct datos *d); // ¿Malloc o no?
int add(struct datos *d, int x); // ¿Qué significa el int retornado?
void process(); // ¿Dónde están los parámetros?
char *get_result(); // ¿Quién libera?
```
<!-- {code-block} c -->
**Tareas:**
1. Listar todos los problemas
2. Proponer API mejorada
3. Justificar cada cambio
### 13.4: Comparación SQLite vs. MySQL
Comparar las APIs de estas dos librerías de base de datos:
**SQLite (todo en uno):**
``` c
sqlite3 *db;
sqlite3_open("db.sqlite", &db);
sqlite3_exec(db, "SELECT * FROM users", callback, NULL, NULL);
sqlite3_close(db);
```
<!-- c -->
**MySQL (orientada a objetos):**
``` c
MYSQL *conn = mysql_init(NULL);
mysql_real_connect(conn, host, user, pass, db, 0, NULL, 0);
MYSQL_RES *result = mysql_query(conn, "SELECT * FROM users");
mysql_free_result(result);
mysql_close(conn);
```
<!-- c -->
**Análisis:**
- Ventajas y desventajas de cada estilo
- ¿Cuál es más fácil de usar?
- ¿Cuál es más flexible?
- ¿Cuál maneja errores mejor?
## 14: Ejercicios Integradores
### 14.1: Diseñar Librería Completa
Diseñar una librería completa de manejo de grafos con:
**Requisitos:**
1. Tipos opacos para grafos y nodos
2. Soporte para grafos dirigidos y no dirigidos
3. Operaciones básicas (agregar nodo, agregar arista, eliminar, buscar)
4. Algoritmos (BFS, DFS, camino más corto)
5. Manejo de errores consistente
6. Documentación completa
7. Tests unitarios
**Entregables:**
- `grafo.h` (interfaz pública)
- `grafo.c` (implementación)
- `test_grafo.c` (tests)
- `README.md` (documentación)
### 14.2: Refactorizar Código Legacy
Dado este código legacy, refactorizar aplicando todos los principios:
```{code-block} c
:linenos:
// legacy_code.c
int data[MAX];
int count = 0;
void add(int x)
{
data[count++] = x;
}
int get(int i)
{
return data[i];
}
void print()
{
for (int i = 0; i < count; i++)
{
printf("%d ", data[i]);
}
}
```
<!-- {code-block} c -->
**Mejoras a implementar:**
- Tipo opaco
- Manejo de errores
- Chequeo de límites
- API clara y documentada
- Thread-safety opcional
- Tests
### 14.3: API REST en C
Diseñar una librería para cliente HTTP REST:
```{code-block} c
:linenos:
typedef struct http_client http_client_t;
typedef struct http_response http_response_t;
// Crear cliente con configuración
http_client_t *http_client_crear(const char *base_url);
void http_client_set_timeout(http_client_t *client, int segundos);
void http_client_set_header(http_client_t *client, const char *clave,
const char *valor);
// Operaciones REST
http_response_t *http_get(http_client_t *client, const char *endpoint);
http_response_t *http_post(http_client_t *client, const char *endpoint,
const char *body);
http_response_t *http_put(http_client_t *client, const char *endpoint,
const char *body);
http_response_t *http_delete(http_client_t *client, const char *endpoint);
// Acceso a respuesta
int http_response_get_status(const http_response_t *resp);
const char *http_response_get_body(const http_response_t *resp);
const char *http_response_get_header(const http_response_t *resp,
const char *clave);
// Limpieza
void http_response_destruir(http_response_t *resp);
void http_client_destruir(http_client_t *client);
```
<!-- {code-block} c -->
## 15: Preguntas Conceptuales
### 15.1: Filosofía de Diseño
**Pregunta:** ¿Cuál es mejor diseño y por qué?
**Opción A: Simple pero menos seguro**
``` c
void buffer_append(buffer_t *buf, const char *str);
// Asume que buf != NULL, undefined behavior si no
```
<!-- c -->
**Opción B: Seguro pero más verboso**
``` c
bool buffer_append(buffer_t *buf, const char *str)
{
if (buf == NULL || str == NULL)
return false;
// ...
return true;
}
```
<!-- c -->
**Respuesta esperada:** Depende del contexto (librería pública vs. código
interno, performance crítica vs. no, etc.)
### 15.2: Abstracción vs. Control
**Pregunta:** Una API de alto nivel es fácil de usar pero menos flexible. Una de
bajo nivel es potente pero compleja. ¿Cómo resolver este trade-off?
**Respuesta esperada:** Múltiples niveles de API, como hace Git con
porcelain/plumbing.
### 15.3: Estabilidad vs. Innovación
**Pregunta:** Mantener compatibilidad hacia atrás limita la evolución de la API.
¿Cómo balancear estabilidad e innovación?
**Respuestas esperadas:**
- Versionado mayor para cambios breaking
- Deprecación gradual
- Nuevas funciones con sufijos `_v2`
- Branches separadas (stable/experimental)
### 15.4: Zero-cost Abstraction
**Pregunta:** ¿Qué significa "zero-cost abstraction"? ¿Es posible en C?
**Respuesta esperada:** Abstracción que no agrega overhead en runtime. En C es
posible mediante inline functions y macros.
### 15.5: Defensive Programming
**Pregunta:** ¿Hasta qué punto debe una función ser defensiva validando entrada?
**Contextos diferentes:**
- Librería pública expuesta a usuarios externos
- Código interno dentro de un mismo programa
- Función de performance crítica en un lazo
**Respuesta esperada:** Varía según contexto, documentar asunciones claramente.
## 16: Ejercicios de Código
### 16.1: Implementación Mínima
Implementar la versión más simple posible de:
```{code-block} c
:linenos:
typedef struct map map_t;
map_t *map_crear(void);
bool map_insertar(map_t *m, const char *clave, int valor);
bool map_buscar(map_t *m, const char *clave, int *valor);
bool map_eliminar(map_t *m, const char *clave);
size_t map_tamano(const map_t *m);
void map_destruir(map_t **ptr_map);
```
<!-- {code-block} c -->
**Requisitos mínimos:**
- Tipo opaco
- Manejo de errores básico
- Documentación de contratos
- No memory leaks
### 16.2: Macro Safety
Convertir estas macros inseguras en funciones inline seguras:
```{code-block} c
:linenos:
// Macro insegura
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// Problema: evalúa argumentos múltiples veces
// Función inline segura
static inline int max_int(int a, int b)
{
return (a > b) ? a : b;
}
```
<!-- {code-block} c -->
**Ejercicio:** Hacer lo mismo para:
``` c
#define SQUARE(x) ((x) * (x))
#define ABS(x) ((x) < 0 ? -(x) : (x))
#define SWAP(a, b) \
{ \
int tmp = a; \
a = b; \
b = tmp; \
}
```
<!-- c -->
### 16.3: Wrapper Generation
Crear un sistema de wrappers automáticos para logging:
```{code-block} c
:linenos:
// Original
int funcion_original(int x, int y);
// Wrapper generado automáticamente
int funcion_original_logged(int x, int y)
{
LOG_ENTRADA("funcion_original", "x=%d, y=%d", x, y);
int resultado = funcion_original(x, y);
LOG_SALIDA("funcion_original", "resultado=%d", resultado);
return resultado;
}
```
<!-- {code-block} c -->
**Tarea:** Crear macros o scripts que generen estos wrappers.
## 17: Auditoría de API
### 17.1: Checklist de Revisión
Crear un checklist completo para revisar una API:
**Nomenclatura:**
- [ ] Todas las funciones tienen prefijo consistente
- [ ] Nombres descriptivos y claros
- [ ] Convención de nombres (snake_case)
- [ ] ...
**Contratos:**
- [ ] Todas las funciones públicas están documentadas
- [ ] Precondiciones especificadas
- [ ] Poscondiciones especificadas
- [ ] ...
**Errores:**
- [ ] Estrategia de errores consistente
- [ ] Códigos de error documentados
- [ ] ...
### 17.2: Revisión de Pares
Intercambiar código con un compañero y revisar mutuamente las APIs diseñadas,
usando el checklist del ejercicio anterior.
## 18: Preguntas de Diseño
### 18.1: ¿Puntero o Valor?
Para cada caso, decidir si pasar/retornar por valor o por puntero:
```{code-block} c
:linenos:
// Estructura pequeña (8 bytes)
typedef struct
{
int x;
int y;
} punto_t;
// ¿Cuál es mejor?
void punto_trasladar_v1(punto_t p, int dx, int dy); // Por valor
void punto_trasladar_v2(punto_t *p, int dx, int dy); // Por puntero
// Estructura grande (>64 bytes)
typedef struct
{
char nombre[256];
int datos[1000];
} registro_t;
// ¿Cuál es mejor?
void procesar_v1(registro_t reg); // Por valor
void procesar_v2(const registro_t *reg); // Por puntero
```
<!-- {code-block} c -->
### 18.2: ¿Callback o Polling?
Diseñar API para operaciones asíncronas: ¿usar callbacks o polling?
**Opción A: Callback**
``` c
typedef void (*completado_callback_t)(void *contexto, int resultado);
void operacion_async(int param, completado_callback_t callback,
void *contexto);
```
<!-- c -->
**Opción B: Polling**
``` c
typedef struct operacion operacion_t;
operacion_t *operacion_iniciar(int param);
bool operacion_completada(const operacion_t *op);
int operacion_obtener_resultado(operacion_t *op);
```
<!-- c -->
**Análisis:** Ventajas y desventajas de cada enfoque.
### 18.3: ¿Mutable o Inmutable?
Comparar diseños mutables vs. inmutables:
```{code-block} c
:linenos:
// Diseño mutable
typedef struct string string_t;
void string_append(string_t *str, const char *texto);
void string_mayuscula(string_t *str);
// Diseño inmutable
string_t *string_append(const string_t *str, const char *texto);
string_t *string_mayuscula(const string_t *str);
// Cada operación retorna nueva string, original sin modificar
```
<!-- {code-block} c -->
**Pregunta:** ¿Cuál preferir y cuándo?
### 18.4: ¿Herencia o Composición?
Simular herencia vs. composición en C:
```{code-block} c
:linenos:
// Herencia (via casting)
typedef struct
{
int tipo; // "vtable" manual
} figura_t;
typedef struct
{
figura_t base;
double radio;
} circulo_t;
// Composición
typedef struct
{
punto_t centro;
double radio;
} circulo_v2_t;
```
<!-- {code-block} c -->
## 19: Ejercicios Avanzados
### 19.1: Plugin System
Diseñar un sistema de plugins dinámicos:
```{code-block} c
:linenos:
typedef struct plugin plugin_t;
typedef struct plugin_api plugin_api_t;
// API que el plugin debe implementar
struct plugin_api
{
const char *nombre;
const char *version;
bool (*inicializar)(void);
void (*procesar)(void *datos);
void (*finalizar)(void);
};
// Sistema de carga de plugins
plugin_t *plugin_cargar(const char *ruta_so);
const plugin_api_t *plugin_get_api(plugin_t *p);
void plugin_descargar(plugin_t *p);
```
<!-- {code-block} c -->
### 19.2: Reflection System
Implementar un sistema básico de reflexión:
```{code-block} c
:linenos:
// Metadatos de tipos
typedef enum
{
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING,
TYPE_STRUCT
} tipo_id_t;
typedef struct
{
const char *nombre;
tipo_id_t tipo;
size_t offset;
size_t tamano;
} campo_info_t;
typedef struct
{
const char *nombre_struct;
size_t tamano_total;
size_t num_campos;
campo_info_t *campos;
} struct_info_t;
// Usar para serialización genérica
void serializar_struct(const void *instancia, const struct_info_t *info);
```
<!-- {code-block} c -->
### 19.3: Expression Builder
Crear un DSL (Domain Specific Language) embebido para construir consultas:
```{code-block} c
:linenos:
typedef struct query query_t;
// API fluida
query_t *query_from(const char *tabla);
query_t *query_where(query_t *q, const char *condicion);
query_t *query_order_by(query_t *q, const char *campo);
query_t *query_limit(query_t *q, int limite);
char *query_to_sql(query_t *q); // Genera SQL
// Uso:
query_t *q =
query_from("usuarios")->where("edad > 18")->order_by("nombre")->limit(10);
char *sql = query_to_sql(q);
// SELECT * FROM usuarios WHERE edad > 18 ORDER BY nombre LIMIT 10
```
<!-- {code-block} c -->
## 20: Documentación de Decisiones
### 20.1: Architecture Decision Records
Documentar una decisión de diseño importante:
```{code-block} markdown
:linenos:
# ADR-001: Usar Tipo Opaco para Stack
## Estado
Aceptado
## Contexto
Necesitamos decidir si exponer la estructura interna del stack
o usar tipo opaco.
## Decisión
Usaremos tipo opaco completo con puntero a estructura incompleta.
## Consecuencias
### Positivo
- Permite cambiar implementación sin romper ABI
- Previene acceso directo a miembros
- Fuerza uso correcto de la API
### Negativo
- Requiere heap allocation (no puede ser stack-local)
- Indirección adicional en cada operación
- Tamaño del struct desconocido para el usuario
```
<!-- {code-block} markdown -->
**Tarea:** Crear ADRs para decisiones en tus propias librerías.
### 20.2: Changelog Profesional
Escribir un changelog siguiendo convenciones:
```{code-block} markdown
:linenos:
# Changelog
## [2.0.0] - 2024-10-24
### Breaking Changes
- Cambiado tipo de retorno de `lista_buscar` de int a bool
- Removida función deprecada `lista_add` (usar `lista_agregar`)
### Added
- Nueva función `lista_insertar_ordenado`
- Soporte para iteradores
### Fixed
- Corregido memory leak en `lista_destruir`
- Bug #42: crash con listas vacías
### Deprecated
- `lista_append` está deprecada, usar `lista_agregar_al_final`
```
<!-- {code-block} markdown -->
## 21-25: Casos Prácticos Complejos
### 21.1: Librería de Compresión
Diseñar API completa para compresión/descompresión:
- Múltiples algoritmos (gzip, bzip2, lz4)
- Streaming y block modes
- Configuración de nivel de compresión
- Callbacks para progreso
- Manejo de errores robusto
### 21.2: Motor de Expresiones Regulares
Diseñar API para regex:
- Compilación de patrones
- Búsqueda y matching
- Grupos de captura
- Reemplazo
- Flags (case-insensitive, multiline, etc.)
### 21.3: Sistema de Logging Multinivel
Diseñar sistema completo de logging:
- Múltiples niveles (DEBUG, INFO, WARN, ERROR)
- Múltiples destinos (archivo, syslog, callback)
- Rotación de logs
- Formato configurable
- Thread-safe
### 21.4: Pool de Conexiones
Diseñar un pool de conexiones reutilizables:
- Límite de conexiones concurrentes
- Timeout y reciclaje
- Health checking
- Estadísticas de uso
- Thread-safe
### 21.5: Máquina de Estados
Diseñar API para definir y ejecutar máquinas de estados finitas:
- Definición declarativa de estados y transiciones
- Callbacks para entrar/salir de estados
- Validación de transiciones
- Serialización del estado actual
- Debugging visual
---
:::{tip} Aplicación Práctica
Estos ejercicios están diseñados para ser progresivos. Comenzá con los
fundamentos (nomenclatura, tipos opacos) y avanzá hacia diseños más complejos.
Cada ejercicio refuerza los principios de
[claridad](#1-claridad-y-expresividad), [principio de mínima
sorpresa](#2-principio-de-minima-sorpresa) y las reglas de estilo del curso.