Паттерны проектирования и чистый код на Python

Этот курс научит вас писать читаемый и поддерживаемый код на Python, используя стандарты PEP 8 и принципы SOLID. Вы освоите применение классических паттернов проектирования, адаптированных под специфику языка, и научитесь строить гибкую архитектуру приложений.

1. Фундамент чистого кода: PEP 8, аннотации типов и принципы SOLID

Фундамент чистого кода: PEP 8, аннотации типов и принципы SOLID

Добро пожаловать на курс «Паттерны проектирования и чистый код на Python». Мы начинаем наше путешествие не с самих паттернов, а с фундамента, на котором строится любое качественное приложение. Паттерны — это архитектурные решения, но даже самая гениальная архитектура рухнет, если кирпичи, из которых она сложена, будут кривыми и хрупкими.

В этой статье мы разберем три кита профессиональной разработки на Python: стандарт оформления кода PEP 8, систему аннотации типов и принципы объектно-ориентированного проектирования SOLID.

> Любой дурак может написать код, который поймет компьютер. Хорошие программисты пишут код, который поймут люди. — Мартин Фаулер Refactoring: Improving the Design of Existing Code

PEP 8: Конституция Python-разработчика

Python славится своей читаемостью. Во многом это заслуга PEP 8 — документа, описывающего соглашения о том, как писать код на Python. Это не просто рекомендации, это стандарт индустрии. Если вы пишете код «как вам удобно», игнорируя PEP 8, ваш код будет чужеродным для любого другого Python-разработчика.

Основные правила именования

