Как принцип Single Responsibility связан с cohesion
LCOM анализирует, сколько методов используют общие поля. Вычисляется статическими анализаторами (SonarQube, jdepend):
🟢 Junior Level
Cohesion (связность) — это мера того, насколько методы и поля класса связаны друг с другом. SRP и cohesion тесно связаны: когда класс имеет одну ответственность (SRP), его элементы естественно работают над одной целью — это высокая связность.
Простая аналогия: Представьте команду. Если все работают над одной задачей (высокая связность) — всё отлично. Если каждый делает своё (низкая связность) — хаос.
Пример высокой связности:
// Высокая cohesion: все методы работают с заказами
public class OrderService {
public Order createOrder() { /* ... */ }
public void cancelOrder(Order order) { /* ... */ }
public BigDecimal calculateTotal(Order order) { /* ... */ }
}
Пример низкой связности:
// Низкая cohesion: методы делают совершенно разные вещи
public class UserManager {
public void sendEmail() { /* почта */ }
public void calculateSalary() { /* зарплата */ }
public void generateReport() { /* отчёты */ }
public void saveToDatabase() { /* БД */ }
}
Когда стремиться к высокой cohesion:
- При проектировании новых классов, где ответственность ещё не устоялась
- При рефакторинге “раздутых” классов
- Не стоит гнаться за идеальной связностью в прототипах и MVP — там скорость важнее
🟡 Middle Level
Как это работает
Cohesion — внутренняя характеристика класса: насколько его части “склеены” для общей цели. SRP — внешнее правило, указывающее, где резать систему.
Связь: Соблюдение SRP как правило ведёт к высокой Cohesion. Класс с одной ответственностью естественно имеет методы, работающие над одной задачей. Но бывают исключения — например, класс-оркестратор может координировать несколько подсистем, оставаясь в рамках одной ответственности.
Уровни Cohesion по Майерсу (классификация Glenford Myers, автора книги “Reliable Software Through Composite Design”)
| Уровень | Описание | Пример | Качество |
|---|---|---|---|
| Functional | Класс делает одну вещь | StringTokenizer |
✅ Идеал |
| Sequential | Методы — конвейер | ImageTransformer |
✅ Хорошо |
| Communicational | Методы работают с одними данными | OrderRepository |
✅ Нормально |
| Procedural | Методы выполняются в порядке | StartupInitializer |
⚠️ Терпимо |
| Temporal | Методы выполняются в одно время | ConfigLoader |
⚠️ Терпимо |
| Utility | Слабая группировка | CommonUtils |
❌ Плохо |
| Coincidental | Случайная группировка | Helper |
❌ Ужасно |
Метрика LCOM (Lack of Cohesion in Methods — “нехватка связности методов”)
LCOM анализирует, сколько методов используют общие поля. Вычисляется статическими анализаторами (SonarQube, jdepend):
- Если 10 методов: 5 работают с полем A, 5 с полем B → LCOM высокий → класс стоит разделить на 2 класса
- LCOM = 0: все методы используют общие поля (высокая связность)
- LCOM > 1.0: методы образуют независимые группы (низкая связность)
SRP и Coupling баланс
Цель: High Cohesion + Low Coupling
| Подход | Cohesion | Coupling | Результат |
|---|---|---|---|
| God Object (один класс делает всё — нарушает SRP) | Низкая | Низкий | ❌ Хрупкий |
| Атомарная пыль (слишком мелкие классы — сотни классов по 5 строк) | Высокая | Чудовищный | ❌ Сложный |
| Баланс | Высокая | Низкий | ✅ Идеал |
Типичные ошибки
| Ошибка | Решение |
|---|---|
| Класс с “And/Or/Manager” в имени | Разделить по ответственности |
| LCOM > 1.0 | Выделить отдельные классы |
| Package-by-Layer (группировка по техническим слоям: все контроллеры, все сервисы, все репозитории) → низкая связность | Package-by-Feature (группировка по бизнес-фичам: Order, User, Payment — каждый пакет содержит свой controller + service + repository) |
Когда НЕ стоит гнаться за идеальной cohesion
- Прототипы/MVP (скорость важнее)
- Маленькие утилиты (до 200 строк)
- Когда баланс нарушается в другую сторону (atomic dust)
🔴 Senior Level
Internal Implementation: LCOM4 и граф связности
LCOM4 (LCOM версии 4, улучшенная от Henderson-Sellers) вычисляется через граф зависимостей:
- Узлы = методы + поля
- Рёбра = метод использует поле
- Если граф распадается на N компонентов → класс стоит разделить на N частей
Методы: [m1, m2, m3, m4, m5]
Поля: [f1, f2, f3, f4]
m1→f1, m2→f1, m3→f2 (Component 1)
m4→f3, m5→f4 (Component 2)
LCOM4 = 2 → разделить на 2 класса
Архитектурные Trade-offs
Strict SRP (Functional Cohesion):
- ✅ Плюсы: Идеальная тестируемость, минимум side effects, easy refactoring
- ❌ Минусы: Class explosion, high coupling между классами, cognitive load
Balanced SRP (Communicational Cohesion):
- ✅ Плюсы: Баланс тестируемости и navigability
- ❌ Минусы: Требует зрелого суждения, gradual degradation risk
Edge Cases
- Anemic Domain Model (классы только с getter/setter, без бизнес-логики — все вычисления вынесены в сервисы): Классы только с getter/setter → LCOM = 0, но это не значит “хорошо”
- Решение: DDD (Domain-Driven Design — подход, где бизнес-логика живёт рядом с данными, а не в отдельных сервисах) — логика рядом с данными
- Package-by-Layer vs Package-by-Feature:
- Layer:
[Controller, Service, DAO]→ низкая связность на уровне пакета - Feature:
[Order, User, Payment]→ высокая связность, SRP на уровне модулей
- Layer:
- Transaction Boundaries: Один класс координирует транзакцию через несколько доменов
- Решение: Orchestrator pattern, но бизнес-логика в отдельных сервисах
Производительность
Cache Locality:
Высокая cohesion:
class OrderProcessor { fields: order, items, total }
→ Данные рядом в памяти → L1 cache hit rate >90%
Низкая cohesion:
class GodObject { fields: order, email, db, cache, log, ... }
→ Данные разбросаны → L1 cache miss rate >30%
CPU Cache Impact: Высокая cohesion → лучше spatial locality → +15-30% throughput
Thread Safety
- High cohesion classes: Обычно проще синхронизировать (один lock на класс)
- Low cohesion (God Object): Multiple responsibilities → lock contention → bottleneck
Production Experience
Рефакторинг BillingEngine (12,000 lines): LCOM4 = 8, 6 полей использовались разными подмножествами методов. Разделили на:
TaxCalculator(Functional cohesion)InvoiceGenerator(Sequential cohesion)PaymentProcessor(Communicational cohesion)BillingOrchestrator(координатор)
Результат: LCOM4 = 1.0 для каждого, баги при деплое ↓70%.
Monitoring
ArchUnit:
@ArchTest
static void classes_should_have_high_cohesion = classes()
.should().haveSimpleNameNotContaining("Manager")
.andShould().haveSimpleNameNotContaining("Utils")
.andShould().haveLessThanNDependencies(7);
SonarQube: LCOM > 1.0 → Code Smell, Cognitive Complexity > 15
jdepend: Metrics reports → Lack of Cohesion in Methods
Best Practices for Highload
- Package-by-Feature для модульной связности
- LCOM4 < 1.0 как target метрика
- Functional Cohesion для hot-path классов
- ArchUnit для автоматической проверки
🎯 Шпаргалка для интервью
Обязательно знать:
- Cohesion (связность) — насколько методы класса работают для одной цели; SRP → высокая Cohesion
- Уровни cohesion по Майерсу: Functional (идеал) → Sequential → Communicational → Procedural → Temporal → Utility → Coincidental (ужасно)
- LCOM (Lack of Cohesion in Methods): LCOM = 0 → высокая связность, LCOM > 1.0 → стоит разделить
- LCOM4: если граф зависимостей распадается на N компонентов → класс стоит разделить на N частей
- Цель: High Cohesion + Low Coupling — избегать God Object и Atomic Dust
- Package-by-Feature даёт высокую связность, Package-by-Layer — низкую
- Высокая cohesion → лучше spatial locality → +15-30% throughput (CPU cache impact)
Частые уточняющие вопросы:
- Какой уровень cohesion стремиться? — Functional для hot-path, Communicational для большинства классов
- LCOM4 = 2 что значит? — Класс стоит разделить на 2 независимых класса
- Anemic Domain Model и cohesion? — Геттеры/сеттеры → LCOM = 0, но это не “хорошо” — логика вынесена в сервисы
- Что такое Atomic Dust? — Слишком мелкие классы (сотни по 5 строк), высокая связность но чудовищное coupling
Красные флаги (НЕ говорить):
- “Нужно гнаться за идеальной cohesion всегда” (прототипы/MVP — скорость важнее)
- “LCOM = 0 всегда означает хороший дизайн” (Anemic Domain Model тоже даёт LCOM = 0)
- “Package-by-Layer — лучший подход” (низкая связность на уровне пакета, лучше Package-by-Feature)
Связанные темы:
- [[1. Что такое принцип Single Responsibility и как его применять]]
- [[14. Что произойдёт, если класс имеет несколько причин для изменения]]
- [[21. Как определить, что класс имеет одну ответственность]]
- [[18. Как рефакторить God Object (божественный объект)]]