Dlaczego Clean Code sugeruje unikanie chronionych zmiennych?

168

Czysty kod sugeruje unikanie chronionych zmiennych w sekcji „Odległość pionowa” rozdziału „Formatowanie”:

Pojęcia ściśle ze sobą powiązane powinny być trzymane pionowo blisko siebie. Oczywiście ta reguła nie działa w przypadku pojęć należących do oddzielnych plików. Ale ściśle powiązane pojęcia nie powinny być dzielone na różne pliki, chyba że masz bardzo dobry powód. Rzeczywiście jest to jeden z powodów, dla których należy unikać chronionych zmiennych .

Jakie jest uzasadnienie?

Matsemann
źródło
7
pokaż przykład, w którym twoim zdaniem potrzebujesz go
komnata
63
Nie wziąłbym wiele z tej książki za ewangelię.
Rig
40
@Rig, graniczysz z bluźnierstwem w tych częściach z takim komentarzem.
Ryathal
40
Nie przeszkadza mi to. Przeczytałem książkę i mogę mieć własne zdanie na jej temat.
Przypon
10
Przeczytałem tę książkę i chociaż zgadzam się z większością tego, co tam jest, nie powiedziałbym, że uważam to za ewangelię. Myślę, że każda sytuacja jest inna ... a trzymanie się dokładnych wytycznych przedstawionych w książce jest dość trudne dla wielu projektów.
Simon Whitehead

Odpowiedzi:

277

Chronionych zmiennych należy unikać, ponieważ:

  1. Prowadzą one zwykle do problemów z YAGNI . O ile nie masz klasy potomnej, która faktycznie robi rzeczy z chronionym członkiem, ustaw ją jako prywatną.
  2. Zwykle prowadzą do problemów z LSP . Chronione zmienne generalnie wiążą się z pewną wewnętrzną niezmiennością (w przeciwnym razie byłyby publiczne). Dziedzicznicy muszą następnie zachować te właściwości, które ludzie mogą popsuć lub umyślnie naruszyć.
  3. Mają tendencję do naruszania OCP . Jeśli klasa podstawowa przyjmuje zbyt wiele założeń dotyczących chronionego elementu lub dziedzicznik jest zbyt elastyczny w zachowaniu klasy, może to prowadzić do modyfikacji zachowania klasy podstawowej przez to rozszerzenie.
  4. Mają tendencję do dziedziczenia zamiast rozszerzenia. Prowadzi to do ściślejszego sprzężenia, większej liczby naruszeń SRP , trudniejszych testów i mnóstwa innych rzeczy, które mieszczą się w dyskusji o „sprzyjaniu kompozycji nad dziedziczeniem”.

Ale jak widzisz, wszystkie z nich są „skłonne”. Czasami chroniony członek jest najbardziej eleganckim rozwiązaniem. Funkcje chronione mają zwykle mniej tych problemów. Ale istnieje wiele rzeczy, które powodują, że traktuje się ich ostrożnie. Dzięki wszystkim, co wymaga tego rodzaju opieki, ludzie popełniają błędy, aw świecie programowania oznacza to błędy i problemy projektowe.

Telastyn
źródło
Dzięki z wielu dobrych powodów. Jednak książka wspomina o tym szczególnie w kontekście formatowania i odległości w pionie, to jest ta część, o której zastanawiam się.
Matsemann
2
Wygląda na to, że wujek Bob odnosi się do odległości w pionie, gdy ktoś odnosi się do chronionego członka między klasami, a nie do tej samej klasy.
Robert Harvey
5
@Matsemann - jasne. Jeśli dobrze pamiętam książkę, ta sekcja koncentruje się na czytelności i możliwościach odkrycia kodu. Jeśli zmienne i funkcje działają na koncepcji, powinny być zbliżone do kodu. Chronione elementy będą używane przez typy pochodne, które niekoniecznie muszą być blisko chronionego elementu, ponieważ znajdują się w zupełnie innym pliku.
Telastyn
2
@ steve314 Nie wyobrażam sobie scenariusza, w którym klasa podstawowa i wszyscy jej spadkobiercy będą żyli tylko w jednym pliku. Bez przykładu skłaniam się ku nadużywaniu dziedzictwa lub złej abstrakcji.
Telastyn
3
YAGNI = Nie potrzebujesz go LSP = Zasada podstawienia Liskowa OCP = Zasada otwarcia / zamknięcia SRP = Zasada pojedynczej odpowiedzialności
Bryan Glazer
54