Именование переменных и функций — это самая частая проблема новичков. В Python приняты следующие конвенции:

  • snake_case (змеиный регистр): используется для имен переменных, функций, методов и модулей. Все буквы строчные, слова разделяются подчеркиванием.
  • CamelCase (верблюжий регистр): используется для имен классов. Каждое слово начинается с заглавной буквы, разделителей нет.
  • UPPER_CASE (верхний регистр): используется для констант.
  • Пример правильного именования:

    Форматирование и структура

    Чистота кода также зависит от визуального восприятия:

    * Отступы: Используйте 4 пробела на уровень отступа. Никогда не смешивайте табуляцию и пробелы. * Длина строки: Ограничивайте длину строки до 79 символов. Это позволяет открывать несколько файлов рядом в редакторе. * Импорты: Импорты всегда должны быть в начале файла. Сначала стандартные библиотеки, затем сторонние, затем локальные модули проекта.

    Соблюдать эти правила вручную сложно. Поэтому в профессиональной среде используют линтеры и форматтеры, такие как flake8, black или ruff. Они автоматически проверяют и исправляют ваш код.

    Аннотации типов: от динамики к надежности

    Python — язык с динамической типизацией. Это значит, что переменная может хранить сначала число, а потом строку. Это удобно для скриптов, но в больших проектах это источник ошибок. Начиная с версии 3.5, Python поддерживает Type Hints (подсказки типов).

    Аннотации типов не влияют на выполнение кода (интерпретатор их игнорирует), но они критически важны для:

  • Документации: Читая код, вы сразу понимаете, что функция принимает и возвращает.
  • IDE: Среда разработки лучше подсказывает методы и автодополняет код.
  • Статического анализа: Инструмент mypy может найти ошибки типов до запуска программы.
  • Примеры использования

    Рассмотрим функцию без аннотаций:

    Что будет, если передать в name число? Программа упадет с ошибкой во время выполнения. Теперь добавим типы:

    Теперь мы явно сказали: name должно быть строкой, и функция вернет строку. Для более сложных структур используется модуль typing (или встроенные типы в новых версиях Python).

    Использование аннотаций типов — это стандарт современного Python-кода, особенно при работе с паттернами проектирования, где важны интерфейсы и контракты взаимодействия.

    Принципы SOLID

    SOLID — это акроним пяти принципов объектно-ориентированного программирования, сформулированных Робертом Мартином. Эти принципы помогают создавать гибкие и поддерживаемые системы.

    !Пять столпов принципов SOLID, удерживающих архитектуру приложения.

    Разберем каждый принцип на примерах Python.

    S — Single Responsibility Principle (Принцип единственной ответственности)

    Класс должен иметь только одну причину для изменения.

    Это значит, что класс должен делать только одну вещь. Если класс UserHandler и сохраняет пользователя в базу данных, и отправляет ему приветственное письмо, и валидирует данные — это нарушение SRP.

    Плохо:

    Хорошо:

    Теперь, если изменится логика отправки почты, мы не трогаем класс User или UserRepository.

    O — Open/Closed Principle (Принцип открытости/закрытости)

    Программные сущности должны быть открыты для расширения, но закрыты для модификации.

    Вы должны иметь возможность добавлять новый функционал, не переписывая старый работающий код.

    Представьте, что у нас есть класс, рассчитывающий площади фигур. Если для добавления новой фигуры (например, треугольника) вам нужно лезть в метод calculate_area и добавлять туда новый if/elif, вы нарушаете OCP.

    Решение: Использовать полиморфизм. Создать абстрактный базовый класс Shape с методом area, и наследовать от него конкретные фигуры.

    L — Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

    Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.

    Если класс B наследуется от класса A, то мы должны иметь возможность использовать B везде, где используется A, и код не должен ломаться.

    Классический пример нарушения: Квадрат, наследуемый от Прямоугольника. В геометрии квадрат — это прямоугольник. В программировании — нет. Если код ожидает, что у прямоугольника можно менять ширину и высоту независимо, то подстановка квадрата (у которого изменение ширины меняет и высоту) сломает логику.

    Совет: Если вам нужно проверять тип объекта (isinstance), чтобы код работал правильно, вы, скорее всего, нарушаете LSP.

    I — Interface Segregation Principle (Принцип разделения интерфейса)

    Клиенты не должны зависеть от методов, которые они не используют.

    Лучше создать много узкоспециализированных интерфейсов, чем один «божественный» интерфейс общего назначения.

    Представьте интерфейс SmartDevice, у которого есть методы print(), scan() и fax(). Если мы создаем класс SimplePrinter, который умеет только печатать, нам придется реализовать методы scan и fax (например, выбрасывая ошибки), что плохо.

    Решение: Разбить на Printer, Scanner, FaxMachine. SimplePrinter будет наследовать только Printer.

    D — Dependency Inversion Principle (Принцип инверсии зависимостей)

    Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракций.

    Это ключевой принцип для создания слабосвязанной архитектуры. Ваш бизнес-код (высокий уровень) не должен знать, какую именно базу данных (низкий уровень) вы используете (PostgreSQL, MySQL или файл).

    Плохо:

    Хорошо:

    Теперь наш выключатель может работать с любым устройством, реализующим интерфейс Switchable.

    Заключение

    Соблюдение PEP 8 делает ваш код читаемым. Аннотации типов делают его надежным и предсказуемым. Принципы SOLID делают его гибким и расширяемым. Это база, без которой изучение паттернов проектирования превратится в простое заучивание схем без понимания их сути.

    В следующей статье мы перейдем к порождающим паттернам и разберем, как правильно создавать объекты в сложных системах.

    2. Порождающие паттерны: Фабричный метод, Абстрактная фабрика и Синглтон в Python

    Порождающие паттерны: Фабричный метод, Абстрактная фабрика и Синглтон в Python

    В предыдущей статье мы заложили фундамент чистого кода, разобрав PEP 8, аннотации типов и принципы SOLID. Теперь, когда мы знаем, как писать качественный код внутри классов, пришло время поговорить о том, как эти классы создавать и связывать друг с другом.

    Создание объектов — это не просто вызов конструктора. В больших системах бесконтрольное использование оператора создания экземпляра (в Python это вызов класса MyClass()) приводит к жесткой связности (tight coupling). Если ваш код наводнен прямыми вызовами конкретных классов, то замена одной библиотеки на другую или добавление новой логики превратится в ад рефакторинга.

    Здесь на сцену выходят порождающие паттерны (Creational Patterns). Они абстрагируют процесс инстанцирования (создания экземпляров), делая систему независимой от того, как именно создаются, компонуются и представляются её объекты.

    Сегодня мы разберем три классических паттерна: Синглтон (Одиночка), Фабричный метод и Абстрактную фабрику.

    > Проектирование — это не то, как предмет выглядит, а то, как он работает. — Стив Джобс The Guts of a New Machine

    Синглтон (Singleton): Один в поле воин

    Начнем с самого простого, но и самого спорного паттерна.

    Суть паттерна: Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.

    !Иллюстрация принципа единственного экземпляра и глобальной точки доступа.

    Зачем это нужно?

    Чаще всего Синглтон используется для доступа к общим ресурсам:

  • Подключение к базе данных: Нерационально создавать новое соединение на каждый запрос.
  • Логгер: Все части программы должны писать логи в один и тот же файл или поток.
  • Конфигурация приложения: Настройки загружаются один раз и используются везде.
  • Реализация на Python

    В Python есть несколько способов реализовать Синглтон. Самый «питоничный» способ — это использование модуля (так как модули в Python импортируются один раз), но классическая реализация через класс выглядит так:

    Метод __new__ отвечает за создание объекта, а __init__ — за его инициализацию. Переопределяя __new__, мы перехватываем момент создания и возвращаем уже существующий объект, если он есть.

    Подводные камни

    Синглтон часто называют анти-паттерном по нескольким причинам: * Нарушение SRP (Single Responsibility Principle): Класс управляет и своей логикой, и своим жизненным циклом. * Глобальное состояние: Синглтон по сути является глобальной переменной, что делает поведение программы менее предсказуемым. * Сложность тестирования: Мокировать (подменять) синглтоны в юнит-тестах сложно, так как состояние сохраняется между тестами.

    Фабричный метод (Factory Method): Делегирование создания

    Представьте, что вы разрабатываете приложение для логистики. Сначала у вас есть только грузовики. Весь ваш код пестрит созданием объектов Truck.

    Потом бизнес растет, и появляются морские перевозки. Вам нужно внедрить класс Ship. Если вы просто добавите if/else по всему коду, вы нарушите принцип открытости/закрытости (Open/Closed Principle).

    Суть паттерна: Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать.

    !Структура паттерна Фабричный метод: разделение создателя и продукта.

    Реализация на Python

    Давайте реализуем пример с логистикой, используя абстрактные классы.

    Преимущества

    * Избавляет от привязки к конкретным классам продуктов. Клиентский код работает с интерфейсом Transport. * Выделяет код создания продуктов в одно место. Упрощает поддержку. * Упрощает добавление новых продуктов. Хотите добавить самолет? Создайте Plane и AirLogistics. Существующий код менять не нужно.

    Абстрактная фабрика (Abstract Factory): Семейства объектов

    Фабричный метод создает один продукт. А что делать, если продукту нужны связанные с ним компоненты? Например, вы делаете кроссплатформенный интерфейс. У вас есть кнопки (Button) и чекбоксы (Checkbox).

    * В стиле Windows кнопки квадратные, чекбоксы плоские. * В стиле MacOS кнопки закругленные, чекбоксы объемные.

    Важно, чтобы программа не перепутала стили: нельзя рисовать кнопку Windows рядом с чекбоксом MacOS.

    Суть паттерна: Предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.

    !Абстрактная фабрика гарантирует совместимость создаваемых объектов.

    Реализация на Python

    Отличие от Фабричного метода

    Это самый частый вопрос на собеседованиях.

    * Фабричный метод используется, когда нужно создавать один тип продукта, и мы хотим делегировать этот процесс подклассам. * Абстрактная фабрика используется, когда есть семейства продуктов (Кнопка + Чекбокс + Скроллбар), которые должны работать вместе.

    Часто Абстрактная фабрика реализуется с помощью набора Фабричных методов.

    Когда использовать порождающие паттерны?

    Не стоит сразу бросаться переписывать весь код на фабрики. Это усложнит программу (появится много новых классов). Используйте их, когда:

  • Вы не знаете заранее, какие типы объектов понадобятся вашей программе (Фабричный метод).
  • Система должна быть независимой от процесса создания продуктов (Все порождающие паттерны).
  • Вам нужно гарантировать, что объекты используются только в определенных сочетаниях (Абстрактная фабрика).
  • Вам нужен строгий контроль над глобальными ресурсами (Синглтон).
  • Заключение

    Порождающие паттерны помогают нам соблюдать принципы SOLID, в частности принцип единственной ответственности (вынося код создания объектов) и принцип открытости/закрытости (позволяя добавлять новые типы продуктов без изменения старого кода).

    В следующей статье мы перейдем к структурным паттернам и узнаем, как собирать сложные объекты из простых, используя Адаптер, Декоратор и Фасад.

    3. Структурные паттерны: Декораторы, Адаптер и Фасад для организации кода

    Структурные паттерны: Декораторы, Адаптер и Фасад для организации кода

    В предыдущих модулях мы заложили прочный фундамент: научились писать чистый код, соблюдая PEP 8 и SOLID, и разобрали, как правильно создавать объекты с помощью порождающих паттернов. Теперь перед нами встает новая задача: как собрать эти объекты в единую, работающую структуру, подобно тому, как из кирпичей и балок собирают стены и перекрытия здания.

    Добро пожаловать в мир структурных паттернов (Structural Patterns). Эти паттерны отвечают за построение удобных иерархий классов и связей между объектами. Их главная цель — сделать так, чтобы изменение одной части системы не требовало переписывания всего остального кода.

    Сегодня мы разберем три фундаментальных паттерна, которые решают самые частые проблемы архитектуры: несовместимость интерфейсов (Адаптер), необходимость динамического расширения функционала (Декоратор) и сложность взаимодействия с большими подсистемами (Фасад).

    > Любую проблему в компьютерных науках можно решить с помощью еще одного уровня косвенности. — Дэвид Уиллер David Wheeler)

    Адаптер (Adapter): Искусство перевода

    Представьте, что вы приехали в другую страну с ноутбуком, но вилка вашего зарядного устройства не подходит к местной розетке. Вы не будете перепаивать розетку в отеле и не станете менять вилку на зарядке. Вы используете переходник.

    В программировании Адаптер делает то же самое: он позволяет объектам с несовместимыми интерфейсами работать вместе.

    !Адаптер выступает посредником, транслирующим вызовы клиента в формат, понятный стороннему сервису.

    Когда использовать?

  • Интеграция легаси-кода: У вас есть старая система, которую нельзя менять, и новый код, который должен с ней работать.
  • Сторонние библиотеки: Вы хотите использовать класс из библиотеки, но его интерфейс не совпадает с тем, что ожидает ваш код.
  • Реализация на Python

    Допустим, наша система работает с данными в формате JSON, но нам нужно интегрировать стороннюю библиотеку аналитики, которая принимает только XML.

    Обратите внимание: клиентский код (client_code) даже не знает, что под капотом происходит конвертация в XML. Он работает с интерфейсом AnalyticsLibrary, соблюдая принцип инверсии зависимостей (DIP из SOLID).

    Декоратор (Decorator): Матрешка функциональности

    Часто нам нужно добавить объекту новые возможности, не изменяя его класс. Наследование здесь может сыграть злую шутку. Представьте, что у вас есть класс Coffee. Вы хотите добавить молоко. Создаете CoffeeWithMilk. Хотите сахар? CoffeeWithSugar. А если и то, и другое? CoffeeWithMilkAndSugar. Количество классов начнет расти экспоненциально.

    Декоратор позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обертки».

    Отличие от декораторов Python

    Важно не путать паттерн Декоратор и синтаксис декораторов в Python (@decorator). Синтаксис @ обычно используется для функций и методов, тогда как паттерн проектирования — это способ компоновки объектов классов. Хотя концептуально они похожи (оборачивание логики), паттерн реализуется через классы-обертки.

    Реализация на Python

    Реализуем систему уведомлений. Базовый класс отправляет Email, а декораторы добавляют отправку в Telegram и Slack.

    Преимущества: * Большая гибкость, чем у наследования. * Можно добавлять и удалять обязанности на лету. * Соблюдается принцип единственной ответственности (каждый класс делает свое дело).

    Фасад (Facade): Простой интерфейс для сложной системы

    Ваш дом — это сложная система: проводка, трубы, фундамент, крыша. Но для взаимодействия с ним у вас есть «фасад»: двери, окна, выключатели. Вам не нужно знать, как течет ток по проводам, чтобы включить свет.

    Паттерн Фасад предоставляет простой интерфейс к сложной системе классов, библиотеке или фреймворку.

    Когда использовать?

    Когда вам нужно упростить взаимодействие с библиотекой, имеющей десятки классов, из которых вам нужно только два-три метода.

    Реализация на Python

    Представьте сложную систему конвертации видео. Она состоит из множества классов: VideoFile, AudioMixer, BitrateReader, CodecFactory.

    Клиенту не нужно знать о кодеках, битрейтах и буферах. Он просто вызывает метод convert. Это и есть суть Фасада.

    Сравнение паттернов

    Иногда новички путают эти паттерны, так как все они «обертывают» классы. Давайте разграничим их:

    | Паттерн | Цель | Пример из жизни | | :--- | :--- | :--- | | Адаптер | Меняет интерфейс одного объекта под другой, чтобы они могли работать вместе. | Переходник для розетки. | | Декоратор | Добавляет объекту новые обязанности, не меняя его интерфейс. | Одежда на человеке (человек остается человеком, но теперь ему тепло). | | Фасад | Упрощает интерфейс, скрывая сложную систему. | Кнопка «Запуск» на автомобиле (скрывает работу стартера, инжектора и свечей). |

    Заключение

    Структурные паттерны помогают бороться с энтропией в проекте. * Используйте Адаптер, когда нужно подружить несовместимое. * Применяйте Декоратор, когда нужно гибко расширять функционал без наследования. * Стройте Фасады, чтобы прятать сложность системы от клиентского кода.

    В следующей статье мы перейдем к самой обширной группе — поведенческим паттернам, которые отвечают за эффективную коммуникацию между объектами и распределение обязанностей.

    4. Поведенческие паттерны: Стратегия, Наблюдатель и цепочка обязанностей

    Поведенческие паттерны: Стратегия, Наблюдатель и Цепочка обязанностей

    Мы прошли долгий путь. Мы научились писать чистый код, соблюдая PEP 8 и принципы SOLID. Мы научились правильно создавать объекты с помощью порождающих паттернов и собирать их в структуры с помощью структурных паттернов. Но архитектура — это не только статика. Это еще и динамика.

    Как объекты общаются друг с другом? Кто кому передает управление? Как избежать хаоса, когда сотни объектов начинают взаимодействовать одновременно?

    В этом модуле мы разберем поведенческие паттерны (Behavioral Patterns). Они отвечают за эффективную коммуникацию между объектами и распределение обязанностей. Мы рассмотрим три паттерна, которые спасают код от бесконечных условий if/else и жесткой связности: Стратегия, Наблюдатель и Цепочка обязанностей.

    > Программирование — это понимание. Если вы не можете объяснить это просто, вы это не понимаете. — Кент Бек Extreme Programming Explained

    Стратегия (Strategy): Взаимозаменяемые алгоритмы

    Представьте, что вы пишете навигатор. Сначала он прокладывает маршрут только для автомобилей. Потом вы добавляете пешеходные маршруты. Затем — общественный транспорт. Ваш класс Navigator разрастается, метод build_route превращается в монстра с кучей условий.

    Суть паттерна: Выделить семейство алгоритмов, поместить каждый из них в отдельный класс и сделать их взаимозаменяемыми. Контекст (навигатор) не знает деталей алгоритма, он просто вызывает метод «проложить маршрут».

    Когда использовать?

  • У вас есть много похожих классов, отличающихся только поведением.
  • Вам нужно менять алгоритм работы динамически (на лету).
  • Вы хотите избавиться от длинных условных операторов, выбирающих вариант поведения.
  • Реализация на Python

    Давайте реализуем систему оплаты в интернет-магазине. Пользователь может платить картой или через PayPal.

    Pythonic way: В Python функции являются объектами первого класса. Если стратегия состоит только из одного метода, можно не создавать классы, а передавать сами функции.

    Наблюдатель (Observer): Держите меня в курсе

    Этот паттерн вы встречаете каждый день. Подписка на YouTube-канал, уведомление о новом письме, рассылка новостей.

    Суть паттерна: Создает механизм подписки, позволяющий одним объектам (наблюдателям) следить за событиями, происходящими в других объектах (издателях).

    !Издатель оповещает всех своих подписчиков о наступлении события.

    Проблема жесткой связности

    Без этого паттерна объектам пришлось бы постоянно опрашивать друг друга: «Есть новости? А сейчас?». Это тратит ресурсы. Наблюдатель реализует принцип «Не звоните нам, мы сами вам позвоним».

    Реализация на Python

    Создадим систему новостей. Агентство публикует новости, а подписчики (СМС-сервис, Email-рассылка) получают их.

    Этот паттерн критически важен для создания событийно-ориентированных систем (Event-Driven Architecture).

    Цепочка обязанностей (Chain of Responsibility): Конвейер обработки

    Представьте, что вы звоните в техподдержку. Сначала вы попадаете на робота. Если он не может помочь, он переключает вас на оператора первой линии. Если проблема сложная, оператор переключает на инженера.

    Суть паттерна: Позволяет передавать запросы последовательно по цепочке обработчиков. Каждый обработчик решает, может ли он обработать запрос сам, или его нужно передать дальше.

    !Запрос движется по цепочке, пока не найдется тот, кто сможет его обработать.

    Когда использовать?

  • Нужно обрабатывать запросы разными способами, но заранее неизвестно, какой именно запрос придет.
  • Нужно выполнять обработку в строгом порядке (например, валидация данных -> авторизация -> логирование -> сохранение).
  • Реализация на Python

    Реализуем систему обработки веб-запроса. Сначала проверяем авторизацию, потом наличие данных.

    Обратите внимание: AuthHandler прерывает цепочку, если проверка не пройдена. DatabaseHandler — последний в цепи, он выполняет действие и не вызывает super().handle().

    Сравнение и выводы

    Поведенческие паттерны помогают организовать логику приложения так, чтобы классы были сфокусированы на своих задачах (SRP) и слабо зависели друг от друга.

    | Паттерн | Ключевая идея | Пример из жизни | | :--- | :--- | :--- | | Стратегия | «Я хочу иметь возможность менять инструмент в руке». | Выбор оружия в игре или способа сортировки списка. | | Наблюдатель | «Сообщи мне, когда что-то изменится». | Подписка на журнал, уведомления в чате. | | Цепочка обязанностей | «Передай дальше, если не можешь сделать сам». | Бюрократия: от секретаря к начальнику отдела, затем к директору. |

    Внедрение этих паттернов делает ваш код чище, тестируемее и понятнее. Вместо огромных процедурных функций вы получаете набор небольших классов, каждый из которых легко проверить и заменить.

    На этом наш курс подходит к концу. Мы разобрали фундамент (PEP 8, SOLID), научились создавать объекты (Порождающие), структурировать их (Структурные) и управлять их поведением (Поведенческие). Эти знания — ваш арсенал для написания профессионального кода на Python.

    5. Рефакторинг, тестирование и антипаттерны: от хаоса к порядку

    Рефакторинг, тестирование и антипаттерны: от хаоса к порядку

    Мы прошли долгий путь. Мы изучили фундамент чистого кода, разобрали порождающие, структурные и поведенческие паттерны. Теперь у вас есть мощный арсенал инструментов. Но любой, даже самый красивый код, со временем имеет свойство «гнить». Требования меняются, дедлайны горят, и в архитектуре появляются трещины.

    В этой завершающей статье курса мы поговорим о том, как поддерживать здоровье вашего кода на длинной дистанции. Мы разберем антипаттерны (то, как делать не надо), рефакторинг (как исправлять то, что уже сделано) и тестирование (как убедиться, что исправления ничего не сломали).

    > Программное обеспечение похоже на энтропию: его трудно ухватить, оно ничего не весит и подчиняется второму закону термодинамики; то есть оно всегда увеличивается. — Норман Огюстин Augustine's Laws

    Антипаттерны: Врага нужно знать в лицо

    Антипаттерны — это распространенные решения, которые кажутся эффективными в краткосрочной перспективе, но приводят к проблемам в будущем. Если паттерны проектирования — это лекарства, то антипаттерны — это болезни.

    !Сравнение запутанного кода (спагетти) и структурированной архитектуры.

    Рассмотрим самые опасные антипаттерны в Python-разработке.

    God Object (Божественный объект)

    Это класс, который знает слишком много и делает слишком много. Он нарушает принцип единственной ответственности (SRP) и часто содержит тысячи строк кода.

    Пример: Класс SystemManager, который и читает конфиги, и подключается к БД, и отправляет почту, и рендерит HTML.

    Как лечить: Декомпозиция. Разбивайте монстра на маленькие классы: ConfigLoader, DatabaseConnector, EmailService.

    Spaghetti Code (Спагетти-код)

    Код с запутанной структурой управления, злоупотребляющий операторами if, циклами и исключениями. Поток выполнения скачет по файлу так, что его невозможно отследить.

    Как лечить: Использование структурных и поведенческих паттернов (например, Цепочка обязанностей или Стратегия), выделение методов.

    Magic Numbers (Магические числа)

    Использование чисел или строк прямо в коде без объяснения, что они значат.

    Плохо:

    Хорошо:

    Poltergeist (Полтергейст)

    Классы, которые существуют только для того, чтобы вызвать метод другого класса, и не несут никакой своей логики. Они создают лишний шум в архитектуре.

    Как лечить: Удалять посредников (Inline Class), если они не выполняют роль Адаптера или Фасада.

    Рефакторинг: Искусство уборки

    Рефакторинг — это процесс изменения внутренней структуры программы без изменения её внешнего поведения. Цель — сделать код понятнее и дешевле в поддержке.

    Когда проводить рефакторинг?

  • Правило трех: Первый раз вы просто пишете код. Второй раз вы копипастите (морщитесь, но делаете). Третий раз вы видите дублирование — пора рефакторить.
  • Перед добавлением новой фичи: Если код грязный, сначала уберитесь, потом стройте новое.
  • Во время код-ревью: Если коллега не понимает ваш код, это сигнал к рефакторингу.
  • Основные техники рефакторинга

    1. Extract Method (Выделение метода)

    Если у вас есть длинная функция, разбейте её на части.

    До:

    После:

    2. Rename Variable (Переименование переменной)

    Имя переменной должно отвечать на вопрос «что я храню?». Переменные d, temp, data — враги читаемости.

    3. Inline Temp (Встраивание временной переменной)

    Если переменная используется только один раз для хранения результата выражения, избавьтесь от неё.

    Тестирование: Ваша страховка

    Рефакторинг без тестов — это игра в русскую рулетку. Вы меняете код, надеясь, что ничего не сломали. Тесты дают вам уверенность.

    Пирамида тестирования

  • Unit Tests (Модульные тесты): Проверяют изолированные кусочки кода (функции, классы). Их должно быть больше всего. Они быстрые.
  • Integration Tests (Интеграционные тесты): Проверяют, как модули работают вместе (например, работа сервиса с базой данных).
  • E2E Tests (End-to-End): Проверяют систему целиком, как её видит пользователь.
  • Инструменты в Python

    Стандартом де-факто в современном Python является pytest. Он лаконичнее встроенного unittest.

    Пример теста на pytest:

    Mocking (Мокирование)

    Когда мы тестируем класс, который зависит от внешних сервисов (например, отправка SMS), мы не хотим реально отправлять SMS при каждом запуске тестов. Мы используем «моки» — заглушки.

    Цикломатическая сложность: Измеряем хаос

    Как понять, насколько сложен ваш код? Для этого существует метрика — Цикломатическая сложность. Она показывает количество линейно независимых маршрутов через программный код.

    Формула вычисления цикломатической сложности:

    Где — цикломатическая сложность, — количество ребер (переходов) в графе потока управления, — количество узлов (блоков кода), — количество компонентов связности (обычно 1 для одной функции).

    Простыми словами: каждый if, for, while, except увеличивает сложность.

    * 1-5: Отлично, код прост. * 6-10: Нормально, но стоит быть внимательным. * 10+: Пора делать рефакторинг (Extract Method).

    В Python для измерения сложности используется библиотека radon.

    Заключение курса

    Поздравляю! Вы завершили курс «Паттерны проектирования и чистый код на Python». Мы начали с PEP 8, научились типизировать код, освоили принципы SOLID. Мы разобрали, как создавать гибкие системы с помощью Фабрик и Стратегий, как связывать их через Адаптеры и Фасады.

    Теперь вы знаете, что:

  • Чистый код — это вежливость по отношению к коллегам и будущему себе.
  • Паттерны — это проверенные решения типовых проблем, а не догма.
  • Рефакторинг и тесты — это ежедневная гигиена проекта.
  • Не бойтесь переписывать код. Бойтесь писать код, который страшно трогать. Удачи в ваших проектах!