Kiedy stosuje się zasadę pojedynczej odpowiedzialności, co stanowi „odpowiedzialność?”

198

Wydaje się całkiem jasne, że „zasada pojedynczej odpowiedzialności” nie oznacza „tylko jednej rzeczy”. Po to są metody.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin mówi, że „klasy powinny mieć tylko jeden powód do zmiany”. Ale trudno jest się ogarnąć, jeśli jesteś programistą w SOLID.

Napisałem odpowiedź na inne pytanie , w którym zasugerowałem, że obowiązki są jak stanowiska pracy, i tańczyłem wokół tego tematu, używając metafory restauracji, aby zilustrować mój punkt widzenia. Ale to wciąż nie określa zestawu zasad, które ktoś mógłby wykorzystać do zdefiniowania obowiązków swoich klas.

Jak to robisz? Jak określasz obowiązki każdej klasy i jak określasz odpowiedzialność w kontekście SRP?

Robert Harvey
źródło
28
Prześlij do recenzji kodu i zostań rozdarty :-D
Jörg W Mittag
8
@ JörgWMittag Hej, nie odstraszaj ludzi :)
Flambino
117
Takie pytania od doświadczonych członków pokazują, że zasady i zasady, których staramy się przestrzegać, nie są w żadnym razie proste ani proste . Oni [rodzaj] wewnętrznie sprzeczne i mistyczne ... jak każdy dobry zestaw reguł powinno być. I chciałbym wierzyć w takie pytania, jak pokorny i mądry, i dać nadzieję tym, którzy czują się beznadziejnie głupi. Dzięki, Robert!
svidgen
41
Zastanawiam się, czy pytanie to zostałoby zanotowane + oznaczone od razu, gdyby zostało wysłane przez nooba :)
Andrejs
9
@rmunn: lub innymi słowy - duży przedstawiciel przyciąga jeszcze więcej przedstawicieli, ponieważ nikt nie anulował podstawowych ludzkich uprzedzeń dotyczących wymiany stosów
Andrejs

Odpowiedzi:

117

Jednym ze sposobów obejścia tego problemu jest wyobrażenie sobie potencjalnych zmian wymagań w przyszłych projektach i zadanie sobie pytania, co trzeba zrobić, aby je zrealizować.

Na przykład:

Nowe wymagania biznesowe: Użytkownicy z Kalifornii otrzymują specjalną zniżkę.

Przykład „dobrej” zmiany: muszę zmodyfikować kod w klasie, która oblicza rabaty.

Przykład złych zmian: muszę zmodyfikować kod w klasie użytkownika, a zmiana ta będzie miała efekt kaskadowy na innych klasach korzystających z klasy użytkownika, w tym klasach, które nie mają nic wspólnego ze zniżkami, np. Rejestracja, wyliczenie i zarządzanie.

Lub:

Nowe niefunkcjonalne wymaganie: zaczniemy używać Oracle zamiast SQL Server

Przykład dobrej zmiany: wystarczy zmodyfikować jedną klasę w warstwie dostępu do danych, która określa sposób zachowania danych w DTO.

Zła zmiana: muszę zmodyfikować wszystkie moje klasy warstw biznesowych, ponieważ zawierają one logikę specyficzną dla SQL Server.

Chodzi o to, aby zminimalizować wpływ przyszłych potencjalnych zmian, ograniczając modyfikacje kodu do jednego obszaru kodu na obszar zmian.

Co najmniej, twoje klasy powinny oddzielić logiczne obawy od fizycznych. Wielki zestaw przykładów można znaleźć w System.IOprzestrzeni nazw: nie możemy znaleźć różne rodzaje strumieni fizycznych (np FileStream, MemoryStreamalbo NetworkStream) i różnych czytelników i pisarzy ( BinaryWriter, TextWriter), które działają na poziomie logicznym. Oddzielając je w ten sposób unikamy combinatoric eksplozji: zamiast konieczności FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter, i MemoryStreamBinaryWriter, wystarczy podłączyć nagrywarkę i strumienia i można mieć, co chcesz. Następnie możemy dodać, powiedzmy, XmlWriterbez konieczności ponownego implementowania go dla pamięci, plików i sieci osobno.

John Wu
źródło
34
Chociaż zgadzam się z myśleniem naprzód, istnieją zasady takie jak YAGNI, a metodologie takie jak TDD sugerują coś wręcz przeciwnego.
Robert Harvey
87
YAGNI mówi nam, abyśmy nie budowali rzeczy, których nie potrzebujemy dzisiaj. Nie mówi, aby nie budować rzeczy w sposób, który można rozszerzyć. Zobacz także zasadę otwartego / zamkniętego , która stwierdza, że ​​„jednostki oprogramowania (klasy, moduły, funkcje itp.) Powinny być otwarte na rozszerzenie, ale zamknięte na modyfikację”.
John Wu
18
@JohnW: +1 za sam komentarz do YAGNI. Nie mogę uwierzyć, ile muszę tłumaczyć ludziom, że YAGNI nie jest pretekstem do zbudowania sztywnego, nieelastycznego systemu, który nie jest w stanie zareagować na zmiany - jak na ironię jest to przeciwieństwo tego, do czego dążą SRP i dyrektorzy Open / Closed.
Greg Burghardt
36
@JohnWu: Nie zgadzam się, YAGNI mówi nam dokładnie, abyśmy nie budowali rzeczy, których dzisiaj nie potrzebujemy. Na przykład czytelność i testy to coś, czego program zawsze potrzebuje „dzisiaj”, więc YAGNI nigdy nie jest usprawiedliwieniem, aby nie dodawać punktów struktury i punktów wstrzykiwania. Jednak gdy tylko „rozszerzalność” powoduje znaczne koszty, dla których korzyści nie są oczywiste „dzisiaj”, YAGNI oznacza unikanie tego rodzaju rozszerzalności, ponieważ ta ostatnia prowadzi do nadinżynierii.
Doc Brown
9
@JohnWu Przeszliśmy z SQL 2008 na 2012. Były w sumie dwa zapytania, które wymagały zmiany. A od uwierzytelniania SQL do zaufanego? Dlaczego miałaby to być nawet zmiana kodu; wystarczy zmienić połączenie w pliku konfiguracyjnym. Znowu YAGNI. YAGNI i SRP są czasem konkurencyjnymi problemami i musisz ocenić, który z nich ma lepszy koszt / korzyść.
Andy
76

