← Назад к вопросам

Как снизить вероятность ошибки при проектировании ООП?

3.0 Senior🔥 131 комментариев
#Python Core#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как снизить вероятность ошибки при проектировании ООП

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

1. Следуй SOLID принципам

S — Single Responsibility Principle

# ❌ ПЛОХО: один класс делает много
class User:
    def __init__(self, name):
        self.name = name
    
    def save_to_db(self):  # БД
        pass
    
    def send_email(self):  # Email
        pass
    
    def log_activity(self):  # Логирование
        pass

# ✅ ХОРОШО: разделённая ответственность
class User:
    def __init__(self, name):
        self.name = name

class UserRepository:
    def save(self, user):
        # Сохранение в БД
        pass

class UserEmailService:
    def send_welcome_email(self, user):
        # Отправка email
        pass

class UserActivityLogger:
    def log_login(self, user):
        # Логирование
        pass

O — Open/Closed Principle

# ❌ ПЛОХО: нужно менять класс при расширении
class PaymentProcessor:
    def process(self, payment):
        if payment.type == "credit_card":
            # Обработка карты
            pass
        elif payment.type == "paypal":
            # Обработка PayPal
            pass
        # Каждый новый способ — изменение класса

# ✅ ХОРОШО: открыто для расширения, закрыто для изменения
from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process(self, amount):
        pass

class CreditCardProcessor(PaymentProcessor):
    def process(self, amount):
        print(f"Processing credit card: ${amount}")

class PayPalProcessor(PaymentProcessor):
    def process(self, amount):
        print(f"Processing PayPal: ${amount}")

class ApplePayProcessor(PaymentProcessor):
    def process(self, amount):
        print(f"Processing Apple Pay: ${amount}")

# Новый способ оплаты = новый класс, не изменение существующего

L — Liskov Substitution Principle

# ❌ ПЛОХО: нарушение контракта
class Bird:
    def fly(self):
        pass

class Penguin(Bird):
    def fly(self):  # Пингвины не летают!
        raise NotImplementedError("Penguins cannot fly")

# ✅ ХОРОШО: правильная иерархия
class Animal:
    pass

class FlyingBird(Animal):
    def fly(self):
        pass

class Bird(Animal):
    pass

class Penguin(Bird):
    def swim(self):
        pass  # Пингвины плавают

I — Interface Segregation Principle

# ❌ ПЛОХО: большой интерфейс
class Worker:
    def work(self):
        pass
    
    def eat_lunch(self):
        pass
    
    def manage_team(self):
        pass

class Robot(Worker):  # Робот не ест!
    def work(self):
        pass
    
    def eat_lunch(self):
        raise NotImplementedError()
    
    def manage_team(self):
        raise NotImplementedError()

# ✅ ХОРОШО: маленькие специализированные интерфейсы
from abc import ABC, abstractmethod

class Workable(ABC):
    @abstractmethod
    def work(self):
        pass

class Eatable(ABC):
    @abstractmethod
    def eat_lunch(self):
        pass

class Manageable(ABC):
    @abstractmethod
    def manage_team(self):
        pass

class Employee(Workable, Eatable, Manageable):
    def work(self):
        pass
    
    def eat_lunch(self):
        pass
    
    def manage_team(self):
        pass

class Robot(Workable):
    def work(self):
        pass
    # Робот не наследует Eatable и Manageable

D — Dependency Inversion Principle

# ❌ ПЛОХО: зависимость от конкретной реализации
class EmailService:
    def send(self, email):
        pass

class UserRegistration:
    def __init__(self):
        self.email_service = EmailService()  # Привязка к конкретному классу
    
    def register(self, user):
        self.email_service.send(user.email)

# ✅ ХОРОШО: зависимость от абстракции
from abc import ABC, abstractmethod

class NotificationService(ABC):
    @abstractmethod
    def send(self, recipient, message):
        pass

class EmailService(NotificationService):
    def send(self, recipient, message):
        pass

class SMSService(NotificationService):
    def send(self, recipient, message):
        pass

class UserRegistration:
    def __init__(self, notification_service: NotificationService):
        # Инъекция зависимости
        self.notification_service = notification_service
    
    def register(self, user):
        self.notification_service.send(user.email, "Welcome!")

# Легко менять реализацию
registration_email = UserRegistration(EmailService())
registration_sms = UserRegistration(SMSService())

2. Используй Type Hints

from typing import Optional, List, Dict
from abc import ABC, abstractmethod

# ❌ ПЛОХО: нет типов
class UserService:
    def get_user(self, id):
        # Какой тип id? Что возвращается?
        pass
    
    def find_users(self, criteria):
        pass

# ✅ ХОРОШО: явные типы
class UserService:
    def get_user(self, user_id: int) -> Optional[User]:
        # Явно: принимаем int, возвращаем User или None
        pass
    
    def find_users(self, criteria: Dict[str, str]) -> List[User]:
        # Явно: принимаем словарь, возвращаем список User
        pass

3. Используй абстрактные базовые классы (ABC)

from abc import ABC, abstractmethod