To naprawdę ten sam powód, dla którego unikasz globalizacji, tylko na mniejszą skalę. Jest tak, ponieważ trudno jest znaleźć wszędzie, gdzie odczytywana jest zmienna lub, co gorsza, jest zapisywana i trudno jest zapewnić spójność użycia. Jeśli użycie jest ograniczone, na przykład pisanie w jednym oczywistym miejscu w klasie pochodnej i czytanie w jednym oczywistym miejscu w klasie bazowej, wówczas zmienne chronione mogą uczynić kod bardziej przejrzystym i zwięzłym. Jeśli masz ochotę czytać i pisać zmienną nie chcąc w wielu plikach, lepiej ją zamknąć.

Karl Bielefeldt
źródło
26

Nie przeczytałem tej książki, ale mogę sprawdzić, co miał na myśli wujek Bob.

Jeśli coś włożysz protected, oznacza to, że klasa może to odziedziczyć. Ale zmienne składowe powinny należeć do klasy, w której są zawarte; jest to część podstawowej enkapsulacji. Zastosowanie protectedzmiennej składowej przerywa enkapsulację, ponieważ teraz klasa pochodna ma dostęp do szczegółów implementacji klasy podstawowej. To ten sam problem, który pojawia się, gdy tworzysz zmienną publicw zwykłej klasie.

Aby rozwiązać problem, możesz umieścić zmienną w chronionej właściwości, na przykład:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Pozwala to namena bezpieczne ustawienie z klasy pochodnej za pomocą argumentu konstruktora, bez ujawniania szczegółów implementacji klasy podstawowej.

Robert Harvey
źródło
Utworzyłeś własność publiczną z chronionym ustawiaczem. Chronione pole należy rozwiązać za pomocą chronionej własności z prywatnym setera.
Andy
2
+1 teraz. Setter może też być chroniony, ale identyfikatory punktów, które baza może wymusić, jeśli nowa wartość jest poprawna, czy nie.
Andy
Aby zaoferować przeciwny punkt widzenia - wszystkie moje zmienne chronię w klasie bazowej. Jeśli mają być dostępne tylko poprzez interfejs publiczny, to nie powinna to być klasa pochodna, powinna ona zawierać obiekt klasy podstawowej. Ale unikam też hierarchii głębszych niż 2 klasy
Martin Beckett
2
Istnieje ogromna różnica pomiędzy protectedi publicczłonkami. Jeśli BaseTypeujawnia członka publicznego, oznacza to, że wszystkie typy pochodne muszą mieć ten sam element działający w ten sam sposób, ponieważ DerivedTypeinstancja może zostać przekazana do kodu, który odbiera BaseTypeodwołanie i spodziewa się użyć tego elementu. Natomiast jeśli BaseTypeujawnia protectedczłonka, DerivedTypemoże oczekiwać dostępu do tego członka base, ale basenie może być niczym innym jak BaseType.
supercat,
18

Argument wujka Boba dotyczy przede wszystkim odległości: jeśli masz pojęcie ważne dla klasy, umieść je razem z klasą w tym pliku. Nie rozdzielone między dwa pliki na dysku.

Chronione zmienne składowe są rozproszone w dwóch miejscach i wygląda to trochę jak magia. Odwołujesz się do tej zmiennej, ale nie jest tu zdefiniowana ... gdzie ona jest zdefiniowana? I tak zaczyna się polowanie. Lepiej protectedcałkowicie unikać jego argumentacji.

Teraz nie wierzę, że regułą należy przestrzegać litery cały czas (na przykład: nie będziesz używać protected). Spójrz na ducha tego, do czego zmierza ... połącz powiązane ze sobą rzeczy w jeden plik - użyj do tego technik programowania i funkcji. Radziłbym, abyś nie analizował nadmiernie i nie wplątał się w szczegóły na ten temat.

J. Polfer
źródło
9

Kilka bardzo dobrych odpowiedzi już tutaj. Ale jedną rzeczą do dodania:

W większości współczesnych języków OO dobrym nawykiem (w Javie AFAIK jest to konieczne), aby umieścić każdą klasę we własnym pliku (proszę nie zwracać uwagi na C ++, gdzie często masz dwa pliki).