Praktycznie rzecz biorąc, obowiązki ograniczają rzeczy, które mogą się zmienić. Tak więc nie ma naukowego ani formalnego sposobu osiągnięcia tego, co stanowi odpowiedzialność, niestety. To jest wyrok sądu.

Chodzi o to, co według twojego doświadczenia może się zmienić.

Stosujemy język zasady w hiperbolicznej, dosłownej, gorliwej furii. Mamy tendencję do dzielenia klas, ponieważ mogą się zmieniać lub postępować zgodnie z liniami, które po prostu pomagają nam rozwiązać problemy. (Ten drugi powód nie jest z natury zły). Ale SRP nie istnieje dla samego siebie; służy do tworzenia konserwowalnego oprogramowania.

Więc ponownie, jeśli podziały nie są napędzane przez prawdopodobne zmiany, to nie są tak naprawdę w służbie dla SRP 1, jeśli YAGNI ma większe zastosowanie. Oba służą temu samemu ostatecznemu celowi. I oba są sprawami osądu - mam nadzieję, że przyprawiony osąd.

Kiedy wujek Bob pisze o tym, sugeruje, abyśmy myśleli o „odpowiedzialności” w kategoriach „kto prosi o zmianę”. Innymi słowy, nie chcemy, aby Partia A straciła pracę, ponieważ Partia B poprosiła o zmianę.

Pisząc moduł oprogramowania, należy upewnić się, że zmiany są wymagane tylko od jednej osoby, a raczej od ściśle powiązanej grupy osób reprezentujących jedną wąsko zdefiniowaną funkcję biznesową. Chcesz odizolować moduły od złożoności organizacji jako całości i zaprojektować systemy tak, aby każdy moduł był odpowiedzialny (odpowiadał) na potrzeby tylko jednej funkcji biznesowej. ( Wujek Bob - Zasada pojedynczej odpowiedzialności )

Dobrzy i doświadczeni programiści będą mieli poczucie, które zmiany są prawdopodobne. Ta lista mentalna będzie się nieco różnić w zależności od branży i organizacji.

Co stanowi odpowiedzialność w danym zastosowaniu, w danej organizacji, jest ostatecznie kwestią doprawione wyroku. Chodzi o to, co może się zmienić. W pewnym sensie chodzi o to, kto jest właścicielem wewnętrznej logiki modułu.


1. Aby być jasnym, nie oznacza to, że są to złe podziały. Mogą to być wielkie podziały, które znacznie poprawiają czytelność kodu. Oznacza to po prostu, że nie kieruje nimi SRP.

svidgen
źródło
11
Najlepsza odpowiedź, a właściwie cytuje myśli wuja Boba. Jeśli chodzi o to, co może się zmienić, wszyscy robią wielkie rzeczy w stosunku do I / O, „co jeśli zmienimy bazę danych?” lub „co, jeśli przejdziemy z XML na JSON?” Myślę, że jest to zwykle mylne. Prawdziwe pytanie powinno brzmieć: „co jeśli będziemy musieli zmienić tę liczbę całkowitą na liczbę zmiennoprzecinkową, dodać pole i zmienić ten ciąg znaków na listę ciągów znaków?”
user949300
2
To jest oszustwo. Pojedyncza odpowiedzialność sama w sobie jest tylko proponowanym sposobem „izolacji izolacji”. Wyjaśnienie, że musisz wyodrębnić zmiany, aby odpowiedzialność była „pojedyncza”, nie sugeruje, jak to zrobić, po prostu wyjaśnia pochodzenie wymogu.
Basilevs,
6
@Basilevs Staram się poprawić niedostatek, który widzisz w tej odpowiedzi - nie wspominając o odpowiedzi wuja Boba! Być może jednak muszę wyjaśnić, że SRP nie polega na zapewnieniu, że „zmiana” wpłynie tylko na 1 klasę. Chodzi o to, aby każda klasa zareagowała tylko na „jedną zmianę”. ... Chodzi o próbę narysowania strzał z każdej klasy jednemu właścicielowi. Nie od każdego właściciela do jednej klasy.
svidgen
2
Dziękujemy za pragmatyczną odpowiedź! Nawet wujek Bob ostrzega przed gorliwym przestrzeganiem zasad SOLID w architekturze Agile . Nie mam pod ręką cytatu, ale w zasadzie mówi, że podział obowiązków z natury zwiększa poziom abstrakcji w twoim kodzie i że cała abstrakcja wiąże się z kosztami, więc upewnij się, że korzyści wynikające z zastosowania SRP (lub innych zasad) przewyższają koszt dodawania większej ilości abstrakcji. (ciąg dalszy następnego komentarza)
Michael L.
4
Dlatego powinniśmy umieścić produkt przed klientem tak wcześnie i tak często, jak to rozsądne, aby wymusili zmiany w naszym projekcie i możemy zobaczyć, jakie obszary mogą się zmienić w tym produkcie. Dodatkowo ostrzega, że ​​nie możemy się chronić przed każdą zmianą. W przypadku każdej aplikacji pewne zmiany będą trudne. Musimy upewnić się, że są to zmiany, które najprawdopodobniej nie nastąpią.
Michael L.,
29

Śledzę: „klasy powinny mieć tylko jeden powód do zmiany”.

Dla mnie oznacza to myślenie o programach typu harebrain, które może wymyślić mój właściciel produktu („Musimy wspierać telefon komórkowy!”, „Musimy przejść do chmury!”, „Musimy wspierać chiński!”). Dobre projekty ograniczą wpływ tych programów do mniejszych obszarów i uczynią je stosunkowo łatwymi do osiągnięcia. Złe projekty oznaczają przechodzenie do dużej ilości kodu i dokonywanie ryzykownych zmian.

Doświadczenie jest jedyną rzeczą, którą znalazłem, aby właściwie ocenić prawdopodobieństwo tych szalonych schematów - ponieważ ułatwienie jednego może utrudnić dwóm innym - i ocenę dobroci projektu. Doświadczeni programiści mogą wyobrazić sobie, co powinni zrobić, aby zmienić kod, co leży i ugryzie ich w tyłek oraz jakie sztuczki ułatwiają to. Doświadczeni programiści mają dobre wyczucie, jak bardzo są wkręceni, gdy właściciel produktu prosi o szalone rzeczy.