# Правильный контракт для подклассов
class DatabaseConnection(ABC):
    @abstractmethod
    def connect(self):
        pass
    
    @abstractmethod
    def execute(self, query: str):
        pass
    
    @abstractmethod
    def close(self):
        pass

class PostgresConnection(DatabaseConnection):
    def connect(self):
        print("Connecting to PostgreSQL")
    
    def execute(self, query: str):
        print(f"Executing: {query}")
    
    def close(self):
        print("Closing PostgreSQL connection")

class MySQLConnection(DatabaseConnection):
    def connect(self):
        print("Connecting to MySQL")
    
    def execute(self, query: str):
        print(f"Executing: {query}")
    
    def close(self):
        print("Closing MySQL connection")

# Функция работает с любой реализацией
def query_database(db: DatabaseConnection, query: str):
    db.connect()
    db.execute(query)
    db.close()

4. Композиция вместо наследования

# ❌ ПЛОХО: глубокая иерархия наследования
class Vehicle:
    def drive(self):
        pass

class Car(Vehicle):
    def drive(self):
        pass

class ElectricCar(Car):
    pass

class FastElectricCar(ElectricCar):  # Громоздко!
    pass

# ✅ ХОРОШО: композиция
class Engine(ABC):
    @abstractmethod
    def start(self):
        pass

class GasEngine(Engine):
    def start(self):
        print("Gas engine started")

class ElectricEngine(Engine):
    def start(self):
        print("Electric engine started")

class Car:
    def __init__(self, engine: Engine):
        self.engine = engine
    
    def drive(self):
        self.engine.start()
        print("Driving...")

# Легко комбинировать
gas_car = Car(GasEngine())
electric_car = Car(ElectricEngine())

5. Тестирование и mock'и

import unittest
from unittest.mock import Mock, patch

class UserService:
    def __init__(self, repository):
        self.repository = repository
    
    def get_user(self, user_id: int):
        return self.repository.find(user_id)

class TestUserService(unittest.TestCase):
    def test_get_user(self):
        # Используем mock вместо реальной БД
        mock_repository = Mock()
        mock_repository.find.return_value = {"id": 1, "name": "John"}
        
        service = UserService(mock_repository)
        user = service.get_user(1)
        
        self.assertEqual(user["name"], "John")
        mock_repository.find.assert_called_once_with(1)

6. Валидация в конструкторе

# ❌ ПЛОХО: объект в неправильном состоянии
class User:
    def __init__(self, name, email, age):
        self.name = name
        self.email = email
        self.age = age

user = User("", "invalid", -5)  # Неправильные данные!

# ✅ ХОРОШО: валидация при создании
class User:
    def __init__(self, name: str, email: str, age: int):
        if not name or len(name) < 2:
            raise ValueError("Name must be at least 2 characters")
        if "@" not in email:
            raise ValueError("Invalid email")
        if age < 0 or age > 150:
            raise ValueError("Invalid age")
        
        self.name = name
        self.email = email
        self.age = age

# Теперь объект всегда в валидном состоянии
try:
    user = User("", "invalid", -5)
except ValueError as e:
    print(f"Error: {e}")

7. Immutable objects где возможно

from dataclasses import dataclass

# ❌ ПЛОХО: можно случайно изменить
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

point = Point(1, 2)
point.x = 999  # Случайное изменение

# ✅ ХОРОШО: неизменяемый объект
@dataclass(frozen=True)
class Point:
    x: int
    y: int

point = Point(1, 2)
point.x = 999  # AttributeError: can't set attribute

# Или через property
class Point:
    def __init__(self, x, y):
        self._x = x
        self._y = y
    
    @property
    def x(self):
        return self._x
    
    @property
    def y(self):
        return self._y

8. Документирование контрактов

class BankAccount:
    """
    Банковский счёт с базовыми операциями.
    
    Инварианты:
    - balance всегда >= 0
    - amount всегда > 0
    """
    
    def __init__(self, initial_balance: float = 0):
        """Инициализация счёта с начальным балансом."""
        if initial_balance < 0:
            raise ValueError("Initial balance cannot be negative")
        self._balance = initial_balance
    
    def withdraw(self, amount: float) -> None:
        """Снять сумму со счёта.
        
        Args:
            amount: Сумма для снятия
        
        Raises:
            ValueError: Если amount <= 0 или недостаточно денег
        """
        if amount <= 0:
            raise ValueError("Amount must be positive")
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount

Чеклист для проектирования

✅ Следуешь ли ты SOLID принципам? ✅ Использованы ли type hints везде? ✅ Есть ли абстрактные интерфейсы для основных компонентов? ✅ Предпочитаешь ли композицию наследованию? ✅ Валидируешь ли входные данные в конструкторе? ✅ Документированы ли контракты (docstring)? ✅ Написаны ли unit тесты? ✅ Можешь ли ты mock'ировать зависимости для тестов? ✅ Используются ли dataclass или frozen объекты где нужна неизменяемость? ✅ Нет ли циклических зависимостей?

Как снизить вероятность ошибки при проектировании ООП? | PrepBro