Cohesion (кохезия) е метрика, която показва колко тясно са свързани елементите в рамките на един модул или клас. Според Wikipedia, високата кохезия е признак на добре проектиран модул, където всички методи и полета работят по една задача. Cohesion пряко влияе върху поддържаемостта на кода и се противопоставя на coupling — свързаността между модулите.
Основни точки
Cohesion (кохезия) — метрика, която оценява доколко логически са свързани методите, полетата и свойствата в рамките на един клас или модул. Модул с висока кохезия изпълнява една задача и съдържа само елементите, необходими за нейното изпълнение. Модул с ниска кохезия се опитва да прави няколко неща едновременно — методите са слабо свързани по смисъл.
В контекста на обектно-ориентираното програмиране кохезията е тясно свързана с принципа Single Responsibility (S). Ако класът има една ясна отговорност, неговата кохезия обикновено е висока. Ако класът се занимава и с UI, и с бизнес логика, и с работа в мрежа — кохезията е ниска и такъв клас трябва да се раздели на няколко отделни класа с по-тясна отговорност.
Разбирането на cohesion помага на разработчика да взема решение за рефакторинг. Когато видите, че в класа има метод, който не използва полетата на класа, това е сигнал за ниска кохезия. Такъв метод или е излишен в класа, или класът е неправилно проектиран. Стремежът към висока кохезия е постоянна работа по подобряване на архитектурата на всяко ниво на кода.
В софтуерното инженерство се различават седем нива на кохезия, подредени от най-лошото към най-доброто. Разбирането на тази скала позволява обективна оценка на качеството на модула и разбиране на посоката при рефакторинг. Колкото по-високо е нивото, толкова по-поддържаем и разбираем ще бъде кодът.
Случайна (coincidental) — най-лошото ниво, когато елементите в модула са групирани случайно, без каквато и да е логическа връзка. Пример: класът Utilities, в който са събрани методи за форматиране на дата, изпращане на имейл и изчисляване на отстъпка. Такъв клас не може да бъде разбран без четене на всички методи и промяната на един метод може да счупи други само защото се намират заедно.
Логическа (logical) кохезия — елементите изпълняват логически свързани, но различни по същество задачи. Клас с методи parseJSON, parseXML и parseCSV е логически свързан с темата „парсване“, но всеки метод прави принципно различна работа. Проблем: при добавяне на нов формат (YAML) класът расте и неговият интерфейс става надут.
Времева (temporal) кохезия — елементите са групирани според времето на изпълнение. Класът AppInitializer, който настройва базата данни, зарежда конфигурацията, инициализира анализи — всичко това се случва при стартиране на приложението, но самите задачи не са свързани помежду си. По-добре да се разделят на отделни Initializer за всяка зона на отговорност.
Процедурна (procedural) кохезия възниква, когато елементите са обединени от последователността на изпълнение. Модулът „Обработка на поръчка“ съдържа методи validateCart, processPayment, sendConfirmation — всеки метод се извиква строго след предишния. Това е по-добре от случайната или логическа кохезия, но все още не е идеал: всяка стъпка може да бъде изнесена в отделен модул.
Комуникационна (communicational) кохезия — елементите работят с едни и същи данни. Класът UserService с методи getUser, updateUser, deleteUser е обединен от общата същност User. Това е значително по-добре от процедурната кохезия: класът има ясна предметна област. Повечето Repository класове в мобилни проекти имат комуникационна кохезия.
Функционална (functional) кохезия — най-високото ниво, когато всеки елемент на модула участва в изпълнението на една задача. Класът PasswordValidator с единствен метод validate, който проверява дължината, наличието на символи и сложността на паролата — пример за функционална кохезия. Ако такъв клас се промени, то е само защото са се променили правилата за валидиране на пароли.
Постигането на функционална кохезия е основната цел на архитектурния рефакторинг. Всеки клас трябва да има точно една причина за промяна. В мобилната разработка функционалната кохезия се постига чрез извличане на отделни Use Cases, персонализирани View, форматировачи и валидатори. Всеки такъв клас е завършен градивен блок с ясна зона на отговорност.
Cohesion и coupling — две страни на едно и също качество. Колкото по-висока е кохезията в модула, толкова по-ниска обикновено е свързаността между модулите. Една добре проектирана система едновременно се стреми към висока кохезия отвътре и слаба свързаност отвън. Това правило се счита за фундаментално в софтуерното инженерство от 70-те години на миналия век.
Връзката cohesion-coupling може да се представи като баланс. Ако разработчикът жертва кохезията, обединявайки няколко задачи в един клас, съседните модули получават повече зависимости — те трябва да се обръщат към този претоварен клас за различни цели, което увеличава свързаността. И обратно, разделянето на малки класове с висока кохезия намалява броя на точките на взаимодействие между модулите.
На практика това означава: когато извличате нов клас с функционална кохезия, вие едновременно освобождавате други модули от необходимостта да знаят детайлите на неговата реализация. Например, извличайки EncryptionManager в отделен клас с функционална кохезия, вие давате на другите модули прост интерфейс encrypt/decrypt без необходимост да разбират детайлите на алгоритъма за криптиране.
// Ниска кохезия — класът прави всичко наведнъж
class UserManager {
fun fetchAndSaveUser(id: String) { }
fun parseUserJson(json: String): User { }
fun displayUserName(user: User): String { }
fun validateEmail(email: String): Boolean { }
}
// Висока кохезия — всеки клас решава една задача
class UserRepository {
fun fetchUser(id: String): User { }
}
class UserJsonParser {
fun parse(json: String): User { }
}
class UserNameFormatter {
fun format(user: User): String { }
}
class EmailValidator {
fun isValid(email: String): Boolean { }
}
Примерът показва разликата: UserManager има логическа кохезия — всички методи са за потребители, но всеки прави принципно различна работа. След рефакторинг всеки клас има функционална кохезия и свързаността намалява, защото другите модули зависят само от класа, от който се нуждаят, а не от целия UserManager.
LCOM (Lack of Cohesion of Methods) — най-известната метрика за измерване на кохезията на клас. LCOM изчислява колко двойки методи не използват общи полета. Стойност 0 означава идеална кохезия (всички методи работят с едни и същи полета), висока стойност означава ниска кохезия. LCOM4 (подобрена версия) отчита транзитивните връзки чрез други методи.
В Android разработката метриките за кохезия могат да бъдат получени чрез Detekt с правило TooManyFunctions. Класове с десет метода, използващи различни групи полета, най-вероятно имат ниска кохезия. В iOS SwiftLint има правила file_length и function_body_length — косвени индикатори: дългите файлове и методи често сигнализират за ниска кохезия.
Ръчен начин за оценка: задайте въпроса „Този клас ще се промени по една причина или по няколко?“ Ако можете да посочите повече от една независима причина — класът има ниска кохезия. Втори тест: „Може ли този клас да се раздели на два независими класа?“ Ако да — направете го. Редовната проверка на кохезията при код-ревю предотвратява появата на God класове и намалява техническия дълг.
Първа стъпка — приложете принципа Single Responsibility. Всеки клас трябва да има една ясна отговорност. Ако в класа има метод, който не се отнася до основната му задача, изнесете го в отделен клас. Техниката Extract Class или Extract Delegate в IDE автоматизира този процес. След извличането проверете дали оригиналният клас е станал по-фокусиран.
Втора стъпка — използвайте шаблона Facade за опростяване на интерфейса. Ако клас предоставя 20 метода, от които клиентите използват само 3-4, вероятно класът има ниска кохезия — предоставя твърде много различна функционалност. Групирайте методите по теми и извлечете отделни класове за всяка група, а оригиналния клас направете фасада или го премахнете.
Трета стъпка — обърнете внимание на групите полета. Ако класът има полета, които се използват само от част от методите — това е индикатор за ниска кохезия. Разделете класа по групи полета. Например, ако класът съдържа полета userRepository, networkClient и analyticsTracker, но методите от първата група използват само userRepository, а от втората — само networkClient, това са два различни класа.
Четвърта стъпка — избягвайте създаването на „помощни“ класове с произволни статични методи. Всеки статичен метод, намиращ се в клас Utils или Helpers, е кандидат за извличане в специализиран клас. FormatUtils.dateToString е по-добре да се премести в DateFormatter, а ValidationUtils.isValidEmail — в EmailValidator. Това повишава кохезията на всеки клас и прави кода самодокументиращ се.
Често задавани въпроси
Почти винаги. Функционалната кохезия прави кода разбираем и предвидим. Прекаляването обаче може да доведе до прекомерно раздробяване: когато за всяка операция се създава отделен клас и архитектурата става излишно сложна. Баланс — няколко класа на функционалност, всеки с функционална кохезия.
Cohesion — метрика за вътрешна съгласуваност на един модул или клас. Modularity — архитектурен принцип, при който приложението се разделя на физически модули. Високата кохезия е цел при проектирането както на отделни класове, така и на цели модули.
Detekt за Android и Xcode Analyzer за iOS подчертават класове с подозрително голям брой методи или полета. IntelliJ IDEA и AppCode имат визуализация на зависимостите — можете да видите графа на връзките и да откриете класове с ниска кохезия. SonarQube изчислява LCOM метриките автоматично.
Да. Интерфейс с методи connect, disconnect и isConnected има висока кохезия — всички методи се отнасят до управлението на връзката. Интерфейс с методи connect, parseData и renderUI има ниска кохезия. Принципът Interface Segregation (SOLID) изисква създаването на тясно специализирани интерфейси с висока кохезия.
Задайте три въпроса: може ли предназначението на класа да се опише с едно изречение? Всички методи поддържат ли това предназначение? Има ли в класа полета, които не се използват от част от методите? Ако отговорът на някой въпрос е отрицателен — кохезията е ниска и класът трябва да се раздели.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също