Перейти к основному содержимому
  1. Rust/

Проектирование библиотек в Rust: Data-Oriented API, разделение lib/main и бебаггинг (Bebugging)

1099 слов·6 минут· loading · loading · · ·Rust-middle Черновик
О Rust - Эта статья часть цикла.
Статей прочитано 0/58
0%
📚 Введение и дополнительные материалы
🟢 Начальный уровень (Rust-basic)
Не прочитана
🔵 Средний уровень (Rust-middle)
44 Проектирование библиотек в Rust: Data-Oriented API, разделение lib/main и бебаггинг (Bebugging) (текущая)
Не прочитана

Введение
#

Написание производственного кода на Rust требует не только знания синтаксиса и системы типов, но и глубокого понимания архитектуры крейтов (Crates) и стандартов инженерии надежности.

Эта статья открывает практическую серию по разработке инструментов и библиотек в Rust, созданную на основе материалов книги John Arundel “The Secrets of Rust: Tools”.

Мы системно разберем, как правильно разделять ответственность между библиотекой и бинарным приложением, почему публичный API обязан быть Data-Oriented, и как техника Bebugging предотвращает ошибки в юнит-тестах.


1. Архитектура Crate: разделение src/lib.rs и src/main.rs
#

Одно из главных правил архитекторов Rust — разделение ответственности между вычислениями и отображением:

  1. src/lib.rs (Библиотечный компонент): содержит всю бизнес-логику, агрегаторы данных, структуры и вычисления.
  2. src/main.rs (Бинарная точка входа): содержит минимальный код, который отвечает исключительно за чтение параметров из CLI или конфигураций и вывод готового результата пользователю.

Главное правило Data-Oriented API
#

Библиотечные функции категорически не должны напрямую выводить данные в терминал через println!. Взамен они обязаны возвращать структуры данных или строки (Data-Oriented API).

Это гарантирует:

  • Переиспользуемость: одну и ту же библиотеку можно подключить к CLI-утилите, Web/gRPC микросервису или GUI-интерфейсу.
  • Тестируемость: состояние системы можно мгновенно проверить в тестах без перехвата стандартного потока вывода stdout.

Ниже представлен пошаговый пример проектирования библиотеки телеметрии и точки входа:

Архитектура Crate и Data-Oriented API
Шаг 1/2
 1// Шаг 1: Проектирование чистой библиотеки (src/lib.rs) без побочных эффектов
 2
 3#[derive(Debug, PartialEq)]
 4pub struct TelemetryReport {
 5    pub service_name: String,
 6    pub cpu_usage_pct: f64,
 7    pub active_connections: usize,
 8}
 9
10pub struct TelemetryAggregator {
11    service_name: String,
12}
13
14impl TelemetryAggregator {
15    pub fn new(service_name: impl Into<String>) -> Self {
16        Self {
17            service_name: service_name.into(),
18        }
19    }
20
21    // Главный принцип библиотеки: функция ВОЗВРАЩАЕТ структуру данных, а не выводит её в stdout через println!
22    pub fn generate_report(&self, cpu_raw: u64, connections: usize) -> TelemetryReport {
23        let cpu_usage_pct = (cpu_raw as f64) / 100.0;
24        TelemetryReport {
25            service_name: self.service_name.clone(),
26            cpu_usage_pct,
27            active_connections: connections,
28        }
29    }
30}
31
32fn main() {
33    let aggregator = TelemetryAggregator::new("auth-service");
34    let report = aggregator.generate_report(4550, 120);
35    println!("Библиотечный модуль сгенерировал данные: {report:?}");
36}
37

1. Data-Oriented API в библиотеках

  • Публичный интерфейс библиотеки должен быть Data-Oriented: функции возвращают структуры, не вызывая println! внутри.
  • Это обеспечивает идеальную тестируемость, возможность повторного использования в CLI, Web/gRPC и отсутствие побочных эффектов.

2. Юнит-тестирование, подробные утверждения и Bebugging
#

Подмодуль #[cfg(test)]
#

Тесты в Rust размещаются в специальном подмодуле #[cfg(test)] mod tests. Благодаря атрибуту #[cfg(test)] весь код тестов и их вспомогательные структуры полностью исключаются из финального релизного бинарника (cargo build --release), не создавая никакого оверхеда.

Информативные утверждения в assert_eq!
#

При написании утверждений не ограничивайтесь стандартным вызовом. Передавайте форматированное контекстное сообщение:

assert_eq!(
    got, want,
    "Индекс здоровья системы для load={load}: want '{want}', got '{got}'"
);

Техника Bebugging (Тестирование самих тестов)
#

Bebugging — это инженерная практика намеренного внесения дефекта в реализацию функции. Если после внесения ошибки ваш тест всё равно остается «зеленым», значит тест написан неверно или имеет избыточную толерантность.

Самодокументируемые тесты (cargo-testdox)
#

Именование функций-тестов в виде понятных полных предложений на английском языке (например health_index_calculates_correctly_under_normal_load) позволяет использовать сторонние утилиты вроде cargo-testdox для автоматической сборки спецификации требований прямо из кода.

Изучите пошаговый пример написания информативных юнит-тестов и проверки методом Bebugging:

Юнит-тесты, Bebugging и самодокументируемый код
Шаг 1/2
 1// Шаг 1: Информативные утверждения в тесте с контекстными сообщениями
 2
 3pub fn calculate_health_index(load: f64, errors: usize) -> f64 {
 4    if load > 100.0 {
 5        return 0.0;
 6    }
 7    let error_penalty = (errors as f64) * 2.5;
 8    (100.0 - load - error_penalty).max(0.0)
 9}
10
11fn main() {
12    let index = calculate_health_index(20.0, 2);
13    println!("Вычисленный индекс здоровья системы: {index}");
14}
15
16#[cfg(test)]
17mod tests {
18    use super::*;
19
20    #[test]
21    fn health_index_calculates_correctly_under_normal_load() {
22        let load = 20.0;
23        let errors = 2;
24        let want = 75.0; // 100 - 20 - 5.0
25        let got = calculate_health_index(load, errors);
26
27        // Информативное сообщение с подробными данными о несовпадении:
28        assert_eq!(
29            got, want,
30            "Индекс здоровья рассчитан неверно для load={load}, errors={errors}: want '{want}', got '{got}'"
31        );
32    }
33}
34

1. Информативные юнит-тесты

  • Тесты размещаются в подмодуле #[cfg(test)] mod tests, который не компилируется в релизную сборку.
  • Использование расширенных контекстных сообщений в assert_eq! позволяет мгновенно понять причину сбоя без дебаггера.

Проверь свои знания!
#

Пройдите короткий тест по архитектуре крейтов и модульному тестированию:

Статья прочитана
Пожалуйста, оцените насколько статья была вам полезна и понятна
Цикл статей
О Rust - Эта статья часть цикла.
Статей прочитано 0/58
0%
📚 Введение и дополнительные материалы
🟢 Начальный уровень (Rust-basic)
Не прочитана
🔵 Средний уровень (Rust-middle)
44 Проектирование библиотек в Rust: Data-Oriented API, разделение lib/main и бебаггинг (Bebugging) (текущая)
Не прочитана

Связанные статьи