Single Responsibility Principle

Single Responsibility Principle (SRP) — первый принцип SOLID: каждый класс, функция или модуль должны иметь только одну причину для изменения, то есть отвечать за одну конкретную задачу.

Зачем нужно

Нарушение SRP приводит к «God objects» — классам, которые знают всё и делают всё. Такой код сложно тестировать (много зависимостей), изменять (риск сломать несвязанный функционал) и читать. SRP позволяет изолировать изменения: правки в логике email-рассылки не затрагивают работу с базой данных.

Где используется

  • Разделение слоёв: data access, business logic, presentation
  • Выделение сервисов: UserService, EmailService, ReportService
  • Функции: каждая делает одно и хорошо (Unix philosophy)
  • React-компоненты: UI отдельно от логики (hooks, сервисы)

Основной контент

Нарушение SRP

// Плохо: класс делает несколько несвязанных вещей
class UserManager {
  constructor(db, mailer) {
    this.db = db;
    this.mailer = mailer;
  }

  createUser(userData) {
    // Валидация
    if (!userData.email) throw new Error('Email обязателен');
    if (userData.password.length < 8) throw new Error('Пароль слишком короткий');

    // Сохранение в БД
    const user = this.db.insert('users', userData);

    // Отправка письма
    this.mailer.send(user.email, 'Добро пожаловать!');

    // Генерация отчёта
    this.db.insert('audit_log', { action: 'user_created', userId: user.id });

    return user;
  }
}
// Изменится формат email → меняем UserManager
// Изменится структура БД → меняем UserManager
// Изменится шаблон письма → меняем UserManager

Соблюдение SRP

// Хорошо: каждый класс — одна ответственность

class UserValidator {
  validate(userData) {
    if (!userData.email) throw new Error('Email обязателен');
    if (userData.password.length < 8) throw new Error('Пароль слишком короткий');
  }
}

class UserRepository {
  constructor(db) { this.db = db; }
  save(userData) { return this.db.insert('users', userData); }
}

class EmailService {
  constructor(mailer) { this.mailer = mailer; }
  sendWelcome(email) {
    return this.mailer.send(email, 'Добро пожаловать!');
  }
}

class AuditLogger {
  constructor(db) { this.db = db; }
  log(action, meta) { this.db.insert('audit_log', { action, ...meta }); }
}

// Координатор — thin controller
class UserRegistrationService {
  constructor(validator, repository, emailService, auditLogger) {
    this.validator = validator;
    this.repository = repository;
    this.emailService = emailService;
    this.auditLogger = auditLogger;
  }

  async register(userData) {
    this.validator.validate(userData);
    const user = await this.repository.save(userData);
    await this.emailService.sendWelcome(user.email);
    this.auditLogger.log('user_created', { userId: user.id });
    return user;
  }
}

SRP для функций

// Плохо: функция делает несколько вещей
function processOrderData(order) {
  // валидация
  if (!order.items.length) throw new Error('Нет товаров');
  // форматирование
  const total = order.items.reduce((s, i) => s + i.price * i.qty, 0);
  const formatted = { ...order, total, date: new Date.toISOString() };
  // сохранение
  localStorage.setItem('lastOrder', JSON.stringify(formatted));
  return formatted;
}

// Хорошо: разделить
const validateOrder = order => {
  if (!order.items.length) throw new Error('Нет товаров');
};

const calculateTotal = items =>
  items.reduce((s, i) => s + i.price * i.qty, 0);

const formatOrder = order => ({
  ...order,
  total: calculateTotal(order.items),
  date: new Date.toISOString()
});

const saveOrder = order =>
  localStorage.setItem('lastOrder', JSON.stringify(order));

Частые ошибки

  • Слишком узкая трактовка — дробить код до одной строки на класс — избыточно. «Одна причина для изменения» не означает «одна строка». Группируйте связанное.
  • Нарушение SRP через «utilities»helpers.js или utils.js с 50 несвязанными функциями — нарушение. Разбивайте по доменам.
  • Игнорирование SRP в функциях — принцип применим не только к классам, но и к функциям. Функция, которая и валидирует, и сохраняет, и отправляет — нарушает SRP.

🎓 Источники

  • 🎓 [SOLID SRP — Single Responsibility Principle for JavaScript] · 2024-11-29 · YouTube
    • Тезисы: SRP близок к Separation of Concerns. Применим не только к ООП — к функциям, модулям, процедурам тоже. Где остановиться дробление — только интуиция инженера, тренируется опытом.
    • Альтернативная позиция: «Single responsibility очень похож на общий инженерный принцип separation of concerns. Все это про то, насколько гранулярно мы хотим писать код». SRP не имеет точного критерия — только опыт.
  • 🎓 [The Single Responsibility Principle and Conway's Law] · 2019-10-17 · YouTube
    • Тезисы: SRP — это принцип, а не закон. Дробление увеличивает количество классов и объём кода — это его цена. Захардкоженный конфиг внутри класса ломает SRP. Связан с законом Конвея: структура кода отражает структуру команды.

Связанные темы

Ресурсы