Cohesion (kohézió) — egy metrika, amely megmutatja, mennyire szorosan kapcsolódnak az elemek egy modulon vagy osztályon belül. A Wikipedia szerint a magas kohézió a jól megtervezett modul jele, ahol az összes metódus és mező egyetlen feladaton dolgozik. Cohesion közvetlenül befolyásolja a kód karbantarthatóságát és szemben áll a coupling — modulok közötti kapcsolódás — fogalmával.
Főbb pontok
Cohesion (kohézió) — metrika, amely értékeli, mennyire logikailag összefüggőek a metódusok, mezők és tulajdonságok egy osztályon vagy modulon belül. A magas kohéziójú modul egyetlen feladatot végez, és csak azokat az elemeket tartalmazza, amelyek annak végrehajtásához szükségesek. Az alacsony kohéziójú modul több dolgot próbál egyszerre végezni — a metódusok gyengén kapcsolódnak jelentés szerint.
Az objektumorientált programozás kontextusában a cohesion szorosan kapcsolódik a Single Responsibility Principle (S) elvhez. Ha egy osztálynak egyértelmű felelőssége van, a cohesion általában magas. Ha egy osztály foglalkozik UI-val, üzleti logikával és hálózati kommunikációval is — a cohesion alacsony, és az ilyen osztályt több különálló osztályra kell bontani szűkebb felelősséggel.
A cohesion megértése segíti a fejlesztőt a refaktorálási döntések meghozatalában. Amikor látja, hogy egy osztályban van egy metódus, amely nem használja az osztály mezőit, ez az alacsony kohézió jele. Egy ilyen metódus vagy felesleges az osztályban, vagy az osztály rosszul van megtervezve. A magas kohézióra való törekvés — folyamatos munka az architektúra javításáért a kód minden szintjén.
A szoftvermérnöklésben hét cohesion szintet különböztetünk meg, a legrosszabbtól a legjobbig rendezve. Ennek a skálának a megértése lehetővé teszi a modul minőségének objektív értékelését és annak meghatározását, hogy milyen irányba haladjunk a refaktorálás során. Minél magasabb a szint, annál karbantarthatóbb és érthetőbb lesz a kód.
Véletlenszerű (coincidental) — a legrosszabb szint, amikor a modul elemei véletlenszerűen, bármiféle logikai kapcsolat nélkül vannak csoportosítva. Példa: az Utilities osztály, amely dátumformázási, e-mail küldési és kedvezményszámítási metódusokat tartalmaz. Egy ilyen osztály nem érthető meg az összes metódus elolvasása nélkül, és egy metódus módosítása tönkretehet másokat csak azért, mert egymás mellett vannak.
Logikai (logical) kohézió — az elemek logikailag kapcsolódó, de lényegükben különböző feladatokat végeznek. A parseJSON, parseXML és parseCSV metódusokkal rendelkező osztály logikailag kapcsolódik a „parsing“ témához, de minden metódus alapvetően más munkát végez. Probléma: új formátum (YAML) hozzáadásakor az osztály nő, és interfésze felduzzad.
Időbeli (temporal) kohézió — az elemek végrehajtási idő szerint vannak csoportosítva. Az AppInitializer osztály, amely konfigurálja az adatbázist, betölti a konfigurációt, inicializálja az analitikát — mindez az alkalmazás indításakor történik, de maguk a feladatok nem kapcsolódnak egymáshoz. Jobb különálló Initializer-ekre bontani őket minden felelősségi területre.
Procedurális (procedural) kohézió akkor keletkezik, amikor az elemeket a végrehajtás sorrendje kapcsolja össze. A „Rendelésfeldolgozás” modul tartalmazza a validateCart, processPayment, sendConfirmation metódusokat — minden metódus szigorúan az előző után kerül meghívásra. Ez jobb, mint a véletlenszerű vagy logikai kohézió, de még mindig nem ideális: minden lépés külön modulba helyezhető.
Kommunikációs (communicational) kohézió — az elemek ugyanazokkal az adatokkal dolgoznak. A UserService osztály a getUser, updateUser, deleteUser metódusokkal a közös User entitás által van összekapcsolva. Ez lényegesen jobb, mint a procedurális kohézió: az osztálynak egyértelmű tárgya van. A mobil projektekben a Repository osztályok többsége kommunikációs kohézióval rendelkezik.
Funkcionális (functional) kohézió — a legmagasabb szint, amikor a modul minden eleme részt vesz egyetlen feladat végrehajtásában. A PasswordValidator osztály az egyetlen validate metódussal, amely ellenőrzi a jelszó hosszát, karaktereinek meglétét és összetettségét — példa a funkcionális kohézióra. Ha egy ilyen osztály változik, az csak azért van, mert a jelszóérvényesítési szabályok változtak.
A funkcionális kohézió elérése — az architekturális refaktorálás fő célja. Minden osztálynak pontosan egy oka legyen a változásra. A mobilfejlesztésben a funkcionális kohézió különálló Use Case-ek, egyedi View-k, formázók és érvényesítők elkülönítésével érhető el. Minden ilyen osztály egy teljes építőelem, egyértelmű felelősségi körrel.
Cohesion és coupling — ugyanazon minőség két oldala. Minél magasabb a cohesion egy modulon belül, annál alacsonyabb általában a coupling a modulok között. Egy jól megtervezett rendszer egyszerre törekszik magas belső kohézióra és gyenge külső kapcsolódásra. Ezt a szabályt az 1970-es évek óta alapvetőnek tekintik a szoftvermérnöklésben.
A cohesion-coupling arányt egyensúlyként lehet elképzelni. Ha a fejlesztő feláldozza a cohesion-t több feladat egy osztályba való összevonásával, a szomszédos modulok több függőséget kapnak — különböző célokra kell hivatkozniuk ehhez a túlterhelt osztályhoz, ami növeli a coupling-ot. És fordítva, a kis, magas kohéziójú osztályokra bontás csökkenti a modulok közötti interakciós pontok számát.
A gyakorlatban ez azt jelenti: amikor kivon egy új osztályt funkcionális kohézióval, egyidejűleg felszabadítja a többi modult a megvalósítási részletek ismeretének szükségessége alól. Például, ha az EncryptionManager-t különálló osztályba helyezi funkcionális kohézióval, egyszerű encrypt/decrypt interfészt ad a többi modulnak anélkül, hogy meg kellene érteniük a titkosítási algoritmus részleteit.
// Alacsony kohézió — az osztály mindent egyszerre csinál
class UserManager {
fun fetchAndSaveUser(id: String) { }
fun parseUserJson(json: String): User { }
fun displayUserName(user: User): String { }
fun validateEmail(email: String): Boolean { }
}
// Magas kohézió — minden osztály egy feladatot old meg
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 { }
}
A példa a különbséget mutatja: a UserManager logikai kohézióval rendelkezik — minden metódus a felhasználókkal kapcsolatos, de mindegyik alapvetően más munkát végez. A refaktorálás után minden osztály funkcionális kohézióval rendelkezik, és a coupling csökken, mert a többi modul csak a számukra szükséges osztálytól függ, nem az egész UserManager-től.
LCOM (Lack of Cohesion of Methods) — a legismertebb metrika az osztály kohéziójának mérésére. Az LCOM megszámolja, hány metóduspár nem használ közös mezőket. A 0 érték ideális kohéziót jelent (minden metódus ugyanazokkal a mezőkkel dolgozik), a magas érték — alacsony kohéziót. Az LCOM4 (továbbfejlesztett változat) figyelembe veszi a más metódusokon keresztüli tranzitív kapcsolatokat is.
Android fejlesztésben a kohéziós metrikák a Detekt-en keresztül érhetők el a TooManyFunctions szabállyal. Azok az osztályok, amelyek tucatnyi metódussal rendelkeznek, különböző mezőcsoportokat használva, nagy valószínűséggel alacsony kohéziójúak. Az iOS-ben a SwiftLint rendelkezik a file_length és function_body_length szabályokkal — közvetett indikátorok: a hosszú fájlok és metódusok gyakran alacsony cohesion-ra utalnak.
Kézi értékelési módszer: tegye fel a kérdést „Ez az osztály egy okból vagy több okból fog változni?” Ha egynél több független okot tud megnevezni — az osztály alacsony kohéziójú. Második teszt: „Két független osztályra lehet osztani ezt az osztályt?” Ha igen — tegye meg. A cohesion rendszeres ellenőrzése a code review során megakadályozza a God osztályok megjelenését és csökkenti a technikai adósságot.
Első lépés — alkalmazza a Single Responsibility Principle elvet. Minden osztálynak egy egyértelmű felelőssége legyen. Ha egy osztályban van egy metódus, amely nem tartozik a fő feladatához, helyezze át egy külön osztályba. Az IDE-ben az Extract Class vagy Extract Delegate technika automatizálja ezt a folyamatot. Az áthelyezés után ellenőrizze, hogy az eredeti osztály fókuszáltabbá vált-e.
Második lépés — használja a Facade mintát az interfész egyszerűsítésére. Ha egy osztály 20 metódust kínál, amelyből az ügyfelek csak 3-4-et használnak, az osztály valószínűleg alacsony kohéziójú — túl sok különféle funkcionalitást kínál. Csoportosítsa a metódusokat téma szerint és hozzon létre külön osztályokat minden csoporthoz, az eredeti osztályt pedig tegye facade-á vagy törölje.
Harmadik lépés — figyeljen a mezőcsoportokra. Ha egy osztálynak vannak olyan mezői, amelyeket csak a metódusok egy része használ — ez az alacsony cohesion jelzője. Ossza fel az osztályt a mezőcsoportok szerint. Például, ha egy osztály tartalmazza a userRepository, networkClient és analyticsTracker mezőket, de az első csoport metódusai csak a userRepository-t, a másodiké pedig a networkClient-et használják — ez két különböző osztály.
Negyedik lépés — kerülje a „segédprogram” osztályok létrehozását véletlenszerű statikus metódusokkal. Minden statikus metódus, amely egy Utils vagy Helpers osztályban található, jelölt egy specializált osztályba való kivonásra. A FormatUtils.dateToString-t jobb áthelyezni a DateFormatter-be, és a ValidationUtils.isValidEmail-t — az EmailValidator-ba. Ez növeli minden osztály cohesion-ját és öndokumentálóvá teszi a kódot.
Gyakran Ismételt Kérdések
Szinte mindig. A funkcionális kohézió érthetővé és kiszámíthatóvá teszi a kódot. Azonban a szélsőségekbe való vétel túlzott töredezettséghez vezethet: amikor minden művelethez külön osztályt hozunk létre, és az architektúra szükségtelenül összetetté válik. Egyensúly — néhány osztály funkcionálisonként, mindegyik funkcionális kohézióval.
Cohesion — egy modul vagy osztály belső konzisztenciájának metrikája. Modularity — architekturális elv, amely szerint az alkalmazás fizikai modulokra van osztva. A magas cohesion cél mind az egyes osztályok, mind az egész modulok tervezésénél.
Detekt Androidhoz és Xcode Analyzer iOS-hez kiemeli azokat az osztályokat, amelyek gyanítottan sok metódust vagy mezőt tartalmaznak. Az IntelliJ IDEA és az AppCode függőségi vizualizációval rendelkezik — láthatja a kapcsolati gráfot és észlelheti az alacsony kohéziójú osztályokat. A SonarQube automatikusan számítja az LCOM metrikákat.
Igen. Egy interfész a connect, disconnect és isConnected metódusokkal magas kohéziójú — minden metódus a kapcsolatkezeléshez kapcsolódik. Egy interfész a connect, parseData és renderUI metódusokkal alacsony kohéziójú. Az Interface Segregation (SOLID) elv speciális, magas kohéziójú interfészek létrehozását követeli meg.
Tegyen fel három kérdést: leírható az osztály célja egy mondatban? Támogatják ezt a célt az összes metódusok? Vannak-e az osztályban olyan mezők, amelyeket a metódusok egy része nem használ? Ha bármelyik kérdésre nem a válasz — alacsony a cohesion, és az osztályt fel kell osztani.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is