Praktycznie uważam, że testy jednostkowe są tutaj pomocne. Jeśli kod jest nieelastyczny, trudno będzie go przetestować. Jeśli nie możesz wstrzykiwać próbnych lub innych danych testowych, prawdopodobnie nie będziesz w stanie wstrzyknąć tego SupportChinesekodu.

Innym przybliżonym miernikiem jest wysokość windy. Tradycyjne stanowiska windowe to „jeśli byłeś w windzie z inwestorem, czy możesz go sprzedać według pomysłu?”. Startupy muszą mieć proste, krótkie opisy tego, co robią - na czym się koncentrują. Podobnie, klasy (i funkcje) powinny mieć prosty opis tego, co robią . Nie „ta klasa implementuje niektóre Fubar, dzięki czemu można ich używać w tych konkretnych scenariuszach”. Coś, co możesz powiedzieć innemu deweloperowi: „Ta klasa tworzy użytkowników”. Jeśli nie można komunikować się, że do innych deweloperów, masz zamiar dostać się błędy.

Telastyn
źródło
Czasami udaje się wdrożyć coś, co uważacie za niechlujną zmianę, i okazuje się to proste, lub mały refaktor upraszcza to i dodaje użytecznej funkcjonalności w tym samym czasie. Ale tak, zwykle widać nadchodzące problemy.
16
Jestem wielkim zwolennikiem pomysłu „windy”. Jeśli trudno jest wyjaśnić, co robi klasa w jednym lub dwóch zdaniach, jesteś na ryzykownym terytorium.
Ivan
1
Dotykasz ważnej kwestii: prawdopodobieństwo tych szalonych schematów różni się znacznie w zależności od właściciela projektu. Musisz polegać nie tylko na swoim ogólnym doświadczeniu, ale także na tym, jak dobrze znasz właściciela projektu. Pracowałem dla ludzi, którzy chcieli skrócić nasze sprinty do jednego tygodnia i nadal nie mogłem uniknąć zmiany kierunku w połowie sprintu.
Kevin Krumwiede
1
Oprócz oczywistych korzyści, dokumentowanie kodu za pomocą „skoków wysokości” służy również do pomocy w pomyśleniu o tym, co robi Twój kod przy użyciu języka naturalnego, co uważam za przydatne w odkrywaniu wielu obowiązków.
Alexander
1
@KevinKrumwiede Do tego właśnie służą metodologie „Kurczaka biegającego z odciętą głową” i „Wild Goose Chase”!
26

Nikt nie wie. A przynajmniej nie jesteśmy w stanie uzgodnić jednej definicji. To sprawia, że ​​SPR (i inne zasady SOLID) są dość kontrowersyjne.

Twierdziłbym, że jedną z umiejętności, których twórca oprogramowania musi się nauczyć w trakcie swojej kariery zawodowej, jest dowiedzieć się, na czym polega odpowiedzialność. Im więcej kodu piszesz i recenzujesz, tym więcej doświadczenia będziesz musiał określić, czy coś jest pojedynczą czy wielokrotną odpowiedzialnością. Lub jeśli pojedyncza odpowiedzialność jest podzielona na oddzielne części kodu.

Twierdziłbym, że głównym celem SRP nie jest twarda zasada. Ma nam przypominać o spójności kodu i zawsze podejmować świadomy wysiłek, aby ustalić, który kod jest spójny, a co nie.

Euforyk
źródło
20
Nowi programiści mają tendencję do traktowania SOLID, jakby to był zbiór praw, co nie jest prawdą. Jest to tylko grupa dobrych pomysłów, które pomogą ludziom poprawić się w projektowaniu klas. Niestety, ludzie traktują te zasady zbyt poważnie; Niedawno widziałem ogłoszenie o pracę, w którym wymieniono SOLID jako jedno z wymagań pracy.
Robert Harvey
9
+42 za ostatni akapit. Jak mówi @RobertHarvey, rzeczy takie jak SPR, SOLID i YAGNI nie powinny być traktowane jako „ bezwzględne reguły ”, ale jako ogólne zasady „dobrych rad”. Porady między nimi (i innymi) są czasami sprzeczne, ale wyważenie tej porady (w przeciwieństwie do przestrzegania sztywnego zestawu zasad) (z czasem, wraz z rosnącym doświadczeniem) poprowadzi cię do tworzenia lepszego oprogramowania. Przy tworzeniu oprogramowania powinna istnieć tylko jedna „bezwzględna reguła”: „ Nie ma bezwzględnych reguł ”.
TripeHound
Jest to bardzo dobre wyjaśnienie jednego aspektu SRP. Ale nawet jeśli SOLIDNE zasady nie są twardymi regułami, nie są strasznie cenne, jeśli nikt nie rozumie ich znaczenia - tym bardziej, jeśli twierdzenie, że „nikt nie wie” jest prawdą! ... ma sens, aby były trudne do zrozumienia. Jak w przypadku każdej umiejętności, istnieje coś , co odróżnia dobro od mniej dobrego! Ale ... „nikt nie wie” czyni to bardziej mglistym rytuałem. (I nie wierzę, że taka jest intencja SOLID!)
svidgen
3
Przez „Nikt nie wie” mam nadzieję, że @Euphoric oznacza po prostu, że nie ma precyzyjnej definicji, która działałaby dla każdego przypadku użycia. Jest to coś, co wymaga pewnego osądu. Myślę, że jednym z najlepszych sposobów ustalenia, na czym spoczywają twoje obowiązki, jest szybkie iterowanie i powiadomienie twojej bazy kodów . Poszukaj „zapachów”, których kodu nie da się łatwo utrzymać. Na przykład, gdy zmiana jednej reguły biznesowej zaczyna mieć wpływ na kaskadę poprzez pozornie niepowiązane klasy, prawdopodobnie masz naruszenie SRP.
Michael L.
1
Bardzo serdecznie popieram @TripeHound i innych, którzy zwrócili uwagę, że wszystkie te „reguły” nie istnieją, aby zdefiniować Jedną Prawdziwą Religię rozwoju, ale aby zwiększyć prawdopodobieństwo opracowania oprogramowania, które można utrzymać. Zachowaj ostrożność, przestrzegając „najlepszych praktyk”, jeśli nie potrafisz wyjaśnić, w jaki sposób promuje ono oprogramowanie, które można utrzymywać, poprawia jakość lub zwiększa efektywność rozwoju.
Michael L.,
5

