Общая мотивация

Поводом задуматься был небольшой пет проект для корпоративной отчетности, где вручную вносят данные в таблицы, формат которых для гибкости задается в Excel. Схема,

Статистика кода

--- .rs (32 files, 3043 lines) ---
452 domain\formulas.rs
286 presentation\table_render.rs
280 infrastructure\repository.rs
265 infrastructure\xlsx_xml.rs
198 services\cell_change_service.rs
127 presentation\controller.rs
121 infrastructure\excel_loader.rs
110 services\export_service.rs
106 presentation\html_views.rs
103 domain\dto.rs
85 services\errors.rs
81 services\calc_service.rs
75 presentation\flows.rs
74 infrastructure\sheet_list.rs
69 app_state.rs
67 infrastructure\auth.rs
66 infrastructure\user_ctx.rs
60 main.rs
57 services\viewer_queries.rs
56 services\adapters.rs
53 domain\styles.rs
48 infrastructure\report_registry.rs
42 domain\utils.rs
40 domain\merges.rs
38 infrastructure\sessions.rs
26 infrastructure\errors.rs
20 presentation\view_models.rs
12 domain\errors.rs
9 infrastructure\mod.rs
6 domain\mod.rs
6 services\mod.rs
5 presentation\mod.rs

--- .sql (6 files, 278 lines) ---
78 migrations\03_templates_mod.sql
56 migrations\04_reports_mod.sql
44 migrations\06_test_users.sql
40 migrations\05_add_postgres_extentions.sql
37 migrations\01_templates.sql
23 migrations\02_reports.sql

--- .html (3 files, 106 lines) ---
54 presentation\templates\reports.html
26 presentation\templates\report_header.html
26 presentation\templates\login.html

Почему Rust и ручная реализация?

TL;DR: Оказалось проще формализовать и реализовать свой потокобезопасный движок Excel с нуля, чем оптимизировать мутабельный движок.

Первая попытка (C# + EPPlus):

Мутабельный ООП-движок формул (готовая библиотека):
├─ Проблема: блокировки при конкурентном доступе
├─ Вариант 1: Полная блокировка - слишком медленно
├─ Вариант 1Б: Полная блокировка листа - и медленно и сложно
├─ Вариант 2: Батч накапливает изменения от всех пользователей + подписка на результат - сложно делать подписку
├─ Вариант 2Б: Таймеры-дятлы (контроллеры спрашивают батч "готово?") - растет лаг до ответа
├─ Вариант 3: Воркер на клиента + синхронизация через БД - очень много ОЗУ, придется делать время жизни воркера, снижать нагрузку на БД путем добавления write through cache с батч записью в БД и gracefull degradation
└─ Результат: каждый вариант сложен или ухудшает свойства системы, непонятно как выбрать

Проблема: оптимизации stateful (чертов ООП!) системы, слишком сложно проектировать,
         нельзя доверить LLM (tacit контекст, trade-offs, partial failure)

Вторая попытка (Rust + ручная реализация):

Иммутабельная модель + функциональное ядро:
├─ Парсер формул по EBNF грамматике
├─ Интерпретатор (чистые функции)
├─ Топологический анализ зависимостей формул (обнаружение циклов)
└─ Eventual consistency (LWW для конфликтов), но мелко гранулярная

Результат: формализованные задачи LLM сделала безупречно,
          а архитектурные решения (консистентность, конкурентность) 
          я зафиксировал сам до генерации кода.

Третья попытка (снова С#, батчинг и дятлы):

Таймеры-дятлы (контроллеры спрашивают у батча "готово?" 10-20 раз в секунду) оказались все же проще. То, что я считал за недостаток - таймер увеличивает лаг ответа на свой интервал - оказалось благом: мы избавились от "удара грома", когда система должна одновременно ответить сотням и тысячам пользователей сразу после завершения батча, создавая пиковую нагрузку. Дятлы-таймеры же вносят небольшой естественный джиттер в доли секунды.

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

Неожиданное открытие:

Сложное, но формализованное (парсер грамматики, интерпретатор формул, обнаружение циклов) проще реализовать с нуля, чем оптимизировать существующий мутабельный движок. LLM сделала безупречно, без правок и последующих доработок.

Простое, но контекстно зависимое (какую модель конкурентности выбрать? батчи или параллельные воркеры на каждое изменение? блокировки какой гранулярности?) - слишком много вариантов, trade-offs зависят от паттернов использования. LLM давала типовые решения, игнорируя специфику. Я тоже затруднялся уверенно оценить все варианты и выбрать лучший.

Два фундаментальных инсайта

1. Отложить некоторые решения или недоделать спецификации дорого мне обошлись

В частности, дорого обошлись ошибки в выборе свой/чужой движок, сложности рефакторинга при изменение контрактов фронт-бэк.

Решения и контракты имеют разный срок «цементирования» - часть надо сразу, часть подстраиваются позже.

Ранняя фиксация требуется для того, что:

  • Дорого изменять (системные / не локализованные гарантии, консистентность данных)

  • Сложно верифицировать результат (контракты фронт-бэк не проверишь компилятором), но может дать большой импакт позже (архитектурные паттерны)

  • Есть риски от нечеткости требований (бизнес-инварианты с неявными корнер кейсами)

Поздняя фиксация допустима для:

  • Дешёвых в изменении компонентов (легкий в рефакторинге код - кстати Rust код рефакторить проще чем C#, адаптеры, DTO) – подгоняем под остальное, рефакторим по мере разработки

2. Парадокс сложности

Сложное, но формализованное теперь дешево (идеальная задача для LLM)

  • Чёткие границы задачи

  • Известная предметная область, есть типовые решения

  • Объективные критерии успеха

  • Быстрая верификация

Простое, но контекстное опасно для LLM

  • Неявные требования

  • Субъективные критерии

  • Отложенная верификация

 

Новая ценность разработчика:
Не скорость кодинга, а способность:

  • Видеть долгосрочные последствия (blast radius изменений, emergent behavior, неопределенности и риски)

  • Извлекать неявное знание (tacit/implicit должно стать explicit)

  • Формализовать намерения. Потребности и требования надо преобразовать в формальные спецификации.

  • Создавать рамки доказуемости (внутри которых LLM безопасна). Идеально, если соответствие спецификациям гарантируются компилятором на уровне типов или хотя бы проверяются property тестами. Еще хуже, если они проверяются тестами на каждый use case и совсем плохо, если не проверяются совсем. Обязательно код должен быть удобен для review, логика понятна, сконцентрирована и не зашумлена.

Мы перестаём писать код.
Мы становимся архитекторами корректности.

P.S. Ценность корректности разная. Для менее ответственных и более определенных систем можно больше доверить LLM и джунам, но это совсем другая история (Каневский жпг)

Комментарии (0)