ООП и чистый код: классы, проектирование, тестирование
В прошлых статьях вы научились писать функции, работать с файлами и JSON, а также выбирать структуры данных и строить простые алгоритмы (фильтрация, группировка, очереди для обхода ссылок). Следующий шаг — научиться собирать это в поддерживаемые программы, которые:
легко расширять (добавить новые поля, новые источники данных)
удобно тестировать (проверять логику без ручных запусков)
не превращать код в один большой файл и один большой циклЭтого обычно достигают через:
ООП (объектно-ориентированное программирование): классы, объекты, композиция
чистый код: понятная структура, ответственность, именование, минимальные побочные эффекты
тестирование: автоматическая проверка ключевой логикиЗачем ООП в задачах анализа данных и сбора структуры сайта
В задачах курса вы постоянно работаете с сущностями, которые удобно представить как объекты:
страница (URL, статус, заголовки, ссылки)
краулер (очередь, visited, ограничения)
хранилище результата (файл JSON, папка, формат)
парсер (как извлекать ссылки/поля из HTML)Функции остаются важнейшим инструментом, но классы помогают упаковать состояние и поведение вместе. Например, у краулера есть состояние (очередь, посещенные URL) и поведение (взять следующий URL, скачать, распарсить, сохранить).
Класс и объект: базовые понятия
Класс — шаблон (описание), как устроен объект.
Объект — конкретный экземпляр класса с собственными данными.
Атрибуты — данные объекта.
Методы — функции, которые принадлежат классу и работают с данными объекта.Минимальный пример класса
Что важно понять:
__init__ вызывается при создании объекта и инициализирует атрибуты.
self — ссылка на текущий объект.
c.value — состояние объекта.Модель данных: класс страницы
Для анализа структуры сайта удобно хранить данные страницы в одном объекте.
Вариант на обычном классе
Обратите внимание на прием links or []: он защищает от ситуации, когда в links пришло None.
Вариант через dataclasses
dataclasses — стандартный модуль Python, который помогает описывать “контейнеры данных” короче.
Почему field(default_factory=list) важен:
он создает новый пустой список для каждого объекта
это предотвращает классическую ошибку, когда один и тот же список случайно разделяют все экземплярыИнкапсуляция и “инварианты”
Инкапсуляция — идея, что объект сам отвечает за корректность своего состояния. На практике это означает: не только хранить данные, но и проверять их.
Например, вы хотите гарантировать, что status всегда целое число.
Если статус невозможно преобразовать к числу, вы получите исключение и быстрее заметите проблему в данных.
Часто удобно делать отдельную функцию нормализации (из предыдущих статей), а класс использовать как структуру результата.
Наследование и композиция
Наследование
Наследование — это “класс A расширяет класс B”. Например, можно сделать разные типы страниц.
Наследование полезно, когда:
у объектов реально общий “скелет”
вы точно понимаете, что подкласс всегда может использоваться там, где ждут базовый классКомпозиция
Композиция — это “объект состоит из других объектов”. Для задач курса композиция обычно безопаснее наследования.
Например, краулер использует загрузчик и парсер, а не “является” их разновидностью.
!Схема показывает, как разделить проект на компоненты, чтобы код было проще расширять и тестировать
Проектирование: разделяем ответственность
Один из главных принципов чистого кода — одна ответственность на один модуль/класс.
Для мини-проекта “сбор структуры сайта” типичная декомпозиция выглядит так:
Fetcher: получить содержимое страницы (в будущем — HTTP-запрос)
Parser: извлечь данные из содержимого (title, ссылки)
Crawler: управлять обходом (очередь, visited, лимиты)
Storage: сохранить результат (JSON)
Analyzer: посчитать метрики по готовому списку страницТак вы избегаете “комбайна”, где один класс делает всё.
Скелет классов (без реального HTTP и HTML)
Обратите внимание: Crawler не знает, как именно скачивать и парсить — он только управляет процессом. Это резко повышает тестируемость.
Чистый код: практические правила для вашего уровня
Именование
имена функций и переменных должны отвечать на вопрос “что это” или “что делает”
избегайте a, x, tmp, если это не счетчик циклаПример:
Маленькие функции и методы
Если функция не помещается в голове за один раз, её стоит разделить.
Практический ориентир:
функция делает один шаг пайплайна (нормализовать, отфильтровать, сгруппировать, сохранить)
метод класса делает одно действие (fetch, parse, crawl)Минимум побочных эффектов
Побочный эффект — когда функция “не только возвращает результат”, но и меняет внешний мир (файл, сеть, глобальные переменные, данные, переданные по ссылке).
Для тестирования полезно разделять:
чистую логику (нормализация, подсчет, группировка)
операции ввода/вывода (файлы, сеть)Не усложняйте ООП
Частая ошибка — делать классы ради классов. ООП оправдано, когда есть:
состояние, которое нужно хранить между вызовами
“роли” компонентов (Fetcher/Parser/Storage)
необходимость подменять реализацию (например, подставить тестовый Fetcher)Тестирование: что и как проверять
Зачем тесты в этом курсе
Тесты особенно полезны, когда вы:
чистите данные (много “грязных” случаев)
строите обход ссылок (важно не зациклиться)
сохраняете структуру данных в ожидаемом форматеЧто тестировать в первую очередь
| Часть проекта | Почему важно | Пример теста |
|---|---|---|
| Нормализация | много краевых случаев | None, пустые строки, пробелы |
| Извлечение ссылок | легко ошибиться в правилах | относительные/абсолютные ссылки |
| Удаление дублей | критично для краулинга | set, порядок |
| Подсчеты/группировки | основа анализа | частоты статусов |
Минимальный пример на unittest
unittest — стандартный модуль Python. Его достаточно для задач курса.
Что здесь закрепляется:
тесты проверяют именно логику, без файлов и сети
каждый тест — один понятный случай
при ошибке вы сразу видите, какой сценарий сломалсяТестирование классов через подмену зависимостей
Ключевой прием проектирования под тесты — внедрение зависимостей: Crawler получает fetcher и parser снаружи. Тогда в тесте вы можете подставить “фейковую” реализацию.
Смысл: вы проверяете алгоритм обхода и работу с visited, не делая никаких реальных запросов.
Как это связано со следующими шагами курса
После этой темы вам будет проще перейти к “настоящему” проекту:
добавить реальный Fetcher (HTTP-запросы)
добавить реальный Parser (извлечение ссылок и полей)
сохранять результат через Storage в JSON
анализировать итоговый JSON (статусы, глубина, частоты, ошибки)При этом структура проекта останется понятной, а ключевая логика будет защищена тестами.
Полезные официальные источники
Документация Python: классы
Документация Python: dataclasses
Документация Python: unittest
Документация Python: typing
PEP 8 — стиль кода