Myślę, że termin „odpowiedzialność” jest użyteczny jako metafora, ponieważ pozwala nam korzystać z oprogramowania w celu sprawdzenia, jak dobrze jest ono zorganizowane. W szczególności skupiłbym się na dwóch zasadach:

  • Odpowiedzialność jest współmierna do władzy.
  • Żadne dwa podmioty nie powinny być odpowiedzialne za to samo.

Te dwie zasady pozwalają nam znacząco oddzielić odpowiedzialność, ponieważ wzajemnie się ze sobą bawią. Jeśli upoważniasz kawałek kodu do zrobienia czegoś dla siebie, musi on ponosić odpowiedzialność za to, co robi. Powoduje to odpowiedzialność, że klasa musi być w stanie urosnąć, rozszerzając to „jeden powód do zmiany” na coraz szersze zakresy. Jednak, gdy poszerzasz zakres, naturalnie zaczynasz napotykać sytuacje, w których wiele podmiotów jest odpowiedzialnych za to samo. Jest to obarczone problemami związanymi z rzeczywistą odpowiedzialnością, więc na pewno jest to również problem z kodowaniem. W rezultacie zasada ta powoduje zawężenie zakresu, gdy dzielisz odpowiedzialność na niepowielone paczki.

Oprócz tych dwóch, trzecia zasada wydaje się uzasadniona:

  • Odpowiedzialność można przekazać

Rozważ świeżo wybity program ... pusta tablica. Na początku masz tylko jeden byt, którym jest program jako całość. Odpowiada za ... wszystko. Oczywiście w pewnym momencie zaczniesz delegować odpowiedzialność na funkcje lub klasy. W tym momencie obowiązują dwie pierwsze zasady, które zmuszają cię do zrównoważenia tej odpowiedzialności. Program najwyższego poziomu jest nadal odpowiedzialny za ogólną wydajność, podobnie jak kierownik odpowiada za produktywność swojego zespołu, ale każda podjednostka została delegowana do odpowiedzialności, a wraz z nią uprawnienia do wykonywania tej odpowiedzialności.

Dodatkową zaletą jest to, że SOLID jest szczególnie kompatybilny z każdym oprogramowaniem firmowym, jakie może być konieczne. Każda firma na tej planecie ma jakieś pojęcie o tym, jak przekazać odpowiedzialność i nie wszyscy się z tym zgadzają. Jeśli przekażesz odpowiedzialność za oprogramowanie w sposób przypominający delegację Twojej firmy, przyszłym programistom będzie znacznie łatwiej dostosować się do sposobu, w jaki robisz to w tej firmie.

Cort Ammon
źródło
Nie jestem w 100% pewien, że to w pełni to wyjaśnia. Myślę jednak, że wyjaśnienie „odpowiedzialności” w odniesieniu do „władzy” jest wnikliwym sposobem na wyrażenie tego! (+1)
svidgen
Pirsig powiedział: „Masz tendencję do wbudowywania swoich problemów w maszynę”, co daje mi przerwę.
@nocomprende Masz również tendencję do budowania swoich mocnych stron w maszynie. Twierdziłbym, że kiedy twoje mocne strony i słabość są tymi samymi rzeczami, wtedy robi się interesująco.
Cort Ammon
5

W tej konferencji w Yale wujek Bob podaje ten zabawny przykład:

Wpisz opis zdjęcia tutaj

Mówi, że Employeema trzy powody do zmiany, trzy źródła wymagań dotyczących zmian, i podaje to dowcipne i zuchwałe , ale ilustracyjne wyjaśnienie:

  • Jeśli CalcPay()metoda zawiera błąd i kosztuje firmę miliony USD, dyrektor finansowy zwolni cię .

  • Jeśli ReportHours()metoda zawiera błąd i kosztuje firmę miliony USD, dyrektor operacyjny zwolni cię .

  • Jeśli w WriteEmmployee(metodzie) wystąpi błąd, który powoduje usunięcie wielu danych i kosztuje firmę miliony USD, CTO zwolni cię .

Zatem posiadanie trzech różnych funkcji poziomu C potencjalnie zwalnia cię z powodu kosztownych błędów w tej samej klasie, co oznacza, że ​​klasa ma zbyt wiele obowiązków.

Podaje to rozwiązanie, które rozwiązuje naruszenie SRP, ale musi rozwiązać naruszenie DIP, które nie jest pokazane na filmie.

Wpisz opis zdjęcia tutaj

Tulains Córdova
źródło
Ten przykład bardziej przypomina klasę, która ma niewłaściwe obowiązki.
Robert Harvey
4
@RobertHarvey Gdy klasa ma zbyt wiele obowiązków, oznacza to, że dodatkowe obowiązki są niewłaściwe .
Tulains Córdova
5
Słyszę, co mówisz, ale nie przekonuje mnie to. Istnieje różnica między klasą, która ma zbyt wiele obowiązków, a klasą, która robi coś, na czym nie ma żadnego interesu. Może to brzmieć tak samo, ale tak nie jest; liczenie orzeszków ziemnych to nie to samo, co nazywanie ich orzechami włoskimi. Jest to zasada wujka Boba i przykład wuja Boba, ale gdyby był wystarczająco opisowy, nie potrzebowalibyśmy tego pytania.
Robert Harvey
@RobertHarvey, jaka jest różnica? Te sytuacje wydają mi się izomorficzne.
Paul Draper,
3

Myślę, że lepszym sposobem na podzielenie rzeczy niż „powody zmiany” jest rozpoczęcie od zastanowienia się nad tym, czy sensowne byłoby wymaganie, aby kod, który musi wykonać dwie (lub więcej) akcji, musiałby zawierać osobne odniesienie do obiektu dla każdej akcji i czy użyteczny byłby obiekt publiczny, który mógłby wykonać jedną akcję, ale nie drugą.

Jeśli odpowiedzi na oba pytania są twierdzące, sugerowałoby to, że działania powinny być wykonane przez osobne klasy. Jeśli odpowiedzi na oba pytania są przeczące, sugerowałoby to, że z publicznego punktu widzenia powinna istnieć jedna klasa; jeśli kod tego byłby niewygodny, może być wewnętrznie podzielony na klasy prywatne. Jeśli odpowiedź na pierwsze pytanie brzmi „nie”, ale na drugie pytanie „tak”, dla każdej akcji powinna istnieć osobna klasa plus klasa złożona, która zawiera odniesienia do instancji innych.

