Чистый код на Rust: принципы и примеры для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Clean Code на Rust: принципы и примеры

Написание чистого кода — это не просто следование модным трендам, а фундаментальный навык, который отличает профессионала от любителя. В мире системного программирования, где Rust набирает всё большую популярность, умение писать понятный, поддерживаемый и безопасный код становится критически важным. В этой статье мы разберём основные принципы Clean Code применительно к Rust, подкрепив их практическими примерами.

1. Именование: говорите, что делает переменная

Правильное именование — основа читаемого кода. В Rust, где строгая типизация и владение (ownership) уже дают много информации, хорошие имена делают код самодокументируемым.

Плохой пример:

fn calc(x: &[i32]) -> i32 {
    let mut r = 0;
    for i in x {
        r += i;
    }
    r
}

Хороший пример (Clean Code):

fn calculate_sum(numbers: &[i32]) -> i32 {
    let mut total = 0;
    for number in numbers {
        total += number;
    }
    total
}

Используйте глаголы для функций (calculate, fetch, validate) и существительные для переменных (total, user_name, config). Избегайте сокращений вроде tmp, res или val — они ничего не говорят о назначении.

2. Функции: маленькие и с одной ответственностью

Принцип единственной ответственности (Single Responsibility) применим не только к классам, но и к функциям. Каждая функция должна делать ровно одно дело и делать его хорошо.

Плохой пример (функция делает слишком много):

fn process_user_data(user: &User) -> Result<(), String> {
    // Валидация
    if user.name.is_empty() {
        return Err("Name is empty".to_string());
    }
    // Логирование
    println!("Processing user: {}", user.name);
    // Сохранение в БД
    save_to_database(user)?;
    // Отправка email
    send_welcome_email(user.email)?;
    Ok(())
}

Хороший пример (разделение на маленькие функции):

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);
}

Обратите внимание: каждая функция теперь имеет чёткую ответственность, а код стал легче тестировать и изменять.

3. Обработка ошибок: используйте Result и свои типы ошибок

В Rust нет исключений, и это прекрасно. Clean Code на Rust означает явную работу с ошибками через типы Result<T, E> и Option<T>. Избегайте паники (unwrap(), expect()) в production-коде.

Плохой пример (паника при ошибке):

fn read_config(path: &str) -> Config {
    let content = std::fs::read_to_string(path).unwrap();
    serde_json::from_str(&content).unwrap()
}

Хороший пример (чистая обработка ошибок):

#[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::from_str(&content)?;
    Ok(config)
}

Используйте крейт thiserror для создания своих типов ошибок — это делает код более выразительным и упрощает отладку.

4. Идиомы Rust: используйте возможности языка

Clean Code в Rust — это не просто перенос привычек из других языков, а использование идиоматических конструкций самого Rust.

  • Используйте match вместо цепочек if-else — это делает код более безопасным и читаемым.
  • Применяйте итераторы вместо явных циклов — они лаконичнее и часто производительнее.
  • Используйте Option и Result вместо магических чисел (например, -1 для ошибки).

Плохой пример (стиль C++ в Rust):

fn find_user(users: &[User], id: u32) -> Option<&User> {
    for i in 0..users.len() {
        if users[i].id == id {
            return Some(&users[i]);
        }
    }
    None
}

Хороший пример (идиоматичный Rust):

fn find_user(users: &[User], id: u32) -> Option<&User> {
    users.iter().find(|user| user.id == id)
}

Ещё один важный приём — использование строителей (Builder pattern) для сложных структур данных. Это улучшает читаемость и позволяет избежать длинных списков параметров в конструкторах.

#[derive(Debug)]
pub struct Query {
    table: String,
    condition: Option<String>,
    limit: Option<u32>,
}

pub struct QueryBuilder {
    query: Query,
}

impl QueryBuilder {
    pub fn new(table: &str) -> Self {
        Self {
            query: Query {
                table: table.to_string(),
                condition: None,
                limit: None,
            },
        }
    }

    pub fn with_condition(mut self, condition: &str) -> Self {
        self.query.condition = Some(condition.to_string());
        self
    }

    pub fn with_limit(mut self, limit: u32) -> Self {
        self.query.limit = Some(limit);
        self
    }

    pub fn build(self) -> Query {
        self.query                

Блоги

Рекомендаций по книгам