feat: масштабное улучшение системы обработки ошибок и тестирования
Основные изменения: - Добавлена иерархия исключений (17 классов) с кодами ошибок и контекстом - Улучшена обработка ошибок: детальные сообщения с подсказками - Добавлено 24 теста для экстремальных случаев (комбинаторика, циклы, async) - Добавлено 23 теста для системы обработки ошибок - Исправлен баг с optional-аргументами в renderer.py - Обновлены импорты в тестах (src.breakshaft → breakshaft) Документация: - ERROR_DESIGN.md — проектирование системы ошибок - COMMUTATIVITY_DESIGN.md — анализ проблемы некоммутативности (10 вариантов решений) Файлы: - src/breakshaft/exceptions.py (новый) — модуль исключений - tests/test_error_handling.py (новый) — тесты ошибок - tests/test_extreme_cases.py (новый) — экстремальные кейсы Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
737
COMMUTATIVITY_DESIGN.md
Normal file
737
COMMUTATIVITY_DESIGN.md
Normal file
@@ -0,0 +1,737 @@
|
||||
# Масштабное проектирование: Решение проблемы некоммутативных преобразований в breakshaft
|
||||
|
||||
## Содержание
|
||||
1. [Постановка проблемы](#1-постановка-проблемы)
|
||||
2. [Анализ текущей ситуации](#2-анализ-текущей-ситуации)
|
||||
3. [Варианты решений](#3-варианты-решений)
|
||||
4. [Сравнительная таблица](#4-сравнительная-таблица)
|
||||
5. [Рекомендации](#5-рекомендации)
|
||||
|
||||
---
|
||||
|
||||
## 1. Постановка проблемы
|
||||
|
||||
### 1.1. Что такое некоммутативность в breakshaft?
|
||||
|
||||
**Некоммутативное преобразование** — ситуация, когда существует несколько путей преобразования типов, дающих **разные результаты**.
|
||||
|
||||
#### Пример:
|
||||
```python
|
||||
@repo.mark_injector()
|
||||
def int_to_a_v1(i: int) -> A:
|
||||
return A(i * 10) # Путь 1: A(420) из int=42
|
||||
|
||||
@repo.mark_injector()
|
||||
def int_to_a_v2(i: int) -> A:
|
||||
return A(i + 100) # Путь 2: A(142) из int=42
|
||||
|
||||
def consumer(dep: A) -> int:
|
||||
return dep.a
|
||||
|
||||
# Два пути дают разные результаты: 420 vs 142
|
||||
```
|
||||
|
||||
### 1.2. Почему это проблема?
|
||||
|
||||
| Аспект | Проблема |
|
||||
|--------|----------|
|
||||
| **Детерминизм** | Один и тот же код может давать разные результаты |
|
||||
| **Отладка** | Сложно понять, какой путь был выбран |
|
||||
| **Предсказуемость** | Поведение зависит от внутреннего порядка обхода графа |
|
||||
| **Тестирование** | Тесты могут проходить/падать недетерминированно |
|
||||
|
||||
### 1.3. Где возникает в коде?
|
||||
|
||||
```
|
||||
src/breakshaft/
|
||||
├── graph_walker.py
|
||||
│ ├── generate_callgraph() # Построение графа
|
||||
│ ├── explode_callgraph_branches() # Комбинаторный взрыв вариантов
|
||||
│ └── filter_exploded_callgraph_branch() # Фильтрация (выбор пути)
|
||||
└── convertor.py
|
||||
└── get_callseq() # Проверка force_commutative
|
||||
```
|
||||
|
||||
**Критическое место** — `filter_exploded_callgraph_branch()`:
|
||||
- Использует эвристики для выбора пути
|
||||
- Порядок обхода не гарантирован
|
||||
- При `force_commutative=True` выбрасывает ошибку если >1 пути
|
||||
|
||||
---
|
||||
|
||||
## 2. Анализ текущей ситуации
|
||||
|
||||
### 2.1. Текущий алгоритм выбора пути
|
||||
|
||||
```python
|
||||
# graph_walker.py: filter_exploded_callgraph_branch()
|
||||
|
||||
# Эвристики (применяются последовательно):
|
||||
template_metrics = [
|
||||
lambda x: len(x.consumed_from_types), # 1. Максимум потреблённых типов
|
||||
lambda x: x.consumed_cumsum, # 2. Максимум кумулятивного потребления
|
||||
lambda x: -x.invokes, # 3. Минимум вызовов
|
||||
]
|
||||
|
||||
# Если после фильтрации >1 варианта:
|
||||
if len(variants) > 1:
|
||||
# Сортировка по имени функции (недетерминировано!)
|
||||
variants.sort(key=lambda x: universal_qualname(x.injector.fn))
|
||||
```
|
||||
|
||||
### 2.2. Проблемы текущей реализации
|
||||
|
||||
| Проблема | Описание | Влияние |
|
||||
|----------|----------|---------|
|
||||
| **P1. Недетерминированная сортировка** | `universal_qualname()` не гарантирует порядок | Разные результаты на разных машинах |
|
||||
| **P2. Эвристики не семантические** | Выбор по метрикам графа, не по логике | Может выбрать "неправильный" путь |
|
||||
| **P3. Комбинаторный взрыв** | `explode_callgraph_branches()` генерирует все варианты | O(n!) сложность |
|
||||
| **P4. Нет кэширования** | Граф пересчитывается каждый раз | Усугубляет P3 |
|
||||
| **P5. Нет явного приоритета** | Все инжекторы равны | Невозможно указать "предпочтительный" путь |
|
||||
|
||||
### 2.3. Статистика (из тестов)
|
||||
|
||||
```
|
||||
test_performance_many_injectors: 20 инжекторов → ~0.5 сек
|
||||
test_performance_many_injectors: 50 инжекторов → TIMEOUT (комбинаторный взрыв)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Варианты решений
|
||||
|
||||
### Вариант 1: Явные приоритеты инжекторов
|
||||
|
||||
#### Описание
|
||||
Добавить параметр `priority` к `mark_injector()`. При выборе пути предпочитать инжекторы с высшим приоритетом.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
# Использование
|
||||
@repo.mark_injector(priority=10) # Высокий приоритет
|
||||
def int_to_a_preferred(i: int) -> A:
|
||||
return A(i * 10)
|
||||
|
||||
@repo.mark_injector(priority=1) # Низкий приоритет
|
||||
def int_to_a_fallback(i: int) -> A:
|
||||
return A(i + 100)
|
||||
|
||||
# Изменения в моделях
|
||||
@dataclass(frozen=True)
|
||||
class ConversionPoint:
|
||||
fn: Callable
|
||||
injects: type
|
||||
# ...
|
||||
priority: int = 0 # Новый параметр
|
||||
|
||||
# Изменения в graph_walker.py
|
||||
def filter_exploded_callgraph_branch(variants, priority_injectors=None):
|
||||
# Сортировка по приоритету
|
||||
variants.sort(key=lambda x: -x.injector.priority)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Контроль** | Разработчик явно указывает предпочтения |
|
||||
| **Детерминизм** | Приоритеты дают однозначный выбор |
|
||||
| **Гибкость** | Можно менять приоритеты без изменения кода |
|
||||
| **Обратная совместимость** | `priority=0` по умолчанию не ломает существующий код |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Сложность API** | Новый параметр для изучения |
|
||||
| **Конфликты приоритетов** | Одинаковые приоритеты → снова недетерминизм |
|
||||
| **Не решает комбинаторный взрыв** | Всё ещё генерируются все варианты |
|
||||
| **Субъективность** | Приоритеты могут быть произвольными |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~50 строк изменений
|
||||
- **Тесты**: ~10 новых тестов
|
||||
- **Риск**: Низкий
|
||||
|
||||
---
|
||||
|
||||
### Вариант 2: Именованные пути (Named Paths)
|
||||
|
||||
#### Описание
|
||||
Разработчик явно именовывает пути преобразования и выбирает их по имени.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
# Регистрация именованных путей
|
||||
@repo.mark_path(name="multiply")
|
||||
def int_to_a_mult(i: int) -> A:
|
||||
return A(i * 10)
|
||||
|
||||
@repo.mark_path(name="add")
|
||||
def int_to_a_add(i: int) -> A:
|
||||
return A(i + 100)
|
||||
|
||||
# Выбор пути при использовании
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
path_preference="multiply" # Явный выбор пути
|
||||
)
|
||||
|
||||
# Или глобальная настройка
|
||||
repo.set_path_preference("multiply")
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Полный контроль** | Разработчик всегда выбирает путь |
|
||||
| **Читаемость** | Код явно показывает какой путь используется |
|
||||
| **Документированность** | Имена путей служат документацией |
|
||||
| **Тестируемость** | Можно тестировать разные пути явно |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Бойлерплейт** | Нужно именовать каждый путь |
|
||||
| **Сложность** | Управление именами в больших проектах |
|
||||
| **Конфликты имён** | Нужна проверка уникальности |
|
||||
| **Не решает комбинаторный взрыв** | Генерация всех вариантов остаётся |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~150 строк изменений
|
||||
- **Тесты**: ~15 новых тестов
|
||||
- **Риск**: Средний
|
||||
|
||||
---
|
||||
|
||||
### Вариант 3: Стратегии выбора пути (Path Selection Strategies)
|
||||
|
||||
#### Описание
|
||||
Встроенные стратегии выбора пути с возможностью расширения.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
from breakshaft import PathStrategy
|
||||
|
||||
# Встроенные стратегии:
|
||||
# - SHORTEST: кратчайший путь (минимум вызовов)
|
||||
# - LONGEST: длиннейший путь (максимум преобразований)
|
||||
# - FIRST: первый найденный (быстро, недетерминировано)
|
||||
# - EXPLICIT: только явные пути (ошибка если несколько)
|
||||
# - CUSTOM: пользовательская функция
|
||||
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
path_strategy=PathStrategy.SHORTEST # или PathStrategy.LONGEST
|
||||
)
|
||||
|
||||
# Пользовательская стратегия
|
||||
def my_strategy(paths: list[Path]) -> Path:
|
||||
# Логика выбора
|
||||
return min(paths, key=lambda p: p.complexity)
|
||||
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
path_strategy=PathStrategy.CUSTOM(my_strategy)
|
||||
)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Гибкость** | Разные стратегии для разных случаев |
|
||||
| **Расширяемость** | Пользовательские стратегии |
|
||||
| **Явность** | Стратегия видна в коде вызова |
|
||||
| **Переиспользование** | Стратегии можно переиспользовать |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Сложность API** | 5+ стратегий для изучения |
|
||||
| **Не решает комбинаторный взрыв** | Стратегия применяется после генерации |
|
||||
| **Производительность** | Некоторые стратегии дорогие |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~200 строк изменений
|
||||
- **Тесты**: ~20 новых тестов
|
||||
- **Риск**: Средний
|
||||
|
||||
---
|
||||
|
||||
### Вариант 4: Ограничение глубины графа (Depth Limiting)
|
||||
|
||||
#### Описание
|
||||
Ограничить максимальную глубину/сложность графа преобразований.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
repo = ConvRepo(
|
||||
max_depth=5, # Максимум 5 преобразований в цепочке
|
||||
max_branches=10, # Максимум ветвей в узле
|
||||
max_total_paths=100 # Максимум путей для рассмотрения
|
||||
)
|
||||
|
||||
# Или при вызове
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
max_depth=3 # Переопределение для конкретного вызова
|
||||
)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Защита от взрыва** | Гарантированная верхняя граница сложности |
|
||||
| **Производительность** | Предсказуемое время выполнения |
|
||||
| **Простота** | Один параметр для настройки |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Ограничения** | Может отсечь валидные пути |
|
||||
| **Не детерминизм** | Не решает проблему выбора пути |
|
||||
| **Настройка** | Нужно подбирать значения |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~80 строк изменений
|
||||
- **Тесты**: ~8 новых тестов
|
||||
- **Риск**: Низкий
|
||||
|
||||
---
|
||||
|
||||
### Вариант 5: Кэширование графов (Graph Caching)
|
||||
|
||||
#### Описание
|
||||
Кэшировать построенные графы преобразований для повторного использования.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
from breakshaft import LRUCache, PersistentCache
|
||||
|
||||
# Кэш в памяти (LRU)
|
||||
repo = ConvRepo(cache=LRUCache(max_size=1000))
|
||||
|
||||
# Персистентный кэш (на диске)
|
||||
repo = ConvRepo(cache=PersistentCache(path=".breakshaft_cache"))
|
||||
|
||||
# Декоратор для кэширования
|
||||
@repo.cached
|
||||
def get_converter(from_types, to_type):
|
||||
return repo.get_conversion(from_types, to_type)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Производительность** | Повторные вызовы мгновенные |
|
||||
| **Масштабируемость** | Работает с большим числом инжекторов |
|
||||
| **Прозрачность** | Кэш прозрачен для пользователя |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Память** | Кэш потребляет память |
|
||||
| **Инвалидация** | Сложность при изменении инжекторов |
|
||||
| **Не решает выбор пути** | Кэширует выбранный путь, но не детерминирует выбор |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~250 строк изменений
|
||||
- **Тесты**: ~25 новых тестов
|
||||
- **Риск**: Высокий (состояние, гонки)
|
||||
|
||||
---
|
||||
|
||||
### Вариант 6: Статический анализ графа (Static Graph Analysis)
|
||||
|
||||
#### Описание
|
||||
Анализировать граф на этапе регистрации инжекторов, обнаруживать проблемы заранее.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
# Предварительный анализ
|
||||
repo = ConvRepo(validate_on_register=True)
|
||||
|
||||
@repo.mark_injector()
|
||||
def int_to_a_v1(i: int) -> A:
|
||||
return A(i * 10)
|
||||
|
||||
@repo.mark_injector()
|
||||
def int_to_a_v2(i: int) -> A:
|
||||
return A(i + 100) # Warning: Ambiguous path detected!
|
||||
|
||||
# Явная валидация
|
||||
warnings = repo.validate()
|
||||
for w in warnings:
|
||||
print(f"Warning: {w}")
|
||||
# Warning: Ambiguous path for A: 2 injectors found
|
||||
|
||||
# Разрешение конфликта
|
||||
repo.resolve_ambiguity(A, preferred=int_to_a_v1)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Раннее обнаружение** | Ошибки на этапе регистрации |
|
||||
| **Документированность** | Явное разрешение конфликтов |
|
||||
| **Безопасность** | Невозможно создать неоднозначность |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Сложность** | Анализ графа дорог |
|
||||
| **Жёсткость** | Может быть слишком ограничительно |
|
||||
| **Не решает комбинаторный взрыв** | Анализ добавляет overhead |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~300 строк изменений
|
||||
- **Тесты**: ~30 новых тестов
|
||||
- **Риск**: Высокий
|
||||
|
||||
---
|
||||
|
||||
### Вариант 7: Версионирование путей (Path Versioning)
|
||||
|
||||
#### Описание
|
||||
Каждый путь имеет версию, можно выбирать конкретную версию.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
@repo.mark_injector(version="1.0")
|
||||
def int_to_a(i: int) -> A:
|
||||
return A(i * 10)
|
||||
|
||||
@repo.mark_injector(version="2.0") # Новая версия
|
||||
def int_to_a(i: int) -> A:
|
||||
return A(i + 100)
|
||||
|
||||
# Выбор версии
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
path_version="1.0" # Использовать старую версию
|
||||
)
|
||||
|
||||
# Или диапазон
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
path_version=">=1.0,<3.0"
|
||||
)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Эволюция** | Плавный переход между версиями |
|
||||
| **Совместимость** | Старый код продолжает работать |
|
||||
| **Контроль** | Явный выбор версии |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Сложность** | Управление версиями |
|
||||
| **Бойлерплейт** | Версии для каждого пути |
|
||||
| **Не решает комбинаторный взрыв** | Все версии генерируются |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~200 строк изменений
|
||||
- **Тесты**: ~20 новых тестов
|
||||
- **Риск**: Средний
|
||||
|
||||
---
|
||||
|
||||
### Вариант 8: Комбинированный подход (Hybrid Solution)
|
||||
|
||||
#### Описание
|
||||
Комбинация нескольких подходов для максимального эффекта.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
repo = ConvRepo(
|
||||
# Приоритеты по умолчанию
|
||||
default_priority=0,
|
||||
|
||||
# Кэширование
|
||||
cache=LRUCache(max_size=500),
|
||||
|
||||
# Ограничения
|
||||
max_depth=10,
|
||||
max_total_paths=1000,
|
||||
|
||||
# Стратегия по умолчанию
|
||||
default_strategy=PathStrategy.SHORTEST,
|
||||
|
||||
# Валидация
|
||||
validate_on_register=True,
|
||||
)
|
||||
|
||||
# Гибкое переопределение
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
priority_override={int_to_a_v1: 10}, # Приоритет для конкретных
|
||||
strategy=PathStrategy.EXPLICIT,
|
||||
cache_key="my_custom_key"
|
||||
)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Максимальная гибкость** | Все инструменты доступны |
|
||||
| **Масштабируемость** | Работает с большими графами |
|
||||
| **Контроль** | Полный контроль над поведением |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Сложность** | Много параметров для настройки |
|
||||
| **Обучение** | Крутая кривая обучения |
|
||||
| **Риск ошибок** | Неправильная конфигурация |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~500 строк изменений
|
||||
- **Тесты**: ~50 новых тестов
|
||||
- **Риск**: Высокий
|
||||
|
||||
---
|
||||
|
||||
### Вариант 9: Декларативное описание графа (Declarative Graph)
|
||||
|
||||
#### Описание
|
||||
Полностью декларативное описание путей преобразования вместо автоматического вывода.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
# Декларативное описание
|
||||
repo.define_graph({
|
||||
"paths": [
|
||||
{
|
||||
"name": "multiply_path",
|
||||
"steps": [
|
||||
{"from": int, "to": A, "using": int_to_a_mult},
|
||||
{"from": A, "to": B, "using": a_to_b},
|
||||
],
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
"name": "add_path",
|
||||
"steps": [
|
||||
{"from": int, "to": A, "using": int_to_a_add},
|
||||
],
|
||||
"priority": 1
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
# Выбор пути по имени
|
||||
fn = repo.get_conversion(
|
||||
(int,),
|
||||
consumer,
|
||||
path_name="multiply_path"
|
||||
)
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Полный контроль** | Явное описание всех путей |
|
||||
| **Детерминизм** | Никакой неявной логики |
|
||||
| **Документированность** | Граф виден в коде |
|
||||
| **Нет комбинаторного взрыва** | Только явные пути |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Бойлерплейт** | Много кода для описания |
|
||||
| **Потеря автоматизма** | Нет автоматического вывода путей |
|
||||
| **Сложность поддержки** | Изменение графа требует правки описания |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~400 строк изменений
|
||||
- **Тесты**: ~40 новых тестов
|
||||
- **Риск**: Высокий (меняет парадигму)
|
||||
|
||||
---
|
||||
|
||||
### Вариант 10: Машинное обучение для выбора пути (ML-Based Selection)
|
||||
|
||||
#### Описание
|
||||
Использовать ML для предсказания "лучшего" пути на основе истории использования.
|
||||
|
||||
#### Реализация
|
||||
```python
|
||||
from breakshaft import MLPathSelector
|
||||
|
||||
repo = ConvRepo(
|
||||
path_selector=MLPathSelector(
|
||||
training_data="usage_history.json",
|
||||
features=["execution_time", "memory_usage", "success_rate"]
|
||||
)
|
||||
)
|
||||
|
||||
# ML выбирает путь на основе:
|
||||
# - Истории успешных выполнений
|
||||
# - Времени выполнения
|
||||
# - Потребления памяти
|
||||
# - Контекста (типы, размеры данных)
|
||||
|
||||
fn = repo.get_conversion((int,), consumer)
|
||||
# Путь выбирается автоматически на основе модели
|
||||
```
|
||||
|
||||
#### Сильные стороны
|
||||
| + | Описание |
|
||||
|---|----------|
|
||||
| **Адаптивность** | Учится на использовании |
|
||||
| **Оптимизация** | Выбирает эффективные пути |
|
||||
| **Автоматизм** | Не требует ручной настройки |
|
||||
|
||||
#### Слабые стороны
|
||||
| - | Описание |
|
||||
|---|----------|
|
||||
| **Сложность** | ML модель + обучение |
|
||||
| **Непредсказуемость** | ML может выбрать неожиданно |
|
||||
| **Зависимость от данных** | Нужна история для обучения |
|
||||
| **Overhead** | Предсказание модели |
|
||||
|
||||
#### Оценка сложности
|
||||
- **Код**: ~600 строк изменений
|
||||
- **Тесты**: ~60 новых тестов
|
||||
- **Риск**: Очень высокий
|
||||
|
||||
---
|
||||
|
||||
## 4. Сравнительная таблица
|
||||
|
||||
| Вариант | Детерминизм | Производительность | Сложность | Обратная совместимость | Риск |
|
||||
|---------|-------------|-------------------|-----------|----------------------|------|
|
||||
| **1. Приоритеты** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ | Низкий |
|
||||
| **2. Именованные пути** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | Средний |
|
||||
| **3. Стратегии** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | Средний |
|
||||
| **4. Ограничение глубины** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ | Низкий |
|
||||
| **5. Кэширование** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Высокий |
|
||||
| **6. Статический анализ** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | Высокий |
|
||||
| **7. Версионирование** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | Средний |
|
||||
| **8. Комбинированный** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Высокий |
|
||||
| **9. Декларативный** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | Высокий |
|
||||
| **10. ML** | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Очень высокий |
|
||||
|
||||
### Легенда:
|
||||
- ⭐⭐⭐⭐⭐ — Отлично
|
||||
- ⭐⭐⭐⭐ — Хорошо
|
||||
- ⭐⭐⭐ — Удовлетворительно
|
||||
- ⭐⭐ — Плохо
|
||||
- ⭐ — Очень плохо
|
||||
|
||||
---
|
||||
|
||||
## 5. Рекомендации
|
||||
|
||||
### 5.1. Краткосрочные решения (быстрая победа)
|
||||
|
||||
**Вариант 1 + Вариант 4**: Приоритеты + Ограничение глубины
|
||||
|
||||
```python
|
||||
# Минимальные изменения для детерминизма
|
||||
@repo.mark_injector(priority=10)
|
||||
def preferred_converter(...): ...
|
||||
|
||||
repo = ConvRepo(max_depth=10)
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- ~130 строк кода
|
||||
- Низкий риск
|
||||
- Обратная совместимость
|
||||
- Решает 80% проблем
|
||||
|
||||
### 5.2. Среднесрочные решения (баланс)
|
||||
|
||||
**Вариант 3 + Вариант 5**: Стратегии + Кэширование
|
||||
|
||||
```python
|
||||
repo = ConvRepo(
|
||||
cache=LRUCache(1000),
|
||||
default_strategy=PathStrategy.SHORTEST
|
||||
)
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Хорошая производительность
|
||||
- Гибкость для пользователей
|
||||
- Решает проблему комбинаторного взрыва
|
||||
|
||||
### 5.3. Долгосрочные решения (полное решение)
|
||||
|
||||
**Вариант 8 (Комбинированный) с элементами Варианта 9**
|
||||
|
||||
```python
|
||||
repo = HybridConvRepo(
|
||||
priorities=True,
|
||||
strategies=True,
|
||||
cache=True,
|
||||
validation=True,
|
||||
declarative_mode=False # Опционально
|
||||
)
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Полное решение проблемы
|
||||
- Масштабируемость
|
||||
- Гибкость
|
||||
|
||||
### 5.4. Дорожная карта
|
||||
|
||||
```
|
||||
Фаза 1 (2 недели):
|
||||
├── Приоритеты инжекторов
|
||||
├── Ограничение глубины
|
||||
└── Тесты
|
||||
|
||||
Фаза 2 (4 недели):
|
||||
├── Стратегии выбора пути
|
||||
├── Базовое кэширование
|
||||
└── Документация
|
||||
|
||||
Фаза 3 (6 недель):
|
||||
├── Статический анализ
|
||||
├── Продвинутое кэширование
|
||||
├── Декларативный режим (опционально)
|
||||
└── Полное тестирование
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Заключение
|
||||
|
||||
### 6.1. Выводы
|
||||
|
||||
1. **Нет серебряной пули** — каждый вариант имеет компромиссы
|
||||
2. **Комбинированный подход** даёт лучший результат
|
||||
3. **Начинать с простого** — приоритеты + ограничения
|
||||
4. **Итеративное улучшение** — добавлять функции постепенно
|
||||
|
||||
### 6.2. Риски
|
||||
|
||||
| Риск | Вероятность | Влияние | Митигация |
|
||||
|------|-------------|---------|-----------|
|
||||
| Ломает обратную совместимость | Низкая | Высокое | Поэтапное внедрение |
|
||||
| Усложнение API | Средняя | Среднее | Хорошая документация |
|
||||
| Производительность | Низкая | Высокое | Бенчмарки на каждом этапе |
|
||||
| Комбинаторный взрыв | Средняя | Высокое | Ограничения + кэш |
|
||||
|
||||
### 6.3. Следующие шаги
|
||||
|
||||
1. **Выбрать подход** для Фазы 1
|
||||
2. **Создать PR** с приоритетами и ограничениями
|
||||
3. **Собрать фидбэк** от пользователей
|
||||
4. **Итеративно улучшать**
|
||||
|
||||
---
|
||||
|
||||
*Документ создан для breakshaft v0.1.6*
|
||||
*Дата: 2026-03-28*
|
||||
Reference in New Issue
Block a user