Jeśli ktoś ma osobne klasy dla klawiatury kasy, brzęczyka, odczytu numerycznego, drukarki pokwitowań i szuflady kasowej, a nie ma klasy złożonej dla pełnej kasy fiskalnej, kod, który ma przetwarzać transakcję, może zostać przypadkowo wywołany w sposób, który pobiera dane z klawiatury jednego urządzenia, wytwarza hałas z sygnału dźwiękowego drugiego urządzenia, pokazuje liczby na wyświetlaczu trzeciego urządzenia, drukuje pokwitowanie na drukarce czwartego urządzenia i wysuwa szufladę kasową piątego urządzenia. Każda z tych funkcji podrzędnych może być użytecznie obsługiwana przez osobną klasę, ale powinna również istnieć klasa złożona, która do nich dołącza. Klasa złożona powinna przekazać tyle logiki klasom, ile to możliwe,

Można powiedzieć, że „odpowiedzialnością” każdej klasy jest albo włączenie jakiejś prawdziwej logiki, albo zapewnienie wspólnego punktu przyłączenia dla wielu innych klas, które to robią, ale ważne jest przede wszystkim skupienie się na tym, jak kod klienta powinien postrzegać klasę. Jeśli kod klienta ma sens widzieć coś jako pojedynczy obiekt, wówczas kod klienta powinien widzieć to jako pojedynczy obiekt.

supercat
źródło
To dobra rada. Warto zauważyć, że dzielisz obowiązki według większej liczby kryteriów niż tylko SRP.
Jørgen Fogh
1
Analogia samochodowa: Nie muszę wiedzieć, ile gazu znajduje się w czyimś zbiorniku, ani nie chcę włączać wycieraczek innej osoby. (ale taka jest definicja Internetu) (Cii!
1
@nocomprende - „Nie muszę wiedzieć, ile gazu znajduje się w czyimś zbiorniku” - chyba że jesteś nastolatkiem, który próbuje zdecydować, który samochód rodziny pożyczyć na następną podróż…;)
alephzero
3

SRP trudno jest naprawić. Jest to głównie kwestia przypisania „zadań” do twojego kodu i upewnienia się, że każda część ma jasne obowiązki. Podobnie jak w prawdziwym życiu, w niektórych przypadkach dzielenie pracy między ludzi może być całkiem naturalne, ale w innych przypadkach może być naprawdę trudne, szczególnie jeśli ich nie znasz (lub pracę).

Zawsze zalecam po prostu napisanie prostego kodu, który najpierw działa , a następnie trochę zawęzić: Po pewnym czasie zobaczysz, jak kod klastruje się naturalnie. Myślę, że błędem jest narzucanie odpowiedzialności, zanim poznasz kod (lub ludzi) i pracę do wykonania.

Jedną z rzeczy, które zauważysz, jest to, że moduł zaczyna robić za dużo i jest trudny do debugowania / utrzymania. To jest moment na refaktoryzację; jaka powinna być podstawowa praca i jakie zadania można przekazać innemu modułowi? Na przykład, czy powinien obsługiwać kontrole bezpieczeństwa i inne czynności, czy najpierw należy przeprowadzać kontrole bezpieczeństwa gdzie indziej, czy może to skomplikować kod?

Jeśli użyjesz zbyt wielu pośredników, znów stanie się bałaganem ... jeśli chodzi o inne zasady, ten będzie w konflikcie z innymi, takimi jak KISS, YAGNI itp. Wszystko jest kwestią równowagi.

Christophe Roussy
źródło
Czy SRP nie jest po prostu napisane jako „Spójność” Konstantyna?
Nick Keighley,
Naturalnie znajdziesz te wzorce, jeśli kodujesz wystarczająco długo, ale możesz przyspieszyć naukę, nazywając je i pomaga to w komunikacji ...
Christophe Roussy
@NickKeighley Myślę, że jest to spójność, nie tyle napisana duża, ale patrząc z innej perspektywy.
sdenham
3

„Zasada pojedynczej odpowiedzialności” może być mylącą nazwą. „Tylko jeden powód do zmiany” jest lepszym opisem zasady, ale nadal łatwo ją źle zrozumieć. Nie mówimy o tym, co powoduje zmianę stanu obiektów w czasie wykonywania. Zastanawiamy się, co może spowodować, że programiści będą musieli zmienić kod w przyszłości.

O ile nie naprawimy błędu, zmiana będzie spowodowana nowym lub zmienionym wymaganiem biznesowym. Będziesz musiał myśleć poza samym kodem i wyobrażać sobie, jakie czynniki zewnętrzne mogą spowodować, że wymagania zmienią się niezależnie . Mówić:

  • Stawki podatkowe zmieniają się w wyniku decyzji politycznej.
  • Marketing postanawia zmienić nazwy wszystkich produktów
  • Interfejs użytkownika musi zostać przeprojektowany, aby był dostępny
  • Baza danych jest przepełniona, więc musisz dokonać optymalizacji
  • Musisz pomieścić aplikację mobilną
  • i tak dalej...

Idealnie chcesz, aby niezależne czynniki wpływały na różne klasy. Np. Ponieważ stawki podatkowe zmieniają się niezależnie od nazw produktów, zmiany nie powinny wpływać na te same klasy. W przeciwnym razie ryzykujesz wprowadzeniem zmiany podatkowej, błędem w nazwie produktu, który jest rodzajem ścisłego połączenia, którego chcesz uniknąć w systemie modułowym.

Nie skupiaj się więc na tym, co może się zmienić - wszystko może się zmienić w przyszłości. Skoncentruj się na tym, co może się niezależnie zmienić. Zmiany są zwykle niezależne, jeśli są spowodowane przez różnych aktorów.

Twój przykład z tytułami pracy jest na dobrej drodze, ale powinieneś wziąć to dosłownie! Jeśli marketing może powodować zmiany w kodzie, a finanse mogą powodować inne zmiany, zmiany te nie powinny wpływać na ten sam kod, ponieważ są to dosłownie różne tytuły stanowisk, a zatem zmiany będą dokonywane niezależnie.

Cytując wuja Boba, który wynalazł ten termin:

