Jak ustalić, czy klasa spełnia zasadę jednej odpowiedzialności?

34

Zasada jednolitej odpowiedzialności oparta jest na zasadzie wysokiej spójności. Różnica między nimi polega na tym, że bardzo spójne klasy zawierają zestaw ściśle powiązanych ze sobą obowiązków, podczas gdy klasy przestrzegające SRP mają tylko jedną odpowiedzialność.

Ale w jaki sposób ustalamy, czy dana klasa ma zestaw obowiązków i dlatego jest po prostu bardzo spójna, czy też ma tylko jedną odpowiedzialność i w ten sposób przestrzega SRP? Innymi słowy, czy nie jest to mniej lub bardziej subiektywne, ponieważ niektórzy mogą uznać klasę za bardzo szczegółową (i jako tacy będą wierzyć, że klasa przestrzega SRP), podczas gdy inni mogą uznać ją za niewystarczająco szczegółową?

użytkownik1483278
źródło

Odpowiedzi:

21

Dlaczego tak, jest to bardzo subiektywne i jest przedmiotem wielu gorących debat o czerwonych twarzach, w które angażują się programiści.

Tak naprawdę nie ma jednej odpowiedzi, która może się zmienić w miarę, jak oprogramowanie staje się bardziej złożone. To, co kiedyś było pojedynczym, dobrze zdefiniowanym zadaniem, może ostatecznie stać się wieloma źle zdefiniowanymi zadaniami. To zawsze jest pocieranie. Jak wybrać właściwy sposób podziału programu na zadania?

Jedyną radą, jaką mogę udzielić, jest taka: skorzystaj z własnego osądu (i twoich współpracowników). I pamiętaj, że błędy można (zwykle) skorygować, jeśli złapiesz je wkrótce.

Jason Baker
źródło
Chciałbym, żeby informatyka bardziej przypominała prawdziwą naukę. Subiektywność nie ma miejsca w prawdziwej nauce. Chociaż zasady SOLID są w porządku same w sobie, należy je powtarzać, aby zminimalizować subiektywność i zmaksymalizować obiektywność. To się jeszcze nie wydarzyło, co sprawia, że ​​wątpię w ich prawdziwość.
DarkNeuron
13

Bob Martin (wujek Bob), który stworzył SOLIDNE zasady, których SRP jest pierwszy, mówi o tym (parafrazuję, nie pamiętam faktycznych słów):

Klasa powinna mieć tylko jeden powód do zmiany

Jeśli ma więcej niż jeden powód, nie przestrzega SRP.

Oded
źródło
14
To tylko powtórzenie definicji, ale przestrzeganie srp jest nadal dość subiektywne.
Andy,
7

Mogę podać kilka praktycznych zasad.

  • Jak łatwo nazwać klasę? Jeśli nazwa klasy jest trudna, prawdopodobnie robi to zbyt wiele.
  • Ile metod publicznych ma klasa? 7 +/- 2 to dobra zasada. Jeśli klasa ma więcej, powinieneś pomyśleć o podzieleniu jej na kilka klas.
  • Czy istnieją spójne grupy metod publicznych stosowane w różnych kontekstach?
  • Ilu jest prywatnych metod lub członków danych? Jeśli klasa ma złożoną strukturę wewnętrzną, prawdopodobnie powinieneś ją przefaktoryzować, aby elementy wewnętrzne były spakowane w oddzielne mniejsze klasy.
  • I najłatwiejsza zasada: jak duża jest klasa? Jeśli masz plik nagłówkowy C ++ zawierający pojedynczą klasę o długości przekraczającej kilkaset linii, prawdopodobnie powinieneś go podzielić.
Dima
źródło
2
Jeśli chodzi o twój drugi punkt, zobacz uxmyths.com/post/931925744/…
Cameron Martin
7
Zdecydowanie nie zgadzam się co do 7 +/- 2 - zasada pojedynczej odpowiedzialności dotyczy spójności semantycznej, a nie liczb arbitralnych.
JacquesB
1
Ogólna zasada nie wymaga niezależnego dowodu naukowego. Współczesna metoda naukowa ma wiele wieków, architektura i inżynieria to tysiące lat. Zasadą ogólną dla metod publicznych jest „kilka”, a żaden z parametrów nie jest „kilka”. W innych wiadomościach, chociaż rysunki niektórych dzieci pokazują, że ludzie nie wychodzą z głowy [potrzebne źródło].
abuzittin gillifirca
@CameronMartin W zależności od konfiguracji interfejs dla klasy może, ale nie musi być dostępny do przeczytania. Przeszukiwanie interfejsu użytkownika nie jest prawie tym samym, co pisanie kodu - jeśli muszę sprawdzać dokumentację co minutę, to przynajmniej podwajam czas potrzebny na wykonanie prawdziwej pracy.
Jaśniejsze
6

Zasady pojedynczej odpowiedzialności mówią, że każdy moduł oprogramowania powinien mieć tylko jeden powód do zmiany. W ostatnim artykule wujek Bob wyjaśnił „powód do zmiany”,

Myśląc o tej zasadzie, pamiętaj jednak, że przyczyną zmian są ludzie. To ludzie żądają zmian. I nie chcesz mylić tych ludzi ani siebie, mieszając ze sobą kod, którym wielu różnych ludzi zależy z różnych powodów.

Następnie wyjaśnił tę koncepcję na przykładzie TUTAJ .

theD
źródło
To świetny artykuł napisany przez samego mężczyznę.
MrDustpan
4

Aby odpowiedzieć na to pytanie, cofnij się o krok i zastanów się nad intencją zasady pojedynczej odpowiedzialności. Dlaczego przede wszystkim jest to zalecana zasada projektowania?

