Módulos refactorizados
Módulo 1 — Error Handling con unwrap()
pipeline/processor.rs
2× unwrap()
clone innecesario
✓ corregido
Antes — antipatrón
pub fn process_event(raw: &str) -> String { let event: Event = serde_json::from_str(raw) .unwrap(); // PANIC en prod let user = get_user(event.user_id) .unwrap(); // PANIC si no existe format!("processed:{}", user.email.clone()) // Clone innecesario }
Después — idiomático
use anyhow::{Context, Result}; pub fn process_event(raw: &str) -> Result<String> { let event: Event = serde_json::from_str(raw) .context("invalid event JSON")?; let user = get_user(event.user_id) .context("user not found")?; Ok(format!("processed:{}", user.email)) }
Panic eliminado
Cambiado a
Result<String> con propagación ?. El caller decide cómo manejar el error.
Clone eliminado
user.email ya es owned al mover desde la struct; no necesita .clone().
Contexto de error
.context() de anyhow añade mensaje legible al stack de error en logs.
Módulo 2 — Estado de Agente con Booleanos
agents/state.rs
estados imposibles
struct flag explosion
✓ corregido
Antes — antipatrón
pub struct AgentState { pub is_running: bool, pub is_paused: bool, pub is_failed: bool, // Siempre presente, vacío al éxito pub error_message: String, pub retry_count: u64, } // Estado inválido posible: // is_running=true Y is_failed=true
Después — idiomático
#[derive(Debug, Clone)] pub enum AgentState { Idle, Running { task_id: String }, Paused { task_id: String, at: u64 }, Failed { reason: String, retries: u32 }, Done { output: String }, } // Imposible estar Running Y Failed // a la vez — el compilador lo garantiza
Antes — match frágil
if state.is_failed { retry(&state); } else if state.is_running { monitor(&state); } // ¿Qué pasa con is_paused=true? // Silenciosamente ignorado.
Después — exhaustivo
match &state { AgentState::Idle => schedule_next(), AgentState::Running { .. } => monitor(&state), AgentState::Paused { .. } => show_resume_btn(), AgentState::Failed { r, .. }=> retry_or_abort(r), AgentState::Done { .. } => archive(), } // El compilador obliga a cubrir todos
Estados imposibles = irrepresentables
El enum elimina 2³=8 combinaciones de booleanos, de las cuales solo 3 eran válidas.
Datos por variante
Cada estado solo lleva los datos que necesita:
Failed tiene reason, Running tiene task_id.
Matching exhaustivo
Si añadimos
Cancelled, el compilador fuerza a actualizar todos los match.
Módulo 3 — Caché Concurrente con Error Handling
db/queries.rs
2× unwrap()
sin tipo de error
✓ corregido
Antes — antipatrón
static DB_CACHE: Lazy<Mutex<HashMap<String, Vec<u8>>>> = Lazy::new(|| Mutex::new(HashMap::new())); pub fn fetch_cached(key: String) -> Vec<u8> { let cache = DB_CACHE.lock().unwrap(); cache.get(&key).unwrap().clone() // PANIC si la key no existe }
Después — idiomático
use thiserror::Error; #[derive(Debug, Error)] pub enum CacheError { #[error("key not found: {0}")] NotFound(String), #[error("mutex poisoned")] Poisoned, } pub fn fetch_cached(key: &str) -> Result<Vec<u8>, CacheError> { let cache = DB_CACHE.lock() .map_err(|_| CacheError::Poisoned)?; cache.get(key) .cloned() .ok_or_else(|| CacheError::NotFound(key.into())) }
thiserror para librería
CacheError con #[derive(Error)] da mensajes legibles y es typeable por el caller.
&str en lugar de String
El parámetro cambia a
&str — más flexible, evita allocations innecesarias al llamar.
.cloned() idiomático
.cloned() sobre Option<&Vec<u8>> es más limpio que .map(|v| v.clone()).
Tabla de patrones aplicados
| Patrón idiomático | Dónde se aplicó | Impacto | Referencia Rust |
|---|---|---|---|
| Result + ? | processor.rs — propagación de errores de parse y DB | ALTO | anyhow::Context |
| enum state machine | state.rs — AgentState como enum con datos por variante | ALTO | exhaustive match |
| thiserror | queries.rs — CacheError tipado para la capa de librería | MEDIO | thiserror::Error |
| &str vs String | queries.rs — parámetro de función de String a &str | MEDIO | borrow don't clone |
| .ok_or_else() | queries.rs — conversión de Option a Result con error tipado | BAJO | Option combinators |
| .cloned() | queries.rs — evitar .map(|v| v.clone()) verboso | BAJO | iterator ergonomics |
Anti-patrones eliminados
❌ .unwrap() en producción
Provoca panics no manejados. Reemplazado por
? + context() o map_err(). Los errores se propagan al caller limpiamente.❌ Booleanos de estado
Múltiples
bool permiten estados imposibles (running=true & failed=true). Enum con variantes hace los estados inválidos no compilables.❌ String como parámetro
Fuerza al caller a hacer
.to_string() o .clone(). Cambiar a &str acepta &String y literales sin allocations.