fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
HOT / 14 minut

O kohezji słów kilka


09.03.2020

Jak zapewne pamiętasz, ostatnio napisałem, że coupling jest pojęciem dość abstrakcyjnym. Kohezja – zwana też spójnością – bije go w tym na głowę. Jak mam stwierdzić, czy moja klasa jest spójna, czy wręcz przeciwnie? Pewnie nieraz myślałeś: „Moja na pewno jest spójna, ale ta kolegi obok spójności nawet nie stała”.

Z kohezją od początku był problem, bo nawet nie wiedziano, jak ją nazwać. Była określana jako „functional relatedness” (czyli „powiązanie funkcjonalne”), jednak ten termin został później uznany za „niezdarny”. Proponowano też inne nazwy, takie jak „binding” czy „functionality”. Glenford Myers z kolei w Reliable software through composite design nazywał ją „siłą”, co w gruncie rzeczy dobrze oddaje znaczenie samego konceptu, bo jest to siła, z jaką powiązane są elementy konkretnej klasy.

Glenn Vanderburg opublikował na swoim blogu post, w którym wytłumaczył, dlaczego jego zdaniem programiści mają problem ze zrozumieniem tego konceptu. Jego głównym argumentem było właśnie to, że nazwa sama w sobie jest źle dobrana, bo nie jest tak opisowa jak na przykład „coupling” oraz że rzadko się ją stosuje w języku angielskim. Przez to ciężko jest mieć punkt odniesienia do sytuacji z życia codziennego. Jako swój sposób na lepsze zwizualizowanie, o co chodzi w tym pojęciu, Vanderburg przedstawił metodę polegającą na zestawieniu słowa „cohesion” z innymi wyrazami o tym samym rdzeniu („hesion”). Na przykład: „kohezja” pochodzi od tego samego słowa co „adhezja”, oznaczająca lepkość. Jeśli coś się lepi do czegoś innego, to jest to jednostronne i często spowodowane tym, że użyliśmy czegoś zewnętrznego, co tę lepkość powoduje, czyli kleju. Jako wzór Vanderburg podał taśmę klejącą, która nie musi pasować całkowicie do tego, do czego się przyczepi. Rzeczy, które są spójne (charakteryzują się wysoką kohezją), nie potrzebują niczego zewnętrznego, żeby się połączyć, ponieważ dobrze do siebie pasują.

Kohezja jest tym większa, im lepsze mamy zrozumienie problemu, dlatego tak ważne jest jego dogłębne przeanalizowanie. Naszym celem jest odpowiednie grupowanie elementów. Dzięki temu zacieśniamy relacje, jakie między nimi występują.

Wszystko, co dotychczas napisałem o kohezji, może nadal być rozumiane subiektywnie. Co zrobić, jeśli chcielibyśmy podejść do sprawy metodycznie, zamiast opierać się tylko na przeczuciu, że coś w klasie pasuje albo nie? Należałoby odpowiedzieć na pytanie: „Co powoduje grupowanie elementów w konkretną klasę?”. A bardziej szczegółowo: „Jakimi kryteriami grupowania się kierujemy?”. Teraz właśnie postaram się zaprezentować te kryteria w kolejności od charakteryzujących się najmniejszą kohezją do tych z największą.

Coincidental cohesion

Pierwsze kryterium podziału to w zasadzie jego brak, czyli całkowita przypadkowość w doborze elementów znajdujących się w klasie. Innymi słowy – wolna amerykanka. Takie klasy powstają mimowolnie, a nie w procesie designu.

Klasy z taką kohezją zazwyczaj nie występują, a jeśli już, to najczęściej są artefaktami modularyzacji na siłę. Decydujemy się podzielić system dla samego dzielenia albo gdy zauważymy, że konkretna sekwencja wywołań się powtarza. Postanawiamy wtedy zamknąć ją w jakąś klasę, chociaż kawałki kodu, które chcemy wydzielić, w każdym z użytych kontekstów mają zupełnie inne znaczenie. Z czasem okazuje się, że dla każdego z tych kontekstów musimy wprowadzić jakieś zmiany w tej klasie.

Listing 1. Przykład przypadkowości w doborze elementów.

Logical cohesion