Jest więc praktycznie niemożliwe, aby funkcje w klasie pochodnej uzyskiwały dostęp do chronionej zmiennej klasy bazowej „pionowo” w kodzie źródłowym do definicji zmiennej. Ale idealnie powinny być tam przechowywane, ponieważ chronione zmienne są przeznaczone do użytku wewnętrznego, co sprawia, że ​​funkcje uzyskujące do nich dostęp często są „ściśle powiązaną koncepcją”.

Doktor Brown
źródło
1
Nie w Swift. Co ciekawe, Swift nie ma „chronionego”, ale ma „fileprivate”, który zastępuje zarówno „chroniony”, jak i „znajomy” w C ++.
gnasher729
@ gnasher729 - oczywiście Swift ma dużo Smalltalk w swoim dziedzictwie. Smalltalk nie ma nic poza zmiennymi prywatnymi i metodami publicznymi. A prywatny w Smalltalk oznacza prywatny: dwa obiekty nawet tej samej klasy nie mogą uzyskać dostępu do swoich zmiennych.
Jules
4

Podstawową ideą jest to, że „pole” (zmienna na poziomie instancji) zadeklarowane jako chronione jest prawdopodobnie bardziej widoczne niż powinno być i mniej „chronione”, niż byś chciał. W C / C ++ / Java / C # nie ma modyfikatora dostępu, który jest odpowiednikiem „dostępnego tylko dla klas podrzędnych w tym samym zestawie”, co daje możliwość definiowania własnych dzieci, które mogą uzyskać dostęp do pola w zestawie, ale nie zezwalanie dzieciom utworzonym w innych zespołach na taki sam dostęp; C # ma modyfikatory wewnętrzne i chronione, ale ich połączenie sprawia, że ​​dostęp jest „wewnętrzny lub chroniony”, a nie „wewnętrzny i chroniony”. Tak więc chronione pole jest dostępne dla każdego dziecka, niezależnie od tego, czy napisałeś to dziecko, czy ktoś inny. Chronione są zatem otwarte drzwi do hakera.

Ponadto pola z ich definicji praktycznie nie mają walidacji związanej z ich zmianą. w C # możesz zrobić tylko do odczytu, co sprawia, że ​​typy wartości są faktycznie stałe, a typy referencyjne nie mogą być ponownie zainicjowane (ale nadal bardzo zmienne), ale o to chodzi. Jako takie, nawet chronione, twoje dzieci (którym nie możesz ufać) mają dostęp do tego pola i mogą ustawić je na coś nieprawidłowego, co powoduje, że stan obiektu jest niespójny (czego należy unikać).

Akceptowanym sposobem pracy z polami jest uczynienie ich prywatnymi i dostęp do nich za pomocą właściwości i / lub metody pobierającej i ustawiającej. Jeśli wszyscy konsumenci klasy potrzebują tej wartości, upublicznij getter (przynajmniej). Jeśli potrzebują tego tylko dzieci, zapewnij getterowi ochronę.

Innym podejściem, które odpowiada na pytanie, jest postawienie sobie pytania; dlaczego kod w metodzie podrzędnej wymaga możliwości bezpośredniej modyfikacji moich danych stanu? Co to mówi o tym kodzie? To argument „pionowej odległości” na jego twarzy. Jeśli w dziecku jest kod, który musi bezpośrednio zmieniać stan rodzica, to może ten kod powinien należeć do rodzica?

KeithS
źródło
4

To ciekawa dyskusja.

