Припиніть лише послаблювати зв’язування — почніть зміцнювати згуртованість також.

Коли ми говоримо про чистий і підтримуваний код, зазвичай акцентуємо увагу на зменшенні зв’язності між компонентами. Це популярний принцип в програмному проектуванні: “Слабка зв’язність, висока когезія”. Проте, незважаючи на важливість зв’язності, когезія часто залишається на другому плані, хоча вона значно важливіша, ніж їй зазвичай надають значення.

Щоб розібратися, давайте пояснимо ці два принципи проектування.

Зв’язність

Зв’язність описує, наскільки один модуль залежить від іншого. Якщо два класи мають високу зв’язність, це означає, що зміна одного з них, ймовірно, вимагатиме зміни і в іншому. Слабка зв’язність дозволяє модулям працювати незалежно один від одного, що робить систему більш гнучкою і легшою для підтримки.

Когезія

На відміну від зв’язності, когезія стосується взаємопов’язаності частин одного модуля або класу. Висока когезія означає, що клас зосереджений на виконанні однієї чіткої задачі. Коли когезія низька, клас стає великим і складним, намагаючись виконати занадто багато різних функцій.

Зв’язність визначає зовнішні взаємозв'язки, а когезія — внутрішню цілісність. Обидва ці принципи важливі, але когезія має більший вплив на здоров’я вашої системи в довгостроковій перспективі.

З цього моменту я хочу поділитися усвідомленням, яке стало для мене яснішим після років роботи з об'єктно-орієнтованими системами: когезія важча для досягнення, ніж зв’язність, і вона значно важливіша.

Чому когезія важча для досягнення, ніж зв’язність

Зв’язність помітити значно легше. Ви можете побачити її через імпорти, параметри конструктора або графи залежностей. Для цього є інструменти, які дозволяють нам побудувати діаграми і побачити, де в системі виникають сильні зв’язки.

Натомість когезія — це питання оцінки, яке вимагає інтуїції, досвіду і розуміння того, що саме ви намагаєтеся досягти. І хоча ви не можете автоматизувати цей процес, відсутність когезії може призвести до того, що ваш код ще буде компілюватися і проходити тести, але з часом стане важким для підтримки.

Потрібно ставити питання: Чи мають ці поведінки сенс разом? Чи логічно цей клас реалізує свою мету? Це складніші питання, ніж просто “Чи є тут сильна зв’язність?”.

Ознаки низької когезії в реальному світі

1. Незалежні методи в одному класі

public class InvoiceProcessor {
public void processPayment() {
// логіка оплати
}
public void generateReport() {
// не пов'язана логіка звітів
}
}

Тут методи processPayment() і generateReport() виконують абсолютно різні завдання, тому вони не повинні бути в одному класі.

2. Великі сервіси, що охоплюють багато різних функцій

public class UserService {
public void createUser() { /* ... / }
public void deactivateUser() { /
... / }
public void sendEmailToUser() { /
... / }
public void logUserActivity() { /
... */ }
}

Цей клас виконує різні задачі, від управління користувачами до комунікації і логування, що знижує його когезію.

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

public class DocumentHandler {
public void handle(Document doc) {
switch (doc.getType()) {
case PDF: processPdf(doc); break;
case WORD: processWord(doc); break;
case EXCEL: processExcel(doc); break;
}
}
}

Ця логіка вказує на відсутність делегування. Кожен випадок можна реалізувати окремим класом, що має високу когезію.

4. Тести, що охоплюють різні аспекти

@Test
void testUserCreation() { /* тести логіки створення користувача / }
@Test
void testEmailNotification() { /
тести незв'язаної логіки електронної пошти */ }

Якщо модульні тести виглядають так, наче вони перевіряють різні програми, це також свідчить про низьку когезію.

5. Зміни, що порушують непов’язані функціональності

public class AccountManager {
public void updateAccountDetails() {
// також несподівано змінює виставлення рахунків
}
public void closeAccount() {
// може не працювати, оскільки updateAccountDetails змінився
}
}

Зміни, які торкаються непов’язаних частин, можуть призвести до поломки, коли невелика зміна в одній частині коду впливає на іншу.

Висока когезія на практиці

Клас з високою когезією чітко описується, і його мета зводиться до однієї простої задачі. Вся його внутрішня логіка здається логічно пов'язаною, і його тести фокусуються на певному аспекті поведінки. Нові функції додаються шляхом створення нових класів, а не за рахунок розширення вже існуючих.

Приклад: когезивний клас

public class EmailValidator {
public boolean isValid(String email) {
return email != null && email.contains("@") && email.indexOf("@") < email.length() - 1;
}
public boolean isCorporateEmail(String email) {
return isValid(email) && email.endsWith("@company.com");
}
}

Цей клас виконує одну задачу — перевірку електронних адрес. Він має високий рівень когезії, оскільки всі його методи спрямовані на досягнення однієї мети.

Запит на хороший клас: Якщо б ви мали передати цей клас іншій команді, чи був би він корисним сам по собі?

Коли когезія висока, рефакторинг стає безпечнішим. Проектування стає зрозумілішим, а поведінка — передбачуваною.

Висока когезія в модулях

Когезія важлива не лише на рівні класів, але й на рівні модулів або пакетів. Пакет з високою когезією групує пов’язані функціональності за поведінкою, а не за технічними шарами.

Наприклад, пакет, що відповідає за операції з рахунками:

com.example.billing
├── InvoiceGenerator.java
├── PaymentProcessor.java
├── TaxCalculator.java

Цей пакет має високу когезію, оскільки всі його частини зосереджені на роботі з рахунками.

Натомість пакет, що містить різні сервіси:

com.example.services
├── UserService.java
├── EmailService.java
├── PaymentService.java

Цей пакет менш когезивний, оскільки сервіси обслуговують різні домени.

Когезія та принцип єдиного обов’язку (SRP)

Когезія тісно пов'язана з Принципом єдиного обов'язку (SRP). Хоча SRP допомагає уникати збільшення функціональності класів, він не забезпечує організації всередині класу.

І ось тут важлива когезія. Вона відповідає на питання: Чи працюють частини класу разом для досягнення однієї мети?

Тому для створення чистого та підтримуваного коду важливо враховувати як SRP, так і когезію.

Висновок

Якщо зв’язність визначає відносини між частинами, то когезія — це про внутрішню гармонію. Зменшення зв'язності полегшує переміщення частин, але підвищення когезії робить ці частини цінними.

Перекладено з: Stop Just Loosening Coupling — Start Strengthening Cohesion Too