Clean Code en Rust: principios y ejemplos
Escribir código limpio no es solo seguir tendencias de moda, sino una habilidad fundamental que distingue a un profesional de un aficionado. En el mundo de la programación de sistemas, donde Rust está ganando cada vez más popularidad, la capacidad de escribir código comprensible, mantenible y seguro se vuelve críticamente importante. En este artículo, analizaremos los principios principales de Clean Code aplicados a Rust, respaldándolos con ejemplos prácticos.
1. Nomenclatura: di lo que hace la variable
Una nomenclatura adecuada es la base de un código legible. En Rust, donde el tipado estricto y la propiedad (ownership) ya proporcionan mucha información, los buenos nombres hacen que el código sea autodocumentado.
Mal ejemplo:
fn calc(x: &[i32]) -> i32 { let mut r = 0; for i in x { r += i; } r}Buen ejemplo (Clean Code):
fn calculate_sum(numbers: &[i32]) -> i32 { let mut total = 0; for number in numbers { total += number; } total}Usa verbos para funciones (calculate, fetch, validate) y sustantivos para variables (total, user_name, config). Evita abreviaciones como tmp, res o val — no dicen nada sobre su propósito.
2. Funciones: pequeñas y con una sola responsabilidad
El principio de responsabilidad única (Single Responsibility) se aplica no solo a las clases, sino también a las funciones. Cada función debe hacer exactamente una cosa y hacerla bien.
Mal ejemplo (la función hace demasiado):
fn process_user_data(user: &User) -> Result<(), String> { // Validación if user.name.is_empty() { return Err("Name is empty".to_string()); } // Registro println!("Processing user: {}", user.name); // Guardar en BD save_to_database(user)?; // Enviar email send_welcome_email(user.email)?; Ok(())}Buen ejemplo (división en funciones pequeñas):
fn process_user_data(user: &User) -> Result<(), AppError> { validate_user(user)?; log_user_processing(user); save_user_to_db(user)?; send_welcome_email(&user.email)?; Ok(())}
fn validate_user(user: &User) -> Result<(), AppError> { if user.name.is_empty() { return Err(AppError::Validation("Name is required".into())); } Ok(())}
fn log_user_processing(user: &User) { println!("Processing user: {}", user.name);}Observa: cada función ahora tiene una responsabilidad clara, y el código es más fácil de probar y modificar.
3. Manejo de errores: usa Result y tus propios tipos de error
En Rust no hay excepciones, y eso es excelente. Clean Code en Rust significa trabajar explícitamente con errores a través de los tipos Result<T, E> y Option<T>. Evita el pánico (unwrap(), expect()) en código de producción.
Mal ejemplo (pánico ante un error):
fn read_config(path: &str) -> Config { let content = std::fs::read_to_string(path).unwrap(); serde_json::from_str(&content).unwrap()}Buen ejemplo (manejo limpio de errores):
#[derive(Debug, thiserror::Error)]pub enum ConfigError { #[error("Failed to read file: {0}")] IoError(#[from] std::io::Error), #[error("Failed to parse config: {0}")] ParseError(#[from] serde_json::Error),}
fn read_config(path: &str) -> Result<Config, ConfigError> { let content = std::fs::read_to_string(path)?; let config: Config = serde_json::fr