
Понять, разбирается человек в Rust или нет, можно за 5 минут.
Не по синтаксису. Не по количеству ошибок, сокращенному с помощью контроллера зависимостей (Borrow checker). По структуре кода.
Старшие бэкендеры пишут на Rust не так, как разработчики-новички. Сдержанно. Осознанно. Их код выглядит скучно. И именно поэтому он масштабируется, оставаясь работоспособным и читаемым спустя полгода.
Вот три принципа, которым, как я заметил, старшие разработчики следуют инстинктивно. Большинство разработчиков их игнорирует. До тех пор, пока их код не попадет в продакшен.
Принцип 1: Недопустимые состояния должны быть недопустимы
Младшие разработчики на Rust пытаются обработать каждую ошибку в рантайме. Код старших разработчиков предотвращает саму возможность возникновения ошибок.
Рассмотрим следующий пример:
struct User {
id: String,
email: String,
}
Этот код выглядит нормально. Пока не осознаешь, что он допускает пустые идентификаторы, недействительные адреса электронной почты и неполные данные пользователей.
Теперь взгляните на версию опытного разработчика:
struct UserId(String);
struct Email(String);
struct User {
id: UserId,
email: Email,
}
Процесс создания кода становится осознанным:
impl User {
fn new(id: UserId, email: Email) -> Self {
User { id, email }
}
}
Валидация выполняется один раз. После этого компилятор обеспечивает корректность.
Старшие разработчики не добавляют больше проверок. Они исключают саму возможность некорректного использования.
Соблюдение одного только этого принципа позволяет избежать целых категорий ошибок.
Принцип 2: Владение отражает границы системы
Большинство разработчиков на Rust испытывают проблемы с системой владения.
Старшие бэкендеры используют ее как инструмент проектирования.
Взгляните на этот API-обработчик:
fn handle(req: Request) {
process(req);
log(req);
}
Этот код не скомпилируется. В этом и суть.
Опытный разработчик в этом месте останавливается и задается наиболее уместными в данном случае вопросами. Кто владелец запроса? Кто имеет право его использовать?
Решение заключается не в бездумном клонировании, а в перепроектировании ответственности.
fn handle(req: &Request) {
process(req);
log(req);
}
Или в осознанной передаче владения:
fn handle(req: Request) {
let data = extract(req);
process(data);
}
Владение отражает поток данных в системе.
Вот простая схема границ сервиса:
Request
|
v
Parser -> Domain -> Storage
Каждая стрелка представляет передачу владения. Никакой путаницы с общим доступом. Никакого случайного повторного использования.
Опытные разработчики позволяют системе владения выявлять архитектурные недостатки на раннем этапе. До того, как это произойдет в продакшене.
Принцип 3: Ошибки — это данные, а не строки
Большая часть кода на Rust обрабатывает ошибки как сообщения. Код опытных разработчиков обращается с ними как с состояниями.
Вот обычная практика:
fn load(id: &str) -> Result<Data, String> {
Err("not found".to_string())
}
Этот код плохо масштабируется. Его логику невозможно отследить. С ним нельзя безопасно работать через сопоставление.
А вот версия опытного разработчика:
enum LoadError {
NotFound,
Timeout,
Corrupt,
}
fn load(id: &str) -> Result<Data, LoadError> {
Err(LoadError::NotFound)
}
И сразу использование становится корректным:
match load(id) {
Ok(data) => use_data(data),
Err(LoadError::NotFound) => recover(),
Err(e) => fail(e),
}
Никаких догадок. Никакого разбора строк. Никаких сюрпризов.
Старшие разработчики четко определяют пути сбоев, потому что именно с ними возникают проблемы в продакшене.
Почему эти принципы важны для бэкенд-систем
Работа бэкенд-систем нарушается под нагрузкой. Резкий рост трафика. Нестабильность зависимостей. Приходят некорректные данные.
Само по себе знание Rust вас не спасет. Спасет следование принципам его работы.
Помня об этих принципах, вы сможете решить три задачи:
- Уменьшить количество проверок в рантайме.
- Выявить ошибки проектирования на ранних этапах.
- Превратить хаос в продакшене в ошибки компилятора.
Вот почему опытные бэкендеры остаются спокойными во время сбоев. Они уже сделали все, чтобы понять поведение системы.
Небольшой тест, чтобы почувствовать большую разницу
Однажды я сравнил два сервиса.
В одном использовались строковые ошибки и проверки в рантайме. В другом — типизированные ошибки и четкие границы владения.
Результат под нагрузкой:
Сервис А: частые сбои, неясные логи
Сервис В: предсказуемые отказы, чистое восстановление
Никакой магии. Только проектирование.
Почему большинство разработчиков игнорируют эти принципы
Потому что поначалу кажется, что соблюдение этих принципов тормозит процесс разработки.
Создание типов ощущается как церемония. Проектирование владения кажется ограничивающим. Моделирование ошибок кажется избыточным.
Пока кодовая база не разрастется. Пока в команду не придет новый разработчик. Пока в полночь не возникнут сбои в продакшене.
Тогда эти принципы перестают казаться оторванными от практики. Они начинают казаться спасением.
Мышление на уровне старшего бэкендера
Опытные бэкендеры не задаются вопросом: «Как мне заставить это скомпилироваться?» Они думают в другом направлении: «Что здесь недопустимо?»
Строгий Rust сполна вознаграждает такой образ мышления.
Компилятор становится партнером, а не препятствием.
Заключение
Rust сам по себе не сделает из вас профессионала. Но он покажет, подходите ли вы к решению задач как профессионал.
Если код держится на вашей воле и внимании — рано или поздно вы ошибетесь, и в системе возникнет сбой. Если ваш код полагается на структуру, которая делает ошибки невозможными, — компилятор Rust сам обеспечит надежность системы.
В этом вся суть, которую игнорирует большинство разработчиков. И именно поэтому ее не игнорируют опытные бэкендеры.
Читайте также:
- Стоит ли использовать Rust для разработки CRUD-ориентированных бэкенд-систем?
- Почему разработчики JavaScript используют инструменты на Rust
- 5 функций CLI на Rust для оптимизации привычных инструментов
Читайте нас в Telegram, VK и Дзен
Перевод статьи DevLogic — Engineering Thinking: 3 Rust Patterns Senior Backend Engineers Never Skip





