Files
telegram-tui/REFACTORING_OPPORTUNITIES.md
Mikhail Kilin c6beea5608 refactor: create timeout/retry utilities to reduce code duplication (P1.1)
Created new utility modules to eliminate repeated timeout/retry patterns:
- src/utils/retry.rs: with_timeout() and with_timeout_msg() helpers
- src/utils/formatting.rs: timestamp formatting utilities (from utils.rs)
- src/utils/tdlib.rs: TDLib log configuration utilities (from utils.rs)

Refactored src/input/main_input.rs:
- Replaced 18+ instances of timeout(Duration, op).await pattern
- Simplified error handling from nested Ok(Ok(...))/Ok(Err(...))/Err(...)
  to cleaner Ok(...)/Err(...) with custom timeout messages
- Added type annotations for compiler type inference

Benefits:
- Reduced code duplication from ~20 instances to 2 utility functions
- Cleaner, more readable error handling
- Easier to maintain timeout logic in one place
- All 59 tests passing

Progress: REFACTORING_OPPORTUNITIES.md #1 (Дублирование кода) - Частично выполнено

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 19:56:33 +03:00

19 KiB
Raw Blame History

Возможности для рефакторинга

Результаты аудита кодовой базы от 2026-02-01 Статус: В работе (1/10 категорий)

Оглавление

  1. Дублирование кода
  2. Большие файлы/функции
  3. Сложная вложенность
  4. Нарушение Single Responsibility
  5. Плохая инкапсуляция
  6. Отсутствующие абстракции
  7. Несогласованность
  8. Перекрытие функциональности
  9. Проблемы производительности
  10. Отсутствующие архитектурные паттерны

1. Дублирование кода

Приоритет: 🔴 Высокий Статус: Частично выполнено Объем: 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 с общей логикой модальных окон
  • Создать validation.rs с переиспользуемыми валидаторами

Файлы

  • src/input/main_input.rs
  • src/app/handlers/*.rs
  • src/ui/modals/*.rs

2. Большие файлы/функции

Приоритет: 🔴 Высокий Статус: Не начато Объем: 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

  • Создать src/input/handlers/chat_list_input.rs
  • Создать src/input/handlers/messages_input.rs
  • Создать src/input/handlers/compose_input.rs
  • Создать src/input/handlers/search_input.rs
  • Создать src/input/handlers/modal_input.rs
  • Главный handle() делегирует по screen state

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.rs
  • src/tdlib/client.rs
  • src/ui/messages.rs
  • src/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.rs
  • src/tdlib/updates.rs
  • src/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.rs
  • src/tdlib/client.rs

5. Плохая инкапсуляция

Приоритет: 🔴 Высокий Статус: Не начато Объем: Вся структура App

Проблемы

  • 22 публичных поля в App

    pub 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(...);  // Слишком глубоко
    

Решение

  • Сделать все поля приватными
  • Добавить getter методы где нужно
  • Добавить setter методы с валидацией
  • Создать методы для операций (вместо прямого доступа)
    // Вместо app.selected_chat = Some(chat_id)
    app.select_chat(chat_id);
    
    // Вместо app.chats.push(new_chat)
    app.add_chat(new_chat);
    

Файлы

  • src/app/mod.rs
  • src/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.rs
    trait KeyHandler {
        fn handle_key(&mut self, app: &mut App, key: KeyEvent) -> Result<bool>;
    }
    
  • Реализовать для каждого экрана:
    • ChatListKeyHandler
    • MessagesKeyHandler
    • ComposeKeyHandler
    • SearchKeyHandler

6.2. Создать network utilities

  • Создать src/utils/network.rs
    async 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 - получение от TDLib
  • src/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.rs
  • src/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.rs
  • src/app/search.rs
  • src/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 (новые)

Приоритизация

🔴 Высокий приоритет (начать первым)

  1. Дублирование кода - быстрый win, улучшит поддерживаемость
  2. Большие файлы - критично для навигации и понимания кода
  3. Плохая инкапсуляция - защитит от ошибок, улучшит API

🟡 Средний приоритет (после высокого)

  1. Сложная вложенность - улучшит читаемость
  2. Single Responsibility - улучшит архитектуру
  3. Отсутствующие абстракции - упростит расширение
  4. Перекрытие функциональности - уберет путаницу

🟢 Низкий приоритет (когда будет время)

  1. Несогласованность - косметические улучшения
  2. Производительность - пока не critical path
  3. Архитектурные паттерны - 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 строк (в идеале)
  • Улучшенная тестируемость
  • Более четкое разделение ответственностей