Pisząc moduł oprogramowania, należy upewnić się, że zmiany są wymagane tylko od jednej osoby, a raczej od ściśle powiązanej grupy osób reprezentujących jedną wąsko zdefiniowaną funkcję biznesową. Chcesz odizolować moduły od złożoności organizacji jako całości i zaprojektować systemy tak, aby każdy moduł był odpowiedzialny (odpowiadał) na potrzeby tylko jednej funkcji biznesowej.

Podsumowując: „odpowiedzialność” odpowiada jednej funkcji biznesowej. Jeśli więcej niż jeden aktor może spowodować zmianę klasy, klasa prawdopodobnie łamie tę zasadę.

JacquesB
źródło
Według jego książki „Czysta architektura” jest to dokładnie słuszne. Reguły biznesowe powinny pochodzić z jednego źródła i tylko z jednego źródła. Oznacza to, że HR, operacje i IT muszą współpracować przy formułowaniu wymagań w ramach „pojedynczej odpowiedzialności”. I to jest zasada. +1
Benny Skogberg
2

Dobry artykuł, który wyjaśnia zasady programowania SOLID i podaje przykłady kodu zarówno przestrzegającego, jak i nie przestrzegającego tych zasad, to https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented- projekt .

W przykładzie dotyczącym SRP podaje przykład kilku klas kształtów (okrąg i kwadrat) i klasy zaprojektowanej do obliczania całkowitej powierzchni wielu kształtów.

W swoim pierwszym przykładzie tworzy klasę obliczającą obszar i zwraca jej wynik jako HTML. Później decyduje, że zamiast tego chce wyświetlić JSON i musi zmienić klasę obliczania obszaru.

Problem z tym przykładem polega na tym, że jego klasa obliczania powierzchni jest odpowiedzialna za obliczanie powierzchni kształtów ORAZ wyświetlanie tego obszaru. Następnie przechodzi lepszy sposób, aby to zrobić, używając innej klasy zaprojektowanej specjalnie do wyświetlania obszarów.

Jest to prosty przykład (i łatwiejsze do zrozumienia czytanie artykułu, ponieważ zawiera fragmenty kodu), ale pokazuje podstawową ideę SRP.

blitz1616
źródło
0

Przede wszystkim masz dwa osobne problemy: problem z metodami, które należy umieścić w swoich klasach, oraz problem z wzdęcia interfejsu.

Interfejsy

Masz ten interfejs:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Można przypuszczać, że masz wiele klas zgodnych z CustomerCRUDinterfejsem (w przeciwnym razie interfejs jest niepotrzebny) oraz niektóre funkcje, do_crud(customer: CustomerCRUD)które przyjmują zgodny obiekt. Ale już złamałeś SRP: powiązałeś te cztery różne operacje razem.

Powiedzmy, że później będziesz działał na widokach bazy danych. Widok bazy danych ma tylko ten Readsposób dostępne dla niego. Ale chcesz napisać funkcję, do_query_stuff(customer: ???)która operuje transparentnie na pełnych tabelach lub widokach; w końcu używa tylko tej Readmetody.

Utwórz interfejs

public Interface CustomerReader {public Customer Read (customerID: int)}

i uwzględnij swój CustomerCrudinterfejs jako:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Ale nie ma końca w zasięgu wzroku. Mogą istnieć obiekty, które możemy tworzyć, ale nie aktualizować itp. Ta królicza nora jest zbyt głęboka. Jedynym rozsądnym sposobem na przestrzeganie zasady pojedynczej odpowiedzialności jest sprawienie, aby wszystkie interfejsy zawierały dokładnie jedną metodę . Go faktycznie postępuje zgodnie z tą metodologią z tego, co widziałem, z ogromną większością interfejsów zawierających jedną funkcję; jeśli chcesz określić interfejs, który zawiera dwie funkcje, musisz niezręcznie utworzyć nowy interfejs, który łączy te dwie funkcje. Wkrótce otrzymasz kombinatoryczną eksplozję interfejsów.

Wyjściem z tego bałaganu jest zastosowanie podsieci strukturalnych (zaimplementowanych np. W OCaml) zamiast interfejsów (które są formą nominalnego podtypu). Nie definiujemy interfejsów; zamiast tego możemy po prostu napisać funkcję

let do_customer_stuff customer = customer.read ... customer.update ...

to wywołuje dowolne metody, które lubimy. OCaml użyje wnioskowania typu, aby ustalić, że możemy przekazać dowolny obiekt, który implementuje te metody. W tym przykładzie byłoby ustalone, że customerma typ <read: int -> unit, update: int -> unit, ...>.

Klasy

To rozwiązuje bałagan interfejsu ; ale nadal musimy wdrożyć klasy, które zawierają wiele metod. Na przykład, czy powinniśmy stworzyć dwie różne klasy CustomerReaderi CustomerWriter? Co jeśli chcemy zmienić sposób odczytywania tabel (np. Teraz buforujemy nasze odpowiedzi w redis przed pobraniem danych), ale teraz jak są one zapisywane? Jeśli podążysz za tym ciągiem rozumowania do jego logicznej konkluzji, jesteś nierozerwalnie związany z programowaniem funkcjonalnym :)

ogrodnik
źródło
4
„Bez znaczenia” jest trochę silny. Mógłbym przejść za „mistyczne” lub „zen”. Ale nie bez znaczenia!
svidgen
Czy możesz wyjaśnić nieco więcej, dlaczego podtypowanie strukturalne jest rozwiązaniem?
Robert Harvey
@RobertHarvey znacząco zrestrukturyzował moją odpowiedź
ogrodnik
4
Korzystam z interfejsów, nawet jeśli mam tylko jedną klasę, która je implementuje. Dlaczego? Kpiny z testów jednostkowych.
Eternal21
0

Moim zdaniem najbliższą SRP, która przychodzi mi do głowy, jest przepływ użytkowania. Jeśli nie masz wyraźnego przepływu dla danej klasy, prawdopodobnie twoja klasa ma zapach projektowy.

Przepływ użytkowania byłby daną kolejnością wywołań metod, która dałaby oczekiwany (a zatem testowalny) wynik. Zasadniczo definiujesz klasę za pomocą przypadków użycia, które otrzymała IMHO, dlatego cała metodologia programu skupia się na interfejsach nad implementacją.