Celem tej zasady jest „podzielenie na przedziały” podstawy kodu, więc kod odnoszący się do pojedynczej „odpowiedzialności” jest izolowany w jednej jednostce. Ułatwia to znalezienie i zrozumienie kodu, a co ważniejsze, oznacza, że ​​zmiany w „odpowiedzialności” wpłyną tylko na jedną jednostkę kodu.

W systemie absolutnie nigdy nie chcesz, gdy jedna mała szansa powoduje awarię lub zmianę zachowań innej pozornie niezwiązanej części kodu. SRP pomaga izolować błędy i zmiany.

Czym zatem jest „odpowiedzialność”? Jest to coś, co można zmienić niezależnie od innych zmian. Załóżmy, że masz program, który może zapisać niektóre ustawienia w pliku konfiguracyjnym XML i może odczytać je z tego pliku. Czy jest to jedna odpowiedzialność, czy też „ładowanie” i „oszczędzanie” to dwie różne obowiązki? Każda zmiana formatu lub struktury pliku wymagałaby zmiany logiki ładowania i zapisu. Jest to zatem jedna odpowiedzialność, którą powinna reprezentować jedna klasa. Teraz rozważ aplikację, która może eksportować niektóre dane w formacie CVS, Excel i XML. W takim przypadku łatwo sobie wyobrazić, że jeden format może się zmienić bez wpływu na drugi. Jeśli zdecydujesz się zmienić separator w formacie CVS, nie powinno to wpłynąć na wynik programu Excel.

JacquesB
źródło
2

OO mówi, że klasy to grupowanie danych funkcjonalność. Ta definicja pozostawia wiele miejsca na subiektywną interpretację.

Wiemy, że klasy powinny być jasno i łatwo definiowane. Ale aby zdefiniować taką klasę, musimy mieć jasne pojęcie, w jaki sposób klasa pasuje do ogólnego projektu. Bez wymagań typu wodospadu, które paradoksalnie są uważane za anty-wzór ... jest to trudne do osiągnięcia.

Możemy wdrożyć projekt klasy z architekturą, która działa w większości przypadków, takich jak MVC. W aplikacjach MVC zakładamy, że mamy tylko dane, interfejs użytkownika i wymóg komunikacji między nimi.

Dzięki podstawowej architekturze łatwiej jest zidentyfikować przypadki, w których łamane są zasady pojedynczej odpowiedzialności. EG Przekazywanie wystąpienia kontroli użytkownika do modalu.

P.Brian.Mackey
źródło
1

Dla celów dyskusji przywołam klasę JUCE o nazwie AudioSampleBuffer . Teraz ta klasa istnieje, aby przechowywać fragment dźwięku (a może raczej długi fragment) dźwięku. Wie, że liczba kanałów, liczba próbek (na kanał) wydaje się być przypisana do 32-bitowej liczby zmiennoprzecinkowej IEEE, zamiast mieć zmienną reprezentację liczbową lub wielkość słów (ale to nie jest ze mną problem). Istnieją funkcje składowe, które pozwalają uzyskać numChannels lub numSamples i wskaźniki do dowolnego konkretnego kanału. Możesz zwiększyć lub zmniejszyć AudioSampleBuffer. Zakładam, że poprzednie zero-pads bufor, podczas gdy drugi obcina.

Istnieje kilka prywatnych członków tej klasy, które są używane do przydzielania miejsca na specjalnej stercie używanej przez JUCE.

Ale tego właśnie brakuje AudioSampleBuffer (i miałem na ten temat kilka dyskusji z Jules): członek o nazwie SampleRate. Jak mogło tego brakować?

Jedynym obowiązkiem, który AudioSampleBuffer musi spełnić, jest odpowiednie odwzorowanie fizycznego dźwięku, który słyszy, że reprezentują jego próbki. Po wprowadzeniu AudioSampleBuffer z czegoś, co czyta plik dźwiękowy lub ze strumienia, musisz uzyskać dodatkowy parametr i przekazać go wraz z AudioSampleBuffer do metod przetwarzania (powiedzmy, że jest to filtr), który musi znać częstotliwość próbkowania lub, ostatecznie do metody, która odtwarza bufor, aby go usłyszeć (lub przesyła go strumieniowo do innego miejsca). Cokolwiek.

Ale musisz nadal przekazywać ten parametr SampleRate, który jest nieodłączny od specyficznego dźwięku w AudioSampleBuffer, wszędzie wokół. Widziałem kod, w którym stała funkcji 44100.0f została przekazana do funkcji, ponieważ programista chyba nie wiedział, co jeszcze zrobić.

Jest to przykład niewywiązania się z pojedynczej odpowiedzialności.

Robert Bristol-Johnson
źródło
1

Można zrobić konkretny sposób, w oparciu o to, co powiedziałeś - że wysoka spójność pociąga za sobą pojedynczą odpowiedzialność, którą możesz zmierzyć spójność. Maksymalna spójna klasa ma wszystkie pola używane we wszystkich metodach. Chociaż maksymalna spójna klasa nie zawsze jest możliwa lub pożądana, nadal najlepiej jest do niej dotrzeć. Mając ten cel projektowania klas, łatwo jest wywnioskować, że twoja klasa nie może mieć wielu metod lub pól (niektórzy twierdzą najwyżej 7).

Innym sposobem jest czysta podstawa OOP - model po prawdziwych obiektach. O wiele łatwiej jest dostrzec odpowiedzialność prawdziwych obiektów. Jeśli jednak prawdziwy obiekt jest zbyt złożony, podziel go na wiele obiektów zawierających, każdy z nich ma swoją własną odpowiedzialność.

m3th0dman
źródło