Elementy spaja to, że rozwiązują taką samą klasę problemów. Dzięki temu jest coś, co logicznie łączy je ze sobą, ale wykonują one różne funkcjonalności.

Dobry przykład stanowią operacje matematyczne, które mogą być całkowicie od siebie niezależne. Każda z nich wykonuje inną funkcjonalność, a jednak zgrupowane są w ramach jednej klasy z… operacjami matematycznymi.

Listing 2. Wszystkie te metody łączy jedynie to, że są operacjami matematycznymi.

Temporal cohesion

Jeśli dodatkowo zwiążemy elementy czasem, w którym mają zostać wywołane, to otrzymamy spójność czasową.

Najlepszymi przykładami są moduły/klasy inicjalizujące, zatrzymujące lub czyszczące. Tak oto na przykład funkcje inicjalizujące są ze sobą logicznie związane, ponieważ spina je funkcja, jaką pełnią (inicjalizują coś). Ponadto elementy te są ze sobą powiązane czasowo, bo muszą być wywołane w tym samym oknie czasowym w tym przypadku czasie inicjalizacji.

Często jest tak, że niektóre moduły po prostu muszą charakteryzować się tego typu kohezją i trudno zakodować je inaczej.

Listing 3. Przykład spójności czasowej.

Procedural cohesion

Pojawia się wtedy, gdy zamiast modelować domenę problemu, koncentrujemy się wyłącznie na samym algorytmie wykonania – na przepływie kontroli. Ostatecznie prowadzi to do modelowania samego algorytmu – następujących po sobie kroków – a nie problemu, który rozwiązujemy.

Przez to, że elementy w module o spójności proceduralnej są związane ze sobą kolejnością występowania i czasem, kohezja między nimi jest jeszcze większa niż w przypadku spójności czasowej. Tam kolejność nie była istotna.

Spójność proceduralna charakteryzuje się występowaniem wielu pętli, warunków, kroków. Są to często klasy, których analiza sprawia, że chcemy sięgnąć po kartkę i ołówek, aby rozrysować schemat blokowy.

Communicational cohesion

Pierwszym typem kohezji, który koncentruje się na modelowanym problemie, jest spójność komunikacyjna. Tu kryterium grupowania jest oparte na tym, że elementy klasy są ze sobą powiązane komunikacyjnie, czyli że operują na tych samych danych wejściowych lub zwracają te same dane wyjściowe. Dzięki temu klasa może być łatwiej użyta w różnych kontekstach.

Tego typu klasy często powstają jako wynik myślenia o tym, jakie operacje można wykonać na konkretnym zbiorze danych lub w kontekście operacji, które muszą być wykonane, aby uzyskać konkretny wynik – konkretne dane wyjściowe.

Listing 4. Repozytorium jako przykład spójności komunikacyjnej.

Sequential cohesion

W momencie gdy głównym kryterium przynależności elementów do klasy staje się sekwencja przetwarzania danych, mamy do czynienia ze spójnością sekwencyjną. W praktyce może to wyglądać tak, że dane wyjściowe jednego elementu przetwarzającego są danymi wejściowymi kolejnego. Aktywność konkretnego elementu musi być poprzedzona aktywnością innego  w odpowiedniej sekwencji.

Może tu wystąpić jednak mały problem. Kiedy mamy taką sekwencję przetwarzania łańcuch wywołań  możemy w różny sposób ten łańcuch pogrupować. Dlatego klasy z tym typem kohezji mogą wykonywać więcej niż jedną funkcjonalność albo wręcz odwrotnie część funkcjonalności. Z tego właśnie powodu spójność sekwencyjna stoi niżej w hierarchii od ostatniego typu spójności funkcjonalnej.

Functional cohesion

Mamy z nią do czynienia wtedy, gdy elementy w klasie są tak dobrane, aby najlepiej jak to możliwe realizować konkretne zadanie – funkcjonalność. W takiej klasie każdy element przetwarzający stanowi jej integralną część i jest kluczowy dla funkcjonalności, którą klasa ma realizować. Klasa wykonuje nie mniej i nie więcej niż jedną funkcjonalność.

