Cohesion (кохезија) — је метрика која показује колико су чврсто повезани елементи унутар једног модула или класе. Према Википедији, висока кохезија је знак добро пројектованог модула, где све методе и поља раде на једном задатку. Cohesion директно утиче на одрживост кода и супротставља се coupling — повезаности између модула.
Главно
Cohesion (кохезија) — метрика која оцењује колико су методе, поља и својства унутар једне класе или модула логички повезани. Модул са високом кохезијом обавља један задатак и садржи само оне елементе који су потребни за његово извршавање. Модул са ниском кохезијом покушава да ради више ствари истовремено — методе су слабо повезане по значењу.
У контексту објектно-оријентисаног програмирања cohesion је тесно повезан са Single Responsibility Principle (S). Ако класа има једну јасну одговорност, њен cohesion је по правилу висок. Ако се класа бави и UI, и пословном логиком, и мрежном комуникацијом — cohesion је нисак и такву класу треба поделити на више одвојених класа са ужом одговорношћу.
Разумевање cohesion помаже програмеру да доноси одлуке о рефакторисању. Када видите да у класи постоји метода која не користи поља класе, то је сигнал ниске кохезије. Таква метода је или сувишна у класи, или је класа лоше пројектована. Тежња ка високој кохезији — то је непрекидан рад на побољшању архитектуре на сваком нивоу кода.
У инжењерингу софтвера постоји седам нивоа 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 Case, прилагођених View, форматера и валидатора. Свака таква класа је комплетан градевни блок са јасном зоном одговорности.
Cohesion и coupling — две стране исте квалитета. Што је cohesion виши унутар модула, то је, по правилу, coupling нижи између модула. Добро пројектован систем истовремено тежи високој кохезији унутар и слабој повезаности споља. Ово правило се сматра фундаменталним у инжењерингу софтвера од 1970-их година.
Однос cohesion-coupling може се представити као равнотежу. Ако програмер жртвује 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 има логичку кохезију — све методе су о корисницима, али свака ради принципијелно друкчије посао. Након рефакторисања свака класа има функционалну кохезију, а coupling опада јер други модули зависе само од класе која им је потребна, а не од целог UserManager-а.
LCOM (Lack of Cohesion of Methods) — најпознатија метрика за мерење кохезије класе. LCOM броји колико парова метода не користи заједничка поља. Вриједност 0 значи идеалну кохезију (све методе раде са истим пољима), висока вриједност — ниску кохезију. LCOM4 (побољшана верзија) узима у обзир транзитивне везе кроз друге методе.
У Android развоју метрике кохезије могу се добити путем Detekt са правилом TooManyFunctions. Класе са десетинама метода које користе различите групе поља вероватно имају ниску кохезију. У iOS, SwiftLint има правила file_length и function_body_length — посредни индикатори: дугачки фајлови и методе често указују на нисак cohesion.
Ручни начин процене: поставите питање „Да ли ће се ова класа променити из једног разлога или из више њих?” Ако можете навести више од једног независног разлога — класа има ниску кохезију. Други тест: „Може ли се ова класа поделити на двије независне класе?” Ако да — учините то. Редовна провера cohesion-а на code review спречава појаву God класа и смањује технички дуг.
Први корак — примените Single Responsibility Principle. Свака класа треба да има једну јасну одговорност. Ако у класи постоји метода која не припада њеном главном задатку, издвојите је у засебну класу. Техника Extract Class или Extract Delegate у IDE аутоматизује овај процес. Након издвајања проверите да ли је изворна класа постала фокусиранија.
Други корак — користите образац Facade за поједностављивање интерфејса. Ако класа пружа 20 метода, од којих клијенти користе само 3-4, вероватно класа има ниску кохезију — нуди превише разноврсне функционалности. Групишите методе по темама и издвојите посебне класе за сваку групу, а изворну класу претворите у фасаду или обришите.
Трећи корак — обратите пажњу на групе поља. Ако класа има поља која се користе само делом метода — то је показатељ ниског cohesion. Поделите класу према групама поља. На пример, ако класа садржи поља userRepository, networkClient и analyticsTracker, али методе прве групе користе само userRepository, а друге — networkClient — то су двије различите класе.
Четврти корак — избегавајте стварања „утилитних” класа са произвољним static методама. Свака static метода која се налази у класи Utils или Helpers — кандидат за издвајање у специјализовану класу. FormatUtils.dateToString је боље преместити у DateFormatter, а ValidationUtils.isValidEmail — у EmailValidator. Ово повећава cohesion сваке класе и чини код самодокументирајућим.
Често постављана питања
Скоро увек. Функционална кохезија чини код разумљивим и предвидљивим. Међутим, довођење до крајности може довести до прекомерног рачвајања: када се за сваку операцију креира посебна класа, а архитектура постаје непотребно комплексна. Равнотежа — неколико класа по фичи, свака са функционалном кохезијом.
Cohesion — метрика унутрашње усаглашености једног модула или класе. Modularity — архитектурни принцип при којем се апликација дели на физичке модуле. Висок cohesion је циљ при пројектовању како појединачних класа, тако и целих модула.
Detekt за Android и Xcode Analyzer за iOS истичу класе са сумњиво великим бројем метода или поља. IntelliJ IDEA и AppCode имају визуализацију зависности — можете видети граф веза и открити класе са ниском кохезијом. SonarQube аутоматски израчунава LCOM метрике.
Да. Интерфејс са методама connect, disconnect и isConnected има високу кохезију — све методе се односе на управљање везом. Интерфејс са методама connect, parseData и renderUI има ниску кохезију. Принцип Interface Segregation (SOLID) захтева стварање узкоспецијализованих интерфејса са високом кохезијом.
Поставите три питања: може ли се описати намена класе једном реченицом? Да ли све методе подржавају овај намену? Постоје ли у класи поља која се не користе делом метода? Ако је одговор на било које питање негативан — cohesion је нисак и класу треба поделити.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође