Clean Code sur Rust : principes et exemples
Écrire du code propre n'est pas simplement suivre les tendances à la mode, mais une compétence fondamentale qui distingue un professionnel d'un amateur. Dans le monde de la programmation système, où Rust gagne de plus en plus en popularité, la capacité à écrire un code compréhensible, maintenable et sûr devient cruciale. Dans cet article, nous allons analyser les principes de base du Clean Code appliqués à Rust, en les appuyant par des exemples pratiques.
1. Nommage : dites ce que fait la variable
Un bon nommage est la base d'un code lisible. En Rust, où le typage strict et l'ownership (possession) fournissent déjà beaucoup d'informations, de bons noms rendent le code auto-documenté.
Mauvais exemple :
fn calc(x: &[i32]) -> i32 { let mut r = 0; for i in x { r += i; } r}Bon exemple (Clean Code) :
fn calculate_sum(numbers: &[i32]) -> i32 { let mut total = 0; for number in numbers { total += number; } total}Utilisez des verbes pour les fonctions (calculate, fetch, validate) et des noms pour les variables (total, user_name, config). Évitez les abréviations comme tmp, res ou val — elles ne disent rien sur leur objectif.
2. Fonctions : petites et avec une seule responsabilité
Le principe de responsabilité unique (Single Responsibility) ne s'applique pas seulement aux classes, mais aussi aux fonctions. Chaque fonction doit faire exactement une chose et la faire bien.
Mauvais exemple (la fonction fait trop de choses) :
fn process_user_data(user: &User) -> Result<(), String> { // Validation if user.name.is_empty() { return Err("Name is empty".to_string()); } // Journalisation println!("Processing user: {}", user.name); // Sauvegarde en base de données save_to_database(user)?; // Envoi d'email send_welcome_email(user.email)?; Ok(())}Bon exemple (division en petites fonctions) :
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);}Remarquez : chaque fonction a désormais une responsabilité claire, et le code est devenu plus facile à tester et à modifier.
3. Gestion des erreurs : utilisez Result et vos propres types d'erreurs
En Rust, il n'y a pas d'exceptions, et c'est parfait. Le Clean Code en Rust signifie un travail explicite avec les erreurs via les types Result<T, E> et Option<T>. Évitez les paniques (unwrap(), expect()) dans le code de production.
Mauvais exemple (panique en cas d'erreur) :
fn read_config(path: &str) -> Config { let content = std::fs::read_to_string(path).unwrap(); serde_json::from_str(&content).unwrap()}Bon exemple (gestion propre des erreurs) :
#[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