# Стиль кода ## Язык Python 3.12+ --- ## Типизация Полная типизация обязательна. Избегать использования Any. --- ## Размер функций Желательно не более 40 строк. Максимум около 80 строк. Если функция становится длиннее — разделить её. --- ## Размер файлов Желательно до 300 строк. Максимум около 500 строк. --- ## Именование Использовать понятные имена. Хорошо: load_image() split_grid() rotate_image() add_border() Плохо: proc() run2() tmp() --- ## Глобальное состояние Не использовать глобальные переменные. Не использовать Singleton. --- ## Магические числа Все значения должны быть вынесены в именованные константы либо параметры функций. --- ## Ошибки Использовать исключения. CLI должен завершаться ненулевым кодом возврата. Сообщения должны быть понятны пользователю. --- ## Логирование Использовать logging. Не использовать print() для отладки. --- ## Качество Следовать PEP8. Использовать Ruff. Не оставлять: - закомментированный код; - неиспользуемый код; - TODO без объяснения. --- ## Паттерны, выработанные в проекте ### Корректоры: мутация in-place Функции-корректоры (LLM, VLM, fixups) изменяют `ParsedBlock.content` на месте и возвращают `None`. ```python def fix_ocr_errors(blocks: list[ParsedBlock]) -> None: # ← None, не list for block in blocks: if block.label == "formula": block.content = _fix_formula_errors(block.content) ``` Протокол: ```python class Corrector(Protocol): def __call__(self, block: ParsedBlock) -> None: ... ``` ### Общие переменные окружения через `src/env.py` Любой модуль, читающий `.env`, использует `from src.env import load_env`. Не дублировать `_load_env()` в каждом файле. ### Multi-engine через фабрику ```python def _get_ocr_engine(name: str): if name == "surya": return surya_ocr_image, surya_ocr_batch, "Surya 2 VLM" return ocr_image, None, "PP-StructureV3" ``` Возвращает `(single_fn, batch_fn, display_name)`. Новый движок — добавить elif-ветку. ### Синглтоны для ML-моделей Допустимы ленивые синглтоны для тяжёлых объектов (модели, pipeline'ы), но через фабричную функцию: ```python _engine: OcrEngine | None = None def ocr_image(path: str) -> OcrPageResult: global _engine if _engine is None: _engine = OcrEngine() return _engine.process(path) ``` Не использовать модульные глобальные переменные напрямую. ### Периодический SOLID/DRY/KISS/YAGNI аудит После реализации крупной фичи — прогнать аудит по `.ai/SOLID_AUDIT.md`. Не накапливать технический долг.