Co ciekawe, jako przykład podawane są często pojedyncze operacje matematyczne. Można sobie wyobrazić, że tworzymy klasę per operacja matematyczna. Dla porównania: w przypadku spójności logicznej tworzyliśmy klasę, która była zbitką różnych operacji.

O ile można łatwo stwierdzić, czy klasa charakteryzuje się tym rodzajem kohezji, o tyle w przypadku drobnych funkcjonalności, przy wyższych poziomach abstrakcji, często trudno jest to określić. Dlatego też robi się to przez porównywanie i sprawdzanie, czy klasa nie ma „defektów” niższych poziomów kohezji. Ot, taka metoda przez eliminację, podobnie jak to było w kontekście data couplingu.

Listing 5. Klasa prawdopodobnie wykonuje nie mniej i nie więcej niż jedną funkcjonalność.

Przy przedstawionym podziale na typy kohezji warto wspomnieć o jeszcze jednej rzeczy, na którą zwrócili uwagę Larry Constantine i Edward Yourdon. Choć bardzo niechętnie, zaproponowali oni przypisanie wagi każdemu z typów. Niechętnie, ponieważ nie chcieli, żeby były one używane do jakichkolwiek szczegółowych analiz czy też rzutowały w jakikolwiek sposób na przyszłe badania nad kohezją. Niemniej chcieli pokazać, że wartości wag w ich odczuciu rozkładają się nieliniowo:
0: coincidental,
1: logical,
3: temporal,
5: procedural,
7: communicational,
9: sequential,
10: functional.

Ma to na celu jedynie pomóc w podejmowaniu decyzji projektowych, gdy uda nam się poprawnie zidentyfikować poszczególne typy kohezji w naszym kodzie. Zauważmy, że według skali spójność sekwencyjna jest tak bliska ideału, że nieraz nie warto dążyć do spójności funkcjonalnej. W zamian za to lepiej poświęcić energię na poprawę miejsc, gdzie zysk będzie większy.

Czas na rozciąganie!

Na koniec chciałbym jeszcze wspomnieć o kilku prostych ćwiczeniach, które mogą zarówno pomóc określić typ, jakim charakteryzuje się klasa, jak i ułatwić osiagnięcie spójności klas.

Twórcy pojęcia „kohezja” zaproponowali technikę, która polega na zadaniu pytania: „Jaką właściwie operację wykonuje klasa (moduł)?”, a następnie na przedstawieniu tej operacji w postaci jednego zdania.

I tak:

  1. Operacja, jaką wykonuje klasa, która z natury charakteryzuje się spójnością funkcjonalną, powinna dać się opisać prostym zdaniem.
    Na przykład:
    „Oblicz VAT”.
    „Oblicz prowizję”.
    „Odczytaj temperaturę”.
    „Pobierz dane zamówienia”.
  2. Jeśli jedyną rozsądną drogą do opisania operacji klasy jest użycie zdania złożonego (z przecinkami, zawierającego wiele czasowników), to prawdopodobnie mamy niższą kohezję niż funkcjonalna i może to być spójność sekwencyjna, komunikacyjna lub logiczna.
    Na przykład:
    „Zaktualizuj czas spędzony nad zadaniem, ilość przepracowanego czasu pracownika i wartości na fakturze – wszystko na podstawie karty czasu pracy” – spójność komunikacyjna, ponieważ wszystkie elementy są powiązane ze sobą komunikacyjnie przez kartę czasu pracy.
    „Zaktualizuj zamówienie i zapisz je” – sekwencyjna.
  3. Jeśli opis zawiera słowa zorientowane na czas, takie jak „najpierw”, „następnie”, „później”, „wtedy”, „na początek”, „kiedy”, „dopóki”, to często mamy do czynienia ze spójnością czasową albo proceduralną.
    Przykłady:
    „Przed sortowaniem zapisz dane, usuń duplikaty i sprawdź sumy kontrolne” – spójność czasowa, ponieważ zakładamy, że kolejność w tym przypadku nie jest istotna.
    „Ściągnij kursy walut, następnie wyślij wyniki sprzedaży do działu sprzedaży i nowe zamówienia do działu zamówień” – prawdopodobnie spójność proceduralna, bo modelujemy algorytm procesu, gdzie kolejność jest istotna, jednak dane wyjściowe nie są zarazem danymi wejściowymi kolejnych elementów, nie jest to zatem spójność sekwencyjna.
  4. Jeśli po czasowniku występuje rzeczownik w liczbie mnogiej, to może to być spójność logiczna:
    „Zbierz statystyki: ilości wywołanych metod, ilości wyjątków, zużycie procesora” – elementy są związane ze sobą logicznie wyłącznie tym, że są statystykami, chociaż każda z nich ma całkowicie inną charakterystykę.
  5. Gdy występują słowa takie jak zainicjalizuj”, posprzątaj”, „utrzymaj”, to też często jest to spójność czasowa.