Szczerze mówiąc, dobre narzędzia mogą złagodzić wiele z tych „pionowych” problemów. W rzeczywistości moim zdaniem pojęcie „pliku” w rzeczywistości utrudnia tworzenie oprogramowania - nad czym pracuje projekt Light Table (http://www.chris-granger.com/2012/04/12/light- table --- a-new-ide-concept /).

Usunąć pliki ze zdjęcia, a chroniony zakres staje się znacznie bardziej atrakcyjny. Chroniona koncepcja staje się najbardziej wyraźna w językach, takich jak SmallTalk - gdzie każde dziedzictwo po prostu rozszerza oryginalną koncepcję klasy. Powinien robić wszystko i mieć wszystko, co klasa macierzysta.

Moim zdaniem hierarchie klas powinny być tak płytkie, jak to możliwe, przy czym większość rozszerzeń pochodzi z kompozycji. W takich modelach nie widzę żadnych powodów, dla których zmienne prywatne nie powinny być chronione. Jeśli klasa rozszerzona ma reprezentować rozszerzenie obejmujące wszystkie zachowania klasy nadrzędnej (co moim zdaniem powinno, a zatem uważam, że metody prywatne są z natury złe), dlaczego klasa rozszerzona nie powinna reprezentować również przechowywania takich danych?

Innymi słowy - w każdym przypadku, w którym uważam, że zmienna prywatna byłaby lepsza niż chroniona, dziedziczenie nie jest rozwiązaniem problemu.

spronkey
źródło
0

Jak tam napisano, jest to tylko jeden z powodów, dla których logicznie połączony kod powinien być umieszczony w fizycznie połączonych jednostkach (plikach, pakietach i innych). W mniejszej skali jest to ten sam powód, dla którego nie umieścisz klasy, która tworzy zapytania DB w pakiecie z klasami wyświetlającymi interfejs użytkownika.

Głównym powodem, dla którego chronione zmienne nie są zalecane, jest to, że mają tendencję do przerywania enkapsulacji. Zmienne i metody powinny mieć możliwie ograniczoną widoczność; w celu uzyskania dalszych informacji patrz Efektywna Java Joshua Blocha , punkt 13 - „Minimalizuj dostępność klas i członków”.

Jednak nie powinieneś brać całej tej reklamy, tylko jako linii cechu; jeśli chronione zmienne są bardzo złe, nie zostałyby umieszczone w języku. Rozsądnym miejscem dla chronionych pól jest imho w klasie bazowej z frameworka testowego (klasy testów integracyjnych go rozszerzają).

m3th0dman
źródło
1
Nie zakładaj, że ponieważ pozwala na to język, możesz to zrobić. Nie jestem pewien, czy istnieje naprawdę dobry powód, aby zezwolić na chronione pola; w rzeczywistości zabraniamy ich u mojego pracodawcy.
Andy
2
@Andy Nie przeczytałeś tego, co powiedziałem; Powiedziałem, że pola chronione nie są zalecane i powody tego. Oczywiście istnieją scenariusze, w których potrzebne są chronione zmienne; możesz chcieć stałej wartości końcowej tylko w podklasach.
m3th0dman
Być może zechcesz przeredagować część swojego postu, ponieważ wydaje się, że używasz zmiennych i pól zamiennie i wyraźnie zaznaczasz, że za chronione stałe uważa się wyjątek.
Andy,
3
@Andy Jest całkiem oczywiste, że skoro miałem na myśli zmienne chronione, nie mówiłem o zmiennych lokalnych lub zmiennych parametrów, ale o polach. Wspomniałem również o scenariuszu, w którym przydatne są niestałe zmienne chronione; imho to źle, że firma wymusza sposób, w jaki programiści piszą kod.
m3th0dman
1
Gdyby to było oczywiste, nie byłbym zdezorientowany i zagłosowałbym za tobą. Zasada została podjęta przez naszych starszych programistów, podobnie jak nasza decyzja o uruchomieniu StyleCop, FxCop i wymaganiu testów jednostkowych i recenzji kodu.
Andy,
0

Uważam, że używanie chronionych zmiennych do testów JUnit może być przydatne. Jeśli ustawisz je jako prywatne, nie będziesz mieć do nich dostępu podczas testów bez refleksji. Może to być przydatne, jeśli testujesz złożony proces poprzez wiele zmian stanu.

Możesz ustawić je jako prywatne za pomocą chronionych metod pobierających, ale jeśli zmienne będą używane tylko zewnętrznie podczas testu JUnit, nie jestem pewien, czy to lepsze rozwiązanie.

Nacięcie
źródło
-1

Tak, zgadzam się, że jest to nieco dziwne, biorąc pod uwagę, że (jak pamiętam) książka omawia również wszystkie nieprywatne zmienne jako coś, czego należy unikać.

Myślę, że problem z chronionym modyfikatorem polega na tym, że członek jest nie tylko narażony na podklasy, ale także jest widoczny dla całego pakietu. Myślę, że to ten podwójny aspekt, w którym wujek Bob bierze tutaj wyjątek. Oczywiście nie zawsze można ominąć chronione metody, a tym samym kwalifikację chronionej zmiennej.

Myślę, że bardziej interesującym podejściem jest upublicznienie wszystkiego, co zmusi cię do posiadania małych klas, co z kolei sprawi, że cała metryka odległości w pionie będzie nieco dyskusyjna.

CurtainDog
źródło