817 lines
23 KiB
Markdown
817 lines
23 KiB
Markdown
# План рефакторинга app.js на ES6 модули
|
||
|
||
## Текущее состояние
|
||
|
||
- **Размер файла**: 1671 строка
|
||
- **Проблемы**:
|
||
- Монолитная структура затрудняет поддержку
|
||
- Все функции в глобальной области видимости
|
||
- Сложно тестировать отдельные компоненты
|
||
- Повторное использование кода затруднено
|
||
|
||
## Целевая архитектура
|
||
|
||
```
|
||
static/
|
||
├── index.html
|
||
├── styles.css
|
||
├── js/
|
||
│ ├── main.js # Точка входа, инициализация
|
||
│ ├── config.js # Константы и конфигурация
|
||
│ ├── state/
|
||
│ │ └── appState.js # Глобальное состояние приложения
|
||
│ ├── services/
|
||
│ │ ├── api-client.js # Существующий API клиент (переместить)
|
||
│ │ ├── auth.service.js # Аутентификация
|
||
│ │ ├── settings.service.js # Управление настройками
|
||
│ │ └── query.service.js # Запросы к RAG
|
||
│ ├── ui/
|
||
│ │ ├── auth.ui.js # UI авторизации
|
||
│ │ ├── settings.ui.js # Диалог настроек
|
||
│ │ ├── query-builder.ui.js # Построитель запросов
|
||
│ │ ├── answer-viewer.ui.js # Просмотр ответов
|
||
│ │ ├── questions-list.ui.js # Список вопросов
|
||
│ │ ├── annotations.ui.js # Аннотации
|
||
│ │ └── loading.ui.js # Индикаторы загрузки
|
||
│ ├── utils/
|
||
│ │ ├── dom.utils.js # DOM манипуляции
|
||
│ │ ├── format.utils.js # Форматирование (время, текст)
|
||
│ │ ├── file.utils.js # Работа с файлами
|
||
│ │ └── validation.utils.js # Валидация данных
|
||
│ └── data/
|
||
│ ├── storage.js # LocalStorage обертка
|
||
│ └── defaults.js # Дефолтные настройки (из settings.js)
|
||
└── settings.js # Удалить после рефакторинга
|
||
```
|
||
|
||
## Этапы рефакторинга
|
||
|
||
### Этап 1: Подготовка (день 1)
|
||
|
||
**Цель**: Настроить окружение для ES6 модулей
|
||
|
||
#### 1.1. Обновить index.html
|
||
```html
|
||
<!-- Заменить обычные скрипты на модули -->
|
||
<script type="module" src="js/main.js"></script>
|
||
```
|
||
|
||
#### 1.2. Создать структуру папок
|
||
```bash
|
||
mkdir static/js
|
||
mkdir static/js/state
|
||
mkdir static/js/services
|
||
mkdir static/js/ui
|
||
mkdir static/js/utils
|
||
mkdir static/js/data
|
||
```
|
||
|
||
#### 1.3. Настроить конфигурацию
|
||
- Создать `js/config.js` с константами
|
||
- Определить экспорты/импорты
|
||
|
||
---
|
||
|
||
### Этап 2: Утилиты и вспомогательные функции (день 1-2)
|
||
|
||
**Цель**: Вынести независимые функции
|
||
|
||
#### 2.1. `utils/format.utils.js`
|
||
**Функции** (из app.js строки 150-188):
|
||
- `generateUUID()`
|
||
- `formatTime(seconds)`
|
||
- `formatTimestamp(isoString)`
|
||
- `isTableText(text)`
|
||
- `parseTextTable(text)`
|
||
- `escapeHtml(text)`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
export {
|
||
generateUUID,
|
||
formatTime,
|
||
formatTimestamp,
|
||
isTableText,
|
||
parseTextTable,
|
||
escapeHtml
|
||
}
|
||
```
|
||
|
||
#### 2.2. `utils/file.utils.js`
|
||
**Функции** (строки 262-273, 984-1132):
|
||
- `downloadJSON(data, filename)`
|
||
- `loadFileAsJSON(file)` - новая функция для загрузки
|
||
- `loadFileAsText(file)` - новая функция
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
export {
|
||
downloadJSON,
|
||
loadFileAsJSON,
|
||
loadFileAsText
|
||
}
|
||
```
|
||
|
||
#### 2.3. `utils/validation.utils.js`
|
||
**Функции** (строки 823-860):
|
||
- `validateJSON(jsonString)`
|
||
- `validateLoginFormat(login)`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
export {
|
||
validateJSON,
|
||
validateLoginFormat
|
||
}
|
||
```
|
||
|
||
#### 2.4. `utils/dom.utils.js`
|
||
**Функции**:
|
||
- `showElement(id)`
|
||
- `hideElement(id)`
|
||
- `toggleElement(id)`
|
||
- `setElementText(id, text)`
|
||
- `showToast(message, type)` (строка 277-281)
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
export {
|
||
showElement,
|
||
hideElement,
|
||
toggleElement,
|
||
setElementText,
|
||
showToast
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 3: State Management (день 2)
|
||
|
||
**Цель**: Централизовать управление состоянием
|
||
|
||
#### 3.1. `state/appState.js`
|
||
**Содержимое** (строки 10-42):
|
||
```javascript
|
||
class AppState {
|
||
constructor() {
|
||
this.settings = { /* ... */ }
|
||
this.currentEnvironment = 'ift'
|
||
this.environments = {
|
||
ift: { /* ... */ },
|
||
psi: { /* ... */ },
|
||
prod: { /* ... */ }
|
||
}
|
||
}
|
||
|
||
// Геттеры
|
||
getCurrentEnv() { /* ... */ }
|
||
getCurrentEnvSettings() { /* ... */ }
|
||
|
||
// Сеттеры
|
||
setCurrentEnvironment(env) { /* ... */ }
|
||
updateSettings(settings) { /* ... */ }
|
||
|
||
// Persistence
|
||
saveToLocalStorage() { /* ... */ }
|
||
loadFromLocalStorage() { /* ... */ }
|
||
}
|
||
|
||
export default new AppState()
|
||
```
|
||
|
||
#### 3.2. `data/storage.js`
|
||
**Функции**:
|
||
- `saveEnvironmentData(env, data)`
|
||
- `loadEnvironmentData(env)`
|
||
- `clearEnvironmentData(env)`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
export {
|
||
saveEnvironmentData,
|
||
loadEnvironmentData,
|
||
clearEnvironmentData
|
||
}
|
||
```
|
||
|
||
#### 3.3. `data/defaults.js`
|
||
**Перенести из settings.js** (строки 1-70):
|
||
```javascript
|
||
export const defaultSettings = {
|
||
activeEnvironment: 'ift',
|
||
environments: { /* ... */ }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 4: Services (день 3)
|
||
|
||
**Цель**: Бизнес-логика и API взаимодействие
|
||
|
||
#### 4.1. `services/api-client.js`
|
||
**Действие**: Переместить существующий `api-client.js` в папку `services/`
|
||
|
||
**Обновить**:
|
||
```javascript
|
||
class BriefBenchAPI {
|
||
// ... существующий код
|
||
}
|
||
|
||
export default new BriefBenchAPI()
|
||
```
|
||
|
||
#### 4.2. `services/auth.service.js`
|
||
**Функции** (строки 60-140):
|
||
- `checkAuth()`
|
||
- `login(loginString)`
|
||
- `logout()`
|
||
- `isAuthenticated()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import api from './api-client.js'
|
||
|
||
export class AuthService {
|
||
async checkAuth() { /* ... */ }
|
||
async login(loginString) { /* ... */ }
|
||
logout() { /* ... */ }
|
||
isAuthenticated() { /* ... */ }
|
||
}
|
||
|
||
export default new AuthService()
|
||
```
|
||
|
||
#### 4.3. `services/settings.service.js`
|
||
**Функции** (строки 290-357):
|
||
- `loadSettingsFromServer()`
|
||
- `saveSettingsToServer(settings)`
|
||
- `extractEnvironmentSettings(envSettings)`
|
||
- `resetToDefaults()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import api from './api-client.js'
|
||
import appState from '../state/appState.js'
|
||
|
||
export class SettingsService {
|
||
async loadFromServer() { /* ... */ }
|
||
async saveToServer(settings) { /* ... */ }
|
||
extractEnvSettings(envSettings) { /* ... */ }
|
||
resetToDefaults() { /* ... */ }
|
||
}
|
||
|
||
export default new SettingsService()
|
||
```
|
||
|
||
#### 4.4. `services/query.service.js`
|
||
**Функции** (строки 861-1063):
|
||
- `buildRequestBody()`
|
||
- `sendQuery(environment, apiMode, requestBody)`
|
||
- `extractQuestions()`
|
||
- `loadRequestFromFile()`
|
||
- `loadResponseFromFile()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import api from './api-client.js'
|
||
import appState from '../state/appState.js'
|
||
|
||
export class QueryService {
|
||
buildRequestBody() { /* ... */ }
|
||
async sendQuery(env, apiMode, body) { /* ... */ }
|
||
extractQuestions() { /* ... */ }
|
||
async loadRequestFromFile() { /* ... */ }
|
||
async loadResponseFromFile() { /* ... */ }
|
||
}
|
||
|
||
export default new QueryService()
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 5: UI Components (день 4-5)
|
||
|
||
**Цель**: Разделить UI логику по компонентам
|
||
|
||
#### 5.1. `ui/auth.ui.js`
|
||
**Функции** (строки 77-132):
|
||
- `showLoginScreen()`
|
||
- `hideLoginScreen()`
|
||
- `setupLoginListeners()`
|
||
- `handleLoginSubmit()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import authService from '../services/auth.service.js'
|
||
|
||
export class AuthUI {
|
||
showLoginScreen() { /* ... */ }
|
||
hideLoginScreen() { /* ... */ }
|
||
setupListeners() { /* ... */ }
|
||
async handleLoginSubmit() { /* ... */ }
|
||
}
|
||
|
||
export default new AuthUI()
|
||
```
|
||
|
||
#### 5.2. `ui/loading.ui.js`
|
||
**Функции** (строки 1137-1145):
|
||
- `showLoading(message)`
|
||
- `hideLoading()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
export class LoadingUI {
|
||
show(message) { /* ... */ }
|
||
hide() { /* ... */ }
|
||
}
|
||
|
||
export default new LoadingUI()
|
||
```
|
||
|
||
#### 5.3. `ui/settings.ui.js`
|
||
**Функции** (строки 362-813):
|
||
- `openSettingsDialog()`
|
||
- `closeSettingsDialog()`
|
||
- `populateSettingsDialog()`
|
||
- `readSettingsFromDialog()`
|
||
- `toggleBackendSettings(show)`
|
||
- `saveSettings()`
|
||
- `resetSettings()`
|
||
- `exportSettings()`
|
||
- `importSettings()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import settingsService from '../services/settings.service.js'
|
||
import appState from '../state/appState.js'
|
||
|
||
export class SettingsUI {
|
||
open() { /* ... */ }
|
||
close() { /* ... */ }
|
||
populate() { /* ... */ }
|
||
read() { /* ... */ }
|
||
toggleBackendSettings(show) { /* ... */ }
|
||
async save() { /* ... */ }
|
||
async reset() { /* ... */ }
|
||
export() { /* ... */ }
|
||
async import() { /* ... */ }
|
||
setupListeners() { /* ... */ }
|
||
}
|
||
|
||
export default new SettingsUI()
|
||
```
|
||
|
||
#### 5.4. `ui/query-builder.ui.js`
|
||
**Функции** (строки 643-883):
|
||
- `showQueryBuilder()`
|
||
- `switchQueryMode(mode)`
|
||
- `validateJSON()`
|
||
- `setupQueryBuilderListeners()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import queryService from '../services/query.service.js'
|
||
|
||
export class QueryBuilderUI {
|
||
show() { /* ... */ }
|
||
switchMode(mode) { /* ... */ }
|
||
validateJSON() { /* ... */ }
|
||
setupListeners() { /* ... */ }
|
||
async handleSendQuery() { /* ... */ }
|
||
}
|
||
|
||
export default new QueryBuilderUI()
|
||
```
|
||
|
||
#### 5.5. `ui/questions-list.ui.js`
|
||
**Функции** (строки 1179-1273):
|
||
- `renderQuestionsList()`
|
||
- `selectAnswer(index)`
|
||
- `updateQuestionsCount()`
|
||
- `hasAnnotationsInDocs(docsSection)`
|
||
- `pluralize(count, one, few, many)`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import appState from '../state/appState.js'
|
||
|
||
export class QuestionsListUI {
|
||
render() { /* ... */ }
|
||
selectAnswer(index) { /* ... */ }
|
||
updateCount() { /* ... */ }
|
||
hasAnnotations(docsSection) { /* ... */ }
|
||
setupListeners() { /* ... */ }
|
||
}
|
||
|
||
export default new QuestionsListUI()
|
||
```
|
||
|
||
#### 5.6. `ui/answer-viewer.ui.js`
|
||
**Функции** (строки 1279-1443):
|
||
- `renderAnswer(index)`
|
||
- `renderAnswerBody(elementId, text)`
|
||
- `renderDocuments(containerId, docs, ...)`
|
||
- `toggleExpansion(id)`
|
||
- `switchTab(tabButton, tabId)`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import appState from '../state/appState.js'
|
||
import { formatTime, parseTextTable, escapeHtml } from '../utils/format.utils.js'
|
||
|
||
export class AnswerViewerUI {
|
||
render(index) { /* ... */ }
|
||
renderBody(elementId, text) { /* ... */ }
|
||
renderDocuments(containerId, docs, ...) { /* ... */ }
|
||
toggleExpansion(id) { /* ... */ }
|
||
switchTab(tabButton, tabId) { /* ... */ }
|
||
setupListeners() { /* ... */ }
|
||
}
|
||
|
||
export default new AnswerViewerUI()
|
||
```
|
||
|
||
#### 5.7. `ui/annotations.ui.js`
|
||
**Функции** (строки 1448-1615):
|
||
- `initAnnotationForAnswer(index)`
|
||
- `loadAnnotationsForAnswer(index)`
|
||
- `loadSectionAnnotation(section, data)`
|
||
- `loadDocumentAnnotations(section, subsection, docs)`
|
||
- `setupAnnotationListeners()`
|
||
- `updateCheckboxStyle(checkbox)`
|
||
- `saveAnnotationsDraft()`
|
||
|
||
**Экспорт**:
|
||
```javascript
|
||
import appState from '../state/appState.js'
|
||
import { saveEnvironmentData } from '../data/storage.js'
|
||
|
||
export class AnnotationsUI {
|
||
initForAnswer(index) { /* ... */ }
|
||
loadForAnswer(index) { /* ... */ }
|
||
loadSection(section, data) { /* ... */ }
|
||
loadDocuments(section, subsection, docs) { /* ... */ }
|
||
setupListeners() { /* ... */ }
|
||
saveDraft() { /* ... */ }
|
||
}
|
||
|
||
export default new AnnotationsUI()
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 6: Main Entry Point (день 6)
|
||
|
||
**Цель**: Создать точку входа и инициализацию
|
||
|
||
#### 6.1. `js/main.js`
|
||
```javascript
|
||
// Импорты
|
||
import appState from './state/appState.js'
|
||
import authService from './services/auth.service.js'
|
||
import settingsService from './services/settings.service.js'
|
||
import authUI from './ui/auth.ui.js'
|
||
import settingsUI from './ui/settings.ui.js'
|
||
import queryBuilderUI from './ui/query-builder.ui.js'
|
||
import questionsListUI from './ui/questions-list.ui.js'
|
||
import answerViewerUI from './ui/answer-viewer.ui.js'
|
||
import annotationsUI from './ui/annotations.ui.js'
|
||
|
||
// Инициализация приложения
|
||
async function initApp() {
|
||
// Load settings from server
|
||
await settingsService.loadFromServer()
|
||
appState.setCurrentEnvironment(appState.settings.activeEnvironment || 'ift')
|
||
|
||
// Load saved data for each environment
|
||
appState.loadFromLocalStorage()
|
||
|
||
// Setup all UI listeners
|
||
authUI.setupListeners()
|
||
settingsUI.setupListeners()
|
||
queryBuilderUI.setupListeners()
|
||
questionsListUI.setupListeners()
|
||
answerViewerUI.setupListeners()
|
||
annotationsUI.setupListeners()
|
||
|
||
// Setup environment tabs
|
||
setupEnvironmentTabs()
|
||
|
||
// Render initial state
|
||
updateUI()
|
||
}
|
||
|
||
// Setup environment tabs
|
||
function setupEnvironmentTabs() {
|
||
const tabs = document.querySelectorAll('.env-tab')
|
||
tabs.forEach(tab => {
|
||
tab.addEventListener('click', () => {
|
||
switchEnvironment(tab.dataset.env)
|
||
})
|
||
})
|
||
}
|
||
|
||
// Switch environment
|
||
function switchEnvironment(env) {
|
||
appState.setCurrentEnvironment(env)
|
||
updateEnvironmentTabs()
|
||
updateUI()
|
||
}
|
||
|
||
// Update environment tabs
|
||
function updateEnvironmentTabs() {
|
||
const tabs = document.querySelectorAll('.env-tab')
|
||
tabs.forEach(tab => {
|
||
if (tab.dataset.env === appState.currentEnvironment) {
|
||
tab.classList.add('active')
|
||
} else {
|
||
tab.classList.remove('active')
|
||
}
|
||
})
|
||
}
|
||
|
||
// Update UI based on current state
|
||
function updateUI() {
|
||
questionsListUI.render()
|
||
|
||
const env = appState.getCurrentEnv()
|
||
if (env.currentResponse && env.currentResponse.answers) {
|
||
answerViewerUI.render(env.currentAnswerIndex || 0)
|
||
} else {
|
||
queryBuilderUI.show()
|
||
}
|
||
}
|
||
|
||
// Entry point
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
const isAuthenticated = await authService.checkAuth()
|
||
|
||
if (isAuthenticated) {
|
||
await initApp()
|
||
}
|
||
})
|
||
```
|
||
|
||
#### 6.2. `js/config.js`
|
||
```javascript
|
||
// API Configuration
|
||
export const API_CONFIG = {
|
||
baseURL: '/api/v1',
|
||
timeout: 1800000 // 30 minutes
|
||
}
|
||
|
||
// UI Configuration
|
||
export const UI_CONFIG = {
|
||
defaultQueryMode: 'questions',
|
||
defaultWithDocs: true,
|
||
maxAnnotationLength: 5000
|
||
}
|
||
|
||
// Storage Keys
|
||
export const STORAGE_KEYS = {
|
||
token: 'briefBenchToken',
|
||
user: 'briefBenchUser',
|
||
settings: 'briefBenchSettings',
|
||
envData: (env) => `briefBenchData_${env}`,
|
||
annotations: 'briefBenchAnnotationsDraft'
|
||
}
|
||
|
||
// Constants
|
||
export const ENVIRONMENTS = ['ift', 'psi', 'prod']
|
||
export const API_MODES = ['bench', 'backend']
|
||
```
|
||
|
||
---
|
||
|
||
## Этап 7: Тестирование и оптимизация (день 7)
|
||
|
||
### 7.1. Ручное тестирование
|
||
- ✅ Авторизация работает
|
||
- ✅ Загрузка/сохранение настроек
|
||
- ✅ Отправка запросов (bench/backend)
|
||
- ✅ Отображение ответов
|
||
- ✅ Аннотации сохраняются
|
||
- ✅ Экспорт/импорт работает
|
||
- ✅ Переключение окружений
|
||
|
||
### 7.2. Проверка производительности
|
||
- Измерить время загрузки
|
||
- Проверить размер бандла
|
||
- Оптимизировать импорты
|
||
|
||
### 7.3. Очистка
|
||
- Удалить старый `app.js`
|
||
- Удалить `settings.js`
|
||
- Обновить `.gitignore` если нужно
|
||
|
||
---
|
||
|
||
## Миграционная стратегия
|
||
|
||
### Вариант 1: Постепенная миграция (РЕКОМЕНДУЕТСЯ)
|
||
|
||
**Подход**: Модули живут параллельно с монолитом
|
||
|
||
1. Создать папку `js/` с модулями
|
||
2. Оставить `app.js` работающим
|
||
3. Постепенно переносить функции
|
||
4. Тестировать после каждого этапа
|
||
5. Когда все готово - переключиться на `main.js`
|
||
|
||
**Преимущества**:
|
||
- Можно откатиться в любой момент
|
||
- Меньше риска
|
||
- Легче тестировать
|
||
|
||
**index.html во время миграции**:
|
||
```html
|
||
<!-- Старый код (работает) -->
|
||
<script src="settings.js"></script>
|
||
<script src="api-client.js"></script>
|
||
<script src="app.js"></script>
|
||
|
||
<!-- Новый код (тестируется) -->
|
||
<!-- <script type="module" src="js/main.js"></script> -->
|
||
```
|
||
|
||
### Вариант 2: Быстрая миграция
|
||
|
||
**Подход**: Переписать всё за раз
|
||
|
||
**НЕ РЕКОМЕНДУЕТСЯ** из-за высокого риска багов
|
||
|
||
---
|
||
|
||
## Чек-лист миграции
|
||
|
||
### Подготовка
|
||
- [ ] Создать ветку `feature/modularize-frontend`
|
||
- [ ] Сделать backup текущего app.js
|
||
- [ ] Создать структуру папок
|
||
|
||
### Этап 1: Утилиты
|
||
- [ ] Создать `utils/format.utils.js`
|
||
- [ ] Создать `utils/file.utils.js`
|
||
- [ ] Создать `utils/validation.utils.js`
|
||
- [ ] Создать `utils/dom.utils.js`
|
||
- [ ] Тесты: проверить что функции работают
|
||
|
||
### Этап 2: State
|
||
- [ ] Создать `state/appState.js`
|
||
- [ ] Создать `data/storage.js`
|
||
- [ ] Создать `data/defaults.js`
|
||
- [ ] Тесты: проверить геттеры/сеттеры
|
||
|
||
### Этап 3: Services
|
||
- [ ] Переместить `api-client.js` в `services/`
|
||
- [ ] Создать `services/auth.service.js`
|
||
- [ ] Создать `services/settings.service.js`
|
||
- [ ] Создать `services/query.service.js`
|
||
- [ ] Тесты: проверить API вызовы
|
||
|
||
### Этап 4: UI Components
|
||
- [ ] Создать `ui/auth.ui.js`
|
||
- [ ] Создать `ui/loading.ui.js`
|
||
- [ ] Создать `ui/settings.ui.js`
|
||
- [ ] Создать `ui/query-builder.ui.js`
|
||
- [ ] Создать `ui/questions-list.ui.js`
|
||
- [ ] Создать `ui/answer-viewer.ui.js`
|
||
- [ ] Создать `ui/annotations.ui.js`
|
||
- [ ] Тесты: проверить рендеринг
|
||
|
||
### Этап 5: Main
|
||
- [ ] Создать `js/main.js`
|
||
- [ ] Создать `js/config.js`
|
||
- [ ] Обновить `index.html`
|
||
- [ ] Тесты: полный цикл работы
|
||
|
||
### Этап 6: Очистка
|
||
- [ ] Удалить старый `app.js`
|
||
- [ ] Удалить `settings.js`
|
||
- [ ] Обновить документацию
|
||
- [ ] Code review
|
||
|
||
---
|
||
|
||
## Преимущества после рефакторинга
|
||
|
||
### Для разработки
|
||
- ✅ **Модульность**: Каждый файл отвечает за свою область
|
||
- ✅ **Тестируемость**: Легко покрыть модули unit-тестами
|
||
- ✅ **Читаемость**: Проще найти нужную функцию
|
||
- ✅ **Переиспользование**: Функции можно использовать в других проектах
|
||
|
||
### Для поддержки
|
||
- ✅ **Изоляция**: Баг в одном модуле не затронет другие
|
||
- ✅ **Масштабируемость**: Легко добавлять новые функции
|
||
- ✅ **Документация**: Каждый модуль самодокументируемый
|
||
- ✅ **Team work**: Разные разработчики могут работать над разными модулями
|
||
|
||
### Для производительности
|
||
- ✅ **Tree shaking**: Неиспользуемый код не попадет в бандл
|
||
- ✅ **Lazy loading**: Можно подгружать модули по требованию
|
||
- ✅ **Кеширование**: Браузер может кешировать отдельные модули
|
||
|
||
---
|
||
|
||
## Риски и митигация
|
||
|
||
### Риск 1: Поломка существующего функционала
|
||
**Митигация**: Постепенная миграция с тестированием после каждого этапа
|
||
|
||
### Риск 2: Увеличение времени загрузки (много файлов)
|
||
**Митигация**: Использовать bundler (Vite/Webpack) для production
|
||
|
||
### Риск 3: Проблемы совместимости браузеров
|
||
**Митигация**: ES6 модули поддерживаются всеми современными браузерами
|
||
|
||
### Риск 4: Сложность отладки
|
||
**Митигация**: Source maps и понятная структура папок
|
||
|
||
---
|
||
|
||
## Следующие шаги
|
||
|
||
1. **Обсудить план** с командой
|
||
2. **Выбрать стратегию миграции** (постепенная/быстрая)
|
||
3. **Создать ветку** для рефакторинга
|
||
4. **Начать с Этапа 1** (утилиты)
|
||
5. **Тестировать** после каждого этапа
|
||
6. **Code review** перед мержем
|
||
|
||
---
|
||
|
||
## Временные оценки
|
||
|
||
| Этап | Описание | Время |
|
||
|------|----------|-------|
|
||
| Этап 1 | Подготовка | 2 часа |
|
||
| Этап 2 | Утилиты | 3 часа |
|
||
| Этап 3 | State | 2 часа |
|
||
| Этап 4 | Services | 4 часа |
|
||
| Этап 5 | UI Components | 8 часов |
|
||
| Этап 6 | Main Entry | 2 часа |
|
||
| Этап 7 | Тестирование | 4 часа |
|
||
| **ИТОГО** | | **~25 часов** (3-4 рабочих дня) |
|
||
|
||
---
|
||
|
||
## Пример использования после рефакторинга
|
||
|
||
```javascript
|
||
// Было (app.js, строка 900):
|
||
async function handleSendQuery() {
|
||
const envSettings = getCurrentEnvSettings()
|
||
const env = getCurrentEnv()
|
||
const apiMode = envSettings.apiMode || 'bench'
|
||
// ... 50 строк кода
|
||
}
|
||
|
||
// Стало (js/ui/query-builder.ui.js):
|
||
import queryService from '../services/query.service.js'
|
||
import appState from '../state/appState.js'
|
||
import loadingUI from './loading.ui.js'
|
||
|
||
export class QueryBuilderUI {
|
||
async handleSendQuery() {
|
||
const envSettings = appState.getCurrentEnvSettings()
|
||
const apiMode = envSettings.apiMode || 'bench'
|
||
|
||
loadingUI.show('Отправка запроса...')
|
||
|
||
try {
|
||
const result = await queryService.sendQuery(
|
||
appState.currentEnvironment,
|
||
apiMode,
|
||
this.buildRequestBody()
|
||
)
|
||
|
||
// Update state
|
||
appState.getCurrentEnv().currentResponse = result.response
|
||
|
||
// Re-render
|
||
this.hide()
|
||
answerViewerUI.render(0)
|
||
} catch (error) {
|
||
showToast(error.message, 'error')
|
||
} finally {
|
||
loadingUI.hide()
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Преимущества**:
|
||
- Понятно где искать функцию
|
||
- Легко тестировать
|
||
- Явные зависимости
|
||
- Переиспользуемый код
|
||
|
||
---
|
||
|
||
*Автор: Claude Sonnet 4.5*
|
||
*Дата: 2025-12-25*
|