A co, jeśli chcielibyśmy określić, czy coś powinno należeć do klasy, która posiada już jakieś elementy? Można to zrobić, korzystając z zasady łączności, czyli przez analizę, czy dokładany element pasuje do pozostałych. Przykładową techniką jest po prostu próba ułożenia sobie w głowie zdania „Mój nowy element Z jest powiązany z klasą zawierającą X i Y, bo X, Y i Z są powiązane ze sobą ze względu na posiadanie takiej a takiej właściwości” – w tej lub podobnej formie.

Na koniec warto też dodać, że jeśli modelujemy jakiś problem, to najlepiej jest to robić w większym gronie. Dlaczego? Wspominałem, że kohezja będzie tym większa, im większe mamy zrozumienie problemu. Tutaj jest jednak pewien haczyk – każdy z nas, analizując dany problem, może mieć w głowie całkowicie inne jego wyobrażenie. A co za tym idzie – inny sposób jego rozwiązania. Dlatego tak ważne jest, aby zrozumienie problemu w zespole było jak najbardziej spójne.

Jak żyć?

Co zrobić, gdy kolega zarzuci nam na code review: „Twoja klasa charakteryzuje się niską kohezją”? Już czujemy, jak skacze nam adrenalina. Możemy zareagować różnie – uciec, walczyć albo… porozmawiać.

Zakładając, że nasz kolega nie jest dzikim zwierzęciem i po code review nie rozerwie nas na strzępy, spróbujmy skorzystać z opcji numer trzy. Może to jest właśnie sygnał do tego, aby podyskutować w zespole o tym, co rozumiemy pod pojęciem kohezji, a następnie – na jakie jej typy możemy sobie pozwolić w konkretnych miejscach naszego systemu.

 

0 0 votes
Article Rating
11 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Marcin
Marcin
4 years ago

Świetny artykuł.
Czy kohezje mogę rozumieć poprzez kontekst wokół którego tworzę rozwiązanie?

Marcin
Marcin
4 years ago
Reply to  Marcin

Mam wrażenie, że coupling i kohezja ze sobą walczą. Jeden stara się rozrywać a drugi łączyć.

Mariusz
Mariusz
4 years ago

Ciekawy artykuł ale zastanawiam się jakby to przełożyć na projekt w którym pracuję bo mamy klasę którą można opisać prostym zdaniem np “zaimportuj fakturę do księgowości” i tu klops bo pod tym kryję się cała masa funkcjonalności np 1. dodaj rejestr 2. dodaj księgę 3. dodaj kontrahenta 4. dodaj płatność itd. Tak samo Twój przykład “Pobierz dane zamówienia” może okazać się bardzo złożony. Tu jakby jest problem na jakim poziomie ma być ta kohezja, ktoś potrafi to wytłumaczyć jak prostemu chłopu ? :)

Marcin
Marcin
4 years ago
Reply to  Mariusz

Ale ja tu widzę cztery klasy, co najmniej. Może rozpisz to sobie za pomocą Event Stormingu, tam wyjdzie, które części są bliżej, a które dalej.

Damian Naprawa
4 years ago

Ciekawe… Mimo że nieświadomie stosuję te zasady, nie wiedziałem, że mają one swoją definicję :) Dzięki!

Arkadiusz Kałkus
3 years ago

Bardzo ciekawy temat, o którym chyba niewiele się mówi. Mam pytanie – czy ze wszystkich rodzajów spójności powinniśmy dążyć do spójności funkcjonalnej (autorzy przypisali jej najwyższa wagę oraz najlepiej brzmi w jednozdaniowym opisie)?

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również