Arthur Havlicek
źródło
0

Ma to na celu osiągnięcie wielu zmian wymagań, nie wymaga zmiany komponentu .

Ale powodzenia, rozumiejąc to na pierwszy rzut oka, kiedy po raz pierwszy usłyszysz o SOLID.


Widzę wiele komentarzy, które mówią, że SRP i YAGNI mogą być ze sobą sprzeczne, ale YAGN narzucony przez TDD (GOOS, London School) nauczył mnie myśleć i projektować moje komponenty z perspektywy klienta. Zacząłem projektować moje interfejsy w taki sposób, aby najmniej klient tego chciał, czyli jak mało powinien . I to ćwiczenie można wykonać bez znajomości TDD.

Podoba mi się technika opisana przez wuja Boba (niestety nie pamiętam skąd), która idzie w stylu:

Zadaj sobie pytanie, co robi ta klasa?

Czy twoja odpowiedź zawierała albo And, albo Or

Jeśli tak, wyodrębnij tę część odpowiedzi, jest to jej własna odpowiedzialność

Ta technika jest absolutna i, jak powiedział @svidgen, SRP jest wezwaniem do osądu, ale kiedy uczysz się czegoś nowego, absolutne są najlepsze, łatwiej jest po prostu zawsze coś zrobić. Upewnij się, że powodem, dla którego się nie rozdzieliłeś, jest; wykształcone oszacowanie, a nie dlatego, że nie wiesz jak. To jest sztuka i wymaga doświadczenia.


Wydaje mi się, że wiele odpowiedzi wydaje się stanowić argument za oddzieleniem płatności od produkcji w przypadku SRP .

SRP jest nie do upewnij się, że zmiana nie propagować dół wykres zależności.

Teoretycznie bez SRP nie byłoby żadnych zależności ...

Jedna zmiana nie powinna powodować zmiany w wielu miejscach aplikacji, ale mamy inne zasady. SRP poprawia jednak otwartą zasadę zamkniętą . Ta zasada dotyczy bardziej abstrakcji, jednak mniejsze abstrakty są łatwiejsze do reimplementacji .

Dlatego ucząc SOLID jako całości, uważaj, aby nauczyć się, że SRP pozwala zmieniać mniej kodu, gdy zmieniają się wymagania, podczas gdy w rzeczywistości pozwala ci pisać mniej nowego kodu.

Chris Wohlert
źródło
3
When learning something new, absolutes are the best, it is easier to just always do something.- Z mojego doświadczenia wynika, że ​​nowi programiści są zbyt dogmatyczni. Absolutyzm prowadzi do niemądrych programistów i programowania kultowego. Mówiąc „po prostu to zrobić” jest w porządku, tak długo, jak można zrozumieć, że osoba, z którą rozmawiasz będzie musiał później oduczyć czego nauczyły je.
Robert Harvey
@RobertHarvey, Zupełnie prawda, że ​​stwarza dogmatyczne zachowanie, i musisz się oduczyć / ponownie nauczyć w miarę zdobywania doświadczenia. Ale o to mi chodzi. Jeśli nowy programista próbuje wywoływać oceny bez żadnego powodu, aby uzasadnić swoją decyzję, wydaje się to bezużyteczne, ponieważ nie wiedzą, dlaczego to zadziałało. Nakłaniając ludzi do przesadzania , uczy ich szukania wyjątków zamiast zgadywania bez zastrzeżeń. Wszystko, co powiedziałeś, dotyczy absolutyzmu jest poprawne i dlatego powinien być tylko punktem wyjścia.
Chris Wohlert
@RobertHarvey, Szybki przykład z prawdziwego życia : Możesz nauczyć swoje dzieci, aby zawsze były uczciwe, ale w miarę starzenia się prawdopodobnie zdadzą sobie sprawę z kilku wyjątków, w których ludzie nie chcą słyszeć swoich najbardziej uczciwych myśli. Oczekiwanie, że 5-latek podejmie właściwą decyzję, mówiąc o byciu uczciwym, jest co najmniej optymistyczne. :)
Chris Wohlert
0

Nie ma na to jednoznacznej odpowiedzi. Chociaż pytanie jest wąskie, wyjaśnienia nie są.

Dla mnie jest to coś takiego jak Razam Razor, jeśli chcesz. To ideał, w którym próbuję zmierzyć mój obecny kod. Trudno to wyrazić prostymi i prostymi słowami. Inną metaforą byłby »jeden temat«, który jest tak abstrakcyjny, tzn. Trudny do zrozumienia, jak »jedna odpowiedzialność«. Trzecim opisem byłoby „zajmowanie się jednym poziomem abstrakcji”.

Co to znaczy praktycznie?

Ostatnio używam stylu kodowania, który składa się głównie z dwóch faz:

Fazę I najlepiej opisać jako twórczy chaos. W tej fazie piszę kod, gdy myśli płyną - tj. Surowy i brzydki.

Faza II jest całkowitym przeciwieństwem. To jest jak sprzątanie po huraganie. To wymaga najwięcej pracy i dyscypliny. A potem patrzę na kod z perspektywy projektanta.

Obecnie pracuję głównie w Pythonie, co pozwala mi później myśleć o obiektach i klasach. Pierwsza faza I - piszę tylko funkcje i rozkładam je prawie losowo w różnych modułach. W fazie II , kiedy już wszystko zaczęło działać, przyjrzałem się bliżej modułowi, który dotyczy danej części rozwiązania. A gdy przeglądam moduły, pojawiają się dla mnie tematy . Niektóre funkcje są powiązane tematycznie. To są dobrzy kandydaci na zajęcia . A po tym, jak zamieniłem funkcje w klasy - co jest prawie gotowe z wcięciem i dodaniem selfdo listy parametrów w pythonie;) - używam SRPjak Razor Razor do rozszyfrowania funkcjonalności innych modułów i klas.

Obecnym przykładem może być pisanie niewielkiej funkcjonalności eksportu innego dnia.

Potrzebny był plik CSV , Excel i połączone arkusze Excela w zipie.

Zwykła funkcjonalność została wykonana w trzech widokach (= funkcje). Każda funkcja wykorzystywała wspólną metodę określania filtrów i drugą metodę odzyskiwania danych. Następnie w każdej funkcji miało miejsce przygotowanie eksportu i zostało dostarczone jako odpowiedź z serwera.

Wymieszano zbyt wiele poziomów abstrakcji:

I) obsługa przychodzących / wychodzących wniosków / odpowiedzi

II) określanie filtrów

III) odzyskiwanie danych

IV) transformacja danych

Łatwym krokiem było użycie jednej abstrakcji ( exporter) do zajęcia się warstwami II-IV w pierwszym kroku.

Pozostał tylko temat dotyczący zapytań / odpowiedzi . Na tym samym poziomie abstrakcji jest wydobywanie parametrów żądania, co jest w porządku. Miałem więc za ten pogląd jedną „odpowiedzialność”.

Po drugie, musiałem rozbić eksportera, który, jak widzieliśmy, składał się z co najmniej trzech innych warstw abstrakcji.

Określanie kryteriów filtrowania i faktyczne wycofywanie jest prawie na tym samym poziomie abstrakcji (filtry są potrzebne, aby uzyskać odpowiedni podzbiór danych). Poziomy te zostały umieszczone w czymś w rodzaju warstwy dostępu do danych .

W następnym kroku podzieliłem rzeczywiste mechanizmy eksportu: tam, gdzie potrzebne było zapisywanie do pliku tymczasowego, podzieliłem to na dwie „obowiązki”: jedną do faktycznego zapisu danych na dysk i drugą część, która dotyczyła faktycznego formatu.

Wraz z tworzeniem się klas i modułów stało się jasne, co było gdzie. I zawsze ukryte pytanie, czy klasa robi za dużo .

Jak określasz obowiązki każdej klasy i jak określasz odpowiedzialność w kontekście SRP?

Trudno podać przepis do naśladowania. Oczywiście mógłbym powtórzyć tajemniczą »jeden poziom abstrakcji« - zasadę, jeśli to pomoże.

Przeważnie dla mnie jest to rodzaj „artystycznej intuicji”, która prowadzi do obecnego projektu; Modeluję kod tak, jak artysta może rzeźbić glinę lub malować.

Wyobraź sobie mnie jako kodującego Boba Rossa ;)

Thomas Junk
źródło
0

Co próbuję zrobić, aby napisać kod zgodny z SRP:

  • Wybierz konkretny problem, który musisz rozwiązać;
  • Napisz kod, który go rozwiązuje, napisz wszystko za pomocą jednej metody (np .: main);
  • Dokładnie przeanalizuj kod i, w zależności od firmy, spróbuj zdefiniować obowiązki widoczne we wszystkich wykonywanych operacjach (jest to subiektywna część, która zależy również od firmy / projektu / klienta);
  • Należy pamiętać, że wszystkie funkcje są już zaimplementowane; następna jest tylko organizacja kodu (od tego momentu nie będzie zaimplementowana żadna dodatkowa funkcja ani mechanizm);
  • W oparciu o obowiązki zdefiniowane w poprzednich krokach (które są zdefiniowane w oparciu o biznes i pomysł „jeden powód do zmiany”), wyodrębnij dla każdej osobną klasę lub metodę;
  • Należy pamiętać, że takie podejście dba tylko o SPR; idealnie powinny być tutaj dodatkowe kroki, starając się również przestrzegać innych zasad.

Przykład:

Problem: pobierz dwie liczby od użytkownika, oblicz ich sumę i wyślij wynik użytkownikowi:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Następnie spróbuj zdefiniować obowiązki na podstawie zadań, które należy wykonać. Z tego wyodrębnij odpowiednie klasy:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Następnie refaktoryzowany program staje się:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Uwaga: ten bardzo prosty przykład uwzględnia tylko zasadę SRP. Zastosowanie innych zasad (np. Kod „L” powinien zależeć raczej od abstrakcji niż konkrecji) zapewniłoby kodowi więcej korzyści i uczyniłoby go łatwiejszym w przypadku zmian biznesowych.

Emerson Cardoso
źródło
1
Twój przykład jest zbyt prosty, aby odpowiednio zilustrować SRP. Nikt nie zrobiłby tego w prawdziwym życiu.
Robert Harvey
Tak, w prawdziwych projektach piszę jakiś pseudokod zamiast dokładnego kodu, tak jak w moim przykładzie. Po pseudokodzie staram się rozdzielić obowiązki tak, jak w przykładzie. W każdym razie właśnie tak to robię.
Emerson Cardoso
0

Z książki Roberta C. Martinsa Clean Architecture: A Craftsman's Guide to Software Structure and Design , opublikowanej 10 września 2017 r., Robert pisze na stronie 62, co następuje:

Historycznie SRP opisano w następujący sposób:

Moduł powinien mieć jeden i tylko jeden powód do zmiany

Systemy oprogramowania są zmieniane, aby zadowolić użytkowników i interesariuszy; ci użytkownicy i interesariusze „powodem do zmiany”. o której mówi zasada. Rzeczywiście możemy sformułować tę zasadę w następujący sposób:

Moduł powinien być odpowiedzialny przed jednym i tylko jednym użytkownikiem lub interesariuszem

Niestety słowa „użytkownik” i „interesariusz” nie są tak naprawdę odpowiednim słowem do użycia tutaj. Prawdopodobnie będzie więcej niż jeden użytkownik lub interesariusz, który będzie chciał zmienić system w rozsądny sposób. Zamiast tego naprawdę mamy na myśli grupę - jedną lub więcej osób, które wymagają tej zmiany. Będziemy odnosić się do tej grupy jako aktor .

Zatem ostateczna wersja SRP to:

Moduł powinien być odpowiedzialny przed jednym i tylko jednym aktorem.

Więc nie chodzi o kod. SRP polega na kontrolowaniu przepływu wymagań i potrzeb biznesowych, które mogą pochodzić tylko z jednego źródła.

Benny Skogberg
źródło
Nie jestem pewien, dlaczego wprowadzasz rozróżnienie, że „nie chodzi o kod”. Oczywiście chodzi o kod; to jest tworzenie oprogramowania.
Robert Harvey
@RobertHarvey Chodzi mi o to, że przepływ wymagań pochodzi z jednego źródła, aktora. Użytkownicy i Interesariusze nie są zaangażowani w kodowanie, lecz w reguły biznesowe, które przychodzą do nas jako wymagania. Zatem SRP jest procesem kontrolującym te wymagania, które dla mnie nie są kodem. To Software Development (!), Ale nie kod.
Benny Skogberg