Preparation for splitting large input file (#2): - Created src/input/handlers/ structure (7 modules) - clipboard.rs (~100 lines) - clipboard operations extracted - global.rs (~90 lines) - global commands (Ctrl+R/S/P/F) extracted - Stubs: profile.rs, search.rs, modal.rs, messages.rs, chat_list.rs - main_input.rs remains monolithic (1139 lines) - Attempted full migration broke navigation - rolled back - Handlers remain as preparation for gradual migration Updated documentation: - REFACTORING_OPPORTUNITIES.md: #2.1 status updated - CONTEXT.md: Added lesson about careful refactoring Lesson learned: Critical input logic requires careful step-by-step refactoring with functionality verification after each step. Tests: 563 passed, 0 failed Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
22 KiB
Возможности для рефакторинга
Результаты аудита кодовой базы от 2026-02-01 Статус: В работе (2/10 категорий завершены)
Оглавление
- Дублирование кода
- Большие файлы/функции
- Сложная вложенность
- Нарушение Single Responsibility
- Плохая инкапсуляция
- Отсутствующие абстракции
- Несогласованность
- Перекрытие функциональности
- Проблемы производительности
- Отсутствующие архитектурные паттерны
1. Дублирование кода
Приоритет: 🔴 Высокий Статус: ✅ ЗАВЕРШЕНО! (2026-02-01) Объем: 15-20% кодовой базы
Проблемы
-
Timeout/Retry паттерны (~20 экземпляров в обработке ввода)
- Повторяющаяся логика таймаутов в
src/input/main_input.rs - Одинаковые паттерны retry в разных обработчиках
- Повторяющаяся логика таймаутов в
-
Обработка модальных окон (5+ мест)
- Логика открытия/закрытия модалок дублируется
- Валидация ввода в модальных окнах повторяется
- Обработка Escape для закрытия модалок в каждом месте
-
Паттерны валидации
- Проверка пустых строк
- Валидация ID чатов/сообщений
- Проверка длины текста
Решение
- Создать
retry_utils.rsс функциямиwith_timeout(),with_retry()- Выполнено- Создан
src/utils/retry.rsс двумя функциями:with_timeout()иwith_timeout_msg() - Заменены 18+ использований
tokio::time::timeoutвsrc/input/main_input.rs - Код стал чище и короче (убрано вложенное Ok/Err матчинг)
- Создан
- Создать
modal_handler.rsс общей логикой модальных окон - Выполнено (2026-02-01)- Создан
src/utils/modal_handler.rs(120+ строк) - 4 функции:
handle_modal_key(),should_close_modal(),should_confirm_modal(),handle_yes_no() - Enum
ModalActionдля type-safe обработки - Поддержка английской и русской раскладки (y/д, n/т)
- 4 unit теста (все проходят)
- Создан
- Создать
validation.rsс переиспользуемыми валидаторами - Выполнено (2026-02-01)- Создан
src/utils/validation.rs(180+ строк) - 7 функций валидации:
is_non_empty(),is_within_length(),is_valid_chat_id(),is_valid_message_id(),is_valid_user_id(),has_items(),validate_text_input() - Покрывает все основные паттерны валидации
- 7 unit тестов (все проходят)
- Создан
Файлы
src/input/main_input.rssrc/app/handlers/*.rssrc/ui/modals/*.rs
2. Большие файлы/функции
Приоритет: 🔴 Высокий Статус: ✅ Частично выполнено (2026-02-01) Объем: 4 файла, 1000+ строк каждый
Проблемы
| Файл | Строки | Проблема |
|---|---|---|
src/input/main_input.rs |
1164 | Одна функция handle() на ~800 строк |
src/tdlib/client.rs |
1167 | Смешение facade и бизнес-логики |
src/ui/messages.rs |
800+ | Рендеринг всех типов сообщений |
src/tdlib/messages.rs |
850 | Обработка всех типов обновлений сообщений |
Решение
2.1. Разделить src/input/main_input.rs - ⏳ В процессе (2026-02-01)
- Создана структура
src/input/handlers/(7 модулей) - ПОДГОТОВКА - Создан
handlers/clipboard.rs(~100 строк) - извлечён из main_input - Создан
handlers/global.rs(~90 строк) - извлечён из main_input - Созданы заглушки:
profile.rs,search.rs,modal.rs,messages.rs,chat_list.rs - Постепенно мигрировать логику в handlers (требуется тщательное тестирование)
Примечание: Попытка полного переноса была откачена из-за поломки навигации. Handlers остаются как подготовка к будущей миграции. Текущий подход: извлекать независимые модули (clipboard, global), не трогая критичную логику ввода.
2.2. Разделить src/tdlib/client.rs
- Создать
src/tdlib/facade.rs(публичный API) - Переместить бизнес-логику в соответствующие модули
- Упростить
TdClientдо простого facade
2.3. Разделить src/ui/messages.rs
- Создать
src/ui/message_renderer/text.rs - Создать
src/ui/message_renderer/media.rs - Создать
src/ui/message_renderer/service.rs - Создать
src/ui/message_renderer/bubble.rs
2.4. Разделить src/tdlib/messages.rs
- Создать
src/tdlib/message_updates/new_message.rs - Создать
src/tdlib/message_updates/edit_message.rs - Создать
src/tdlib/message_updates/delete_message.rs - Создать
src/tdlib/message_updates/reactions.rs
Файлы
src/input/main_input.rssrc/tdlib/client.rssrc/ui/messages.rssrc/tdlib/messages.rs
3. Сложная вложенность
Приоритет: 🟡 Средний Статус: ❌ Не начато Объем: ~30 функций с глубокой вложенностью
Проблемы
- 4-5 уровней вложенности в обработке ввода
- Глубокая вложенность в обработке обновлений TDLib
- Множественные
if let/matchвложенные друг в друга
Примеры
// src/input/main_input.rs - типичный пример
if let Some(chat_id) = app.selected_chat {
if let Some(message_id) = app.selected_message {
if app.is_message_outgoing(chat_id, message_id) {
match key.code {
// еще больше вложенности
}
}
}
}
Решение
- Применить early returns для уменьшения вложенности
- Извлечь вложенную логику в отдельные функции
- Использовать паттерн "guard clauses"
- Применить
?оператор где возможно
Файлы
src/input/main_input.rssrc/tdlib/updates.rssrc/app/handlers/*.rs
4. Нарушение Single Responsibility
Приоритет: 🟡 Средний Статус: ❌ Не начато Объем: 2 основных структуры
Проблемы
4.1. App struct (50+ методов)
Смешивает ответственности:
- UI state management
- Business logic
- TDLib interaction
- Input handling
- Search logic
- Profile management
- Folder management
4.2. TdClient (facade + бизнес-логика)
Смешивает:
- Facade pattern (делегирование)
- Update processing
- Cache management
- Network operations
Решение
Разделить App
- Создать
ChatListState(состояние списка чатов) - Создать
MessageViewState(состояние просмотра сообщений) - Создать
ComposeState(состояние написания сообщения) - Создать
SearchState(состояние поиска) - Создать
ProfileState(состояние профиля) Appстановится координатором этих state объектов
Разделить TdClient
TdClientтолько facade (делегирование)- Бизнес-логика в
MessageService,ChatService, etc. - Update processing в отдельном модуле
Файлы
src/app/mod.rssrc/tdlib/client.rs
5. Плохая инкапсуляция
Приоритет: 🔴 Высокий
Статус: ✅ Частично выполнено (2026-02-01)
Объем: Вся структура App
Проблемы
-
22 публичных поля в
Apppub struct App { pub td_client: TdClient, pub chats: Vec<ChatInfo>, pub selected_chat: Option<ChatId>, pub messages: HashMap<ChatId, Vec<MessageInfo>>, // ... еще 18 полей } -
Прямой доступ везде
app.selected_chat = Some(chat_id); // Плохо app.chats.push(new_chat); // Плохо app.messages.clear(); // Плохо -
Тесты манипулируют внутренностями
app.td_client.user_cache.chat_user_ids.insert(...); // Слишком глубоко
Решение
- Сделать критичные поля приватными - Частично выполнено (2026-02-01)
- ✅
configсделан приватным (readonly через getterapp.config()) - ✅ Добавлены 30+ методов-геттеров и сеттеров для всех полей
- ⏳ Остальные поля оставлены pub для совместимости (требуется массовый рефакторинг)
- ✅
- Добавить getter методы где нужно - Выполнено
- 30+ методов:
phone_input(),set_phone_input(),screen(),set_screen(),is_loading(), и т.д.
- 30+ методов:
- Полная инкапсуляция всех полей (требует обновления 170+ мест в коде)
- Создать методы для операций (вместо прямого доступа)
// Вместо app.selected_chat = Some(chat_id) app.select_chat(chat_id); // Уже есть! // Вместо app.chats.push(new_chat) app.add_chat(new_chat); // TODO
Файлы
src/app/mod.rssrc/app/state.rs(новый)- Все тесты
6. Отсутствующие абстракции
Приоритет: 🟡 Средний Статус: ❌ Не начато Объем: 3 основные абстракции
Проблемы
6.1. Нет KeyHandler trait
Обработка клавиш размазана по коду:
// В каждом экране повторяется
match key.code {
KeyCode::Char('q') => { ... }
KeyCode::Esc => { ... }
// ...
}
6.2. Нет абстракции для network operations
Timeout/retry логика дублируется:
// Повторяется ~20 раз
let result = tokio::time::timeout(
Duration::from_millis(100),
operation()
).await;
6.3. Хардкод горячих клавиш
Невозможно изменить без правки кода:
KeyCode::Char('e') => edit_message(), // Хардкод
KeyCode::Char('d') => delete_message(), // Хардкод
Решение
6.1. Создать KeyHandler trait
- Создать
src/input/key_handler.rstrait KeyHandler { fn handle_key(&mut self, app: &mut App, key: KeyEvent) -> Result<bool>; } - Реализовать для каждого экрана:
ChatListKeyHandlerMessagesKeyHandlerComposeKeyHandlerSearchKeyHandler
6.2. Создать network utilities
- Создать
src/utils/network.rsasync fn with_timeout<F, T>(f: F, timeout_ms: u64) -> Result<T> async fn with_retry<F, T>(f: F, max_retries: u32) -> Result<T>
6.3. Создать систему горячих клавиш
- Создать
src/config/keybindings.rs - Загружать из конфига
- Позволить переопределять
Файлы
src/input/key_handler.rs(новый)src/utils/network.rs(новый)src/config/keybindings.rs(новый)
7. Несогласованность
Приоритет: 🟢 Низкий Статус: ❌ Не начато Объем: Вся кодовая база
Проблемы
7.1. Разные типы ошибок
// В одних местах
Result<T, String>
// В других
Result<T, Box<dyn Error>>
// В третьих
Result<T> // с неявным типом ошибки
7.2. Разные паттерны state management
- В одних местах флаги (
is_editing: bool) - В других энумы (
EditMode::Active) - В третьих Option (
editing_message: Option<MessageId>)
7.3. Разные подходы к валидации
- Иногда в UI слое
- Иногда в бизнес-логике
- Иногда в обработчиках ввода
Решение
- Стандартизировать обработку ошибок (один тип ошибки)
- Выбрать единый подход к state management (enum-based)
- Определить слой для валидации (бизнес-логика)
- Создать style guide в документации
Файлы
- Вся кодовая база
8. Перекрытие функциональности
Приоритет: 🟡 Средний Статус: ❌ Не начато Объем: 2 основные области
Проблемы
8.1. Фильтрация чатов (3 места)
- В
App::filter_chats_by_folder() - В
App::filter_chats() - В UI слое при рендеринге
8.2. Обработка сообщений (3+ модуля)
src/tdlib/messages.rs- получение от TDLibsrc/app/mod.rs- бизнес-логикаsrc/ui/messages.rs- рендеринг- Размыто, что за что отвечает
Решение
8.1. Централизовать фильтрацию
- Создать
src/app/chat_filter.rs - Один источник правды для фильтрации
- UI и App используют его
8.2. Четко разделить слои обработки сообщений
tdlib/messages.rs- только получение и преобразованиеapp/message_service.rs- бизнес-логикаui/messages.rs- только рендеринг
Файлы
src/app/chat_filter.rs(новый)src/app/message_service.rs(новый)src/tdlib/messages.rssrc/ui/messages.rs
9. Проблемы производительности
Приоритет: 🟢 Низкий Статус: ❌ Не начато Объем: Локальные оптимизации
Проблемы
9.1. Множественные клоны в обработке ввода
let text = app.input_text.clone(); // Клон
let chat_id = app.selected_chat.clone(); // Клон
// Используются только для чтения
9.2. Нет кеширования результатов поиска
- Каждый поиск выполняется заново
- Нет инвалидации кеша при изменениях
9.3. Неэффективная LRU cache
Vec::retain()+Vec::push()на каждый доступ- O(n) вместо потенциального O(1)
Решение
- Заменить клоны на borrowing где возможно
- Добавить
SearchCacheс TTL - Оптимизировать
LruCache(использоватьVecDequeили готовую библиотеку)
Файлы
src/input/main_input.rssrc/app/search.rssrc/tdlib/users.rs(LruCache)
10. Отсутствующие архитектурные паттерны
Приоритет: 🟢 Низкий Статус: ❌ Не начато Объем: Архитектурные изменения
Проблемы
10.1. Нет Event Bus
Компоненты напрямую вызывают друг друга:
- Сложно тестировать
- Сильная связанность
- Тяжело добавлять новые фичи
10.2. Нет Repository паттерна
Прямой доступ к данным везде:
app.messages.get(chat_id)app.chats.iter().find(...)- Нет единой точки доступа к данным
10.3. Нет Service Layer
Бизнес-логика размазана:
- Часть в
App - Часть в
TdClient - Часть в UI handlers
Решение
10.1. Event Bus (опционально)
- Создать
src/event_bus.rs - Pub/Sub для событий между компонентами
- Decoupling
10.2. Repository Pattern
- Создать
src/repositories/chat_repository.rs - Создать
src/repositories/message_repository.rs - Создать
src/repositories/user_repository.rs - Единая точка доступа к данным
10.3. Service Layer
- Создать
src/services/chat_service.rs - Создать
src/services/message_service.rs - Создать
src/services/search_service.rs - Вся бизнес-логика в сервисах
Файлы
src/event_bus.rs(новый, опционально)src/repositories/*.rs(новые)src/services/*.rs(новые)
Приоритизация
🔴 Высокий приоритет (начать первым)
- Дублирование кода - быстрый win, улучшит поддерживаемость
- Большие файлы - критично для навигации и понимания кода
- Плохая инкапсуляция - защитит от ошибок, улучшит API
🟡 Средний приоритет (после высокого)
- Сложная вложенность - улучшит читаемость
- Single Responsibility - улучшит архитектуру
- Отсутствующие абстракции - упростит расширение
- Перекрытие функциональности - уберет путаницу
🟢 Низкий приоритет (когда будет время)
- Несогласованность - косметические улучшения
- Производительность - пока не critical path
- Архитектурные паттерны - nice to have
План выполнения
Фаза 1: Быстрые победы (1-2 дня)
- #1: Создать утилиты для дублирующегося кода
- #5: Инкапсулировать поля App
Фаза 2: Разделение больших файлов (3-5 дней)
- #2.1: Разделить
main_input.rs - #2.2: Разделить
client.rs - #2.3: Разделить
messages.rs
Фаза 3: Улучшение архитектуры (5-7 дней)
- #4: Разделить ответственности App/TdClient
- #6: Добавить абстракции (KeyHandler, network utils)
- #8: Убрать перекрытие функциональности
Фаза 4: Полировка (2-3 дня)
- #3: Упростить вложенность
- #7: Стандартизировать подходы
- #9: Оптимизировать производительность
Фаза 5: Архитектурные паттерны (опционально)
- #10: Рассмотреть Event Bus / Repository / Service Layer
Метрики
До рефакторинга
- Строк кода: ~15,000
- Файлов: ~50
- Средний размер файла: 300 строк
- Максимальный файл: 1167 строк
- Дублирование: ~15-20%
- Публичных полей в App: 22
Цели после рефакторинга
- Максимальный файл: <500 строк
- Дублирование: <5%
- Публичных полей в App: 0
- Все файлы <400 строк (в идеале)
- Улучшенная тестируемость
- Более четкое разделение ответственностей