W Javie, C # i wielu innych silnie typowanych, sprawdzanych statycznie językach, jesteśmy przyzwyczajeni do pisania takiego kodu:
public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }
Niektóre dynamicznie sprawdzane języki nie zapewniają słów kluczowych do wyrażenia poziomu „prywatności” danego członka klasy i polegają na konwencjach kodowania. Na przykład Python poprzedza członków prywatnych znakiem podkreślenia:
_m(self): pass
Można argumentować, że udostępnianie takich słów kluczowych w dynamicznie sprawdzanych językach przydałoby się niewiele, ponieważ jest sprawdzane tylko w czasie wykonywania.
Nie mogę też znaleźć dobrego powodu, aby podać te słowa kluczowe w językach sprawdzonych statycznie. Uważam, że muszę wypełnić mój kod dość szczegółowymi słowami kluczowymi, takimi protected
jak irytujące i rozpraszające. Do tej pory nie byłem w sytuacji, w której błąd kompilatora spowodowany tymi słowami kluczowymi uratowałby mnie przed błędem. Wręcz przeciwnie, byłem w sytuacjach, w których błędnie umieszczone protected
uniemożliwiło mi korzystanie z biblioteki.
Mając to na uwadze, moje pytanie brzmi:
Czy informacje ukrywają coś więcej niż konwencję między programistami używaną do zdefiniowania, co jest częścią oficjalnego interfejsu klasy?
Czy można go wykorzystać do zabezpieczenia tajnego stanu klasy przed atakiem? Czy odbicie może zastąpić ten mechanizm? Co byłoby warte wymuszenia ukrywania informacji przez kompilator ?
źródło
Odpowiedzi:
Studiuję do certyfikacji Java i cała jej masa dotyczy tego, jak zarządzać modyfikatorami dostępu. I mają sens i powinny być właściwie używane.
Pracowałem z Pythonem i podczas mojej podróży edukacyjnej słyszałem, że w Pythonie konwencja istnieje, ponieważ ludzie, którzy z nim pracują, powinni wiedzieć, co to znaczy i jak go stosować. To powiedziawszy,
_m(self): pass
podkreślenie ostrzegłoby mnie, żebym nie zadzierał z tym polem. Ale czy wszyscy będą przestrzegać tej konwencji? Pracuję z javascript i muszę powiedzieć, że nie . Czasami muszę sprawdzić problem, a przyczyną było to, że osoba robi coś, czego nie powinna robić ...przeczytaj tę dyskusję dotyczącą wiodącego podkreślenia w pythonie
Z tego, co powiedziałem: tak.
Tak, może być użyty do zabezpieczenia tajnego stanu klasy i powinien być nie tylko używany do tego, ale także aby uniemożliwić użytkownikom bałaganowanie stanu obiektów w celu zmiany ich zachowań na coś, co według nich powinno być takie, jak obiekt powinien mieć pracowałem. Jako deweloper powinieneś o tym pomyśleć i zaprojektować swoją klasę w sposób, który ma sens, aby jej zachowanie nie zostało naruszone. A kompilator, jako dobry przyjaciel, pomoże ci zachować kod w sposób, w jaki go zaprojektowałeś, egzekwując zasady dostępu.
EDYTOWANE Tak, refleksja może, sprawdź komentarze
Ostatnie pytanie jest interesujące i chętnie przeczytam dotyczące go odpowiedzi.
źródło
Specyfikator dostępu „prywatny” nie dotyczy błędu kompilatora, który generuje za pierwszym razem. W rzeczywistości chodzi o uniemożliwienie ci dostępu do czegoś, co wciąż może ulec zmianie, gdy zmieni się implementacja klasy przechowującej członka prywatnego.
Innymi słowy, niedopuszczenie do użycia go, gdy nadal działa, zapobiega przypadkowemu użyciu go, gdy nie działa.
Jak zauważył Delnan poniżej, konwencja prefiksowa odradza przypadkowe użycie członków, którzy mogą ulec zmianie, o ile konwencja jest przestrzegana i poprawnie rozumiana. W przypadku złośliwego (lub nieświadomego) użytkownika nic nie stoi na przeszkodzie, aby uzyskać dostęp do tego członka ze wszystkimi możliwymi konsekwencjami. W językach z wbudowaną obsługą specyfikatorów dostępu nie dzieje się to w niewiedzy (błąd kompilatora) i wyróżnia się jak obolały kciuk, gdy jest złośliwy (dziwne konstrukcje, aby dostać się do członka prywatnego).
„Chroniony” specyfikator dostępu to inna historia - nie myśl o tym jako o „niezupełnie publicznym” lub „bardzo prywatnym”. „Chroniony” oznacza, że prawdopodobnie będziesz chciał skorzystać z tej funkcji, gdy wywodzisz się z klasy zawierającej chroniony element członkowski. Chronieni członkowie są częścią „interfejsu rozszerzenia”, którego użyjesz do dodania funkcjonalności nad istniejącymi klasami bez zmiany samych istniejących klas.
Podsumowując:
źródło
Jeśli piszesz kod, który będzie używany przez kogoś innego, ukrywanie informacji może zapewnić znacznie łatwiejszy do zrozumienia interfejs. „Ktoś inny” może być innym programistą w twoim zespole, programistami korzystającymi z interfejsu API, który napisałeś komercyjnie, a nawet przyszłym sobą, który „po prostu nie pamięta, jak to działa. O wiele łatwiej jest pracować z klasą, która ma tylko 4 dostępne metody niż jedna, która ma 40.
źródło
_member
czyprivate member
w mojej klasie, jest nieistotne, o ile inny programista rozumie, że powinien patrzeć tylko napublic
członków bez prefiksu.Sugerowałbym, aby w tle zacząć od przeczytania o niezmiennikach klasowych .
Niezmiennikiem jest, mówiąc krótko, założenie o stanie klasy, które ma pozostać prawdziwe przez cały czas jej trwania.
Użyjmy bardzo prostego przykładu w języku C #:
Co tu się dzieje?
addresses
jest inicjowany w konstrukcji klasy.readonly
, więc nic od wewnątrz nie może go dotknąć po zakończeniu budowy (nie zawsze jest to poprawne / konieczne, ale jest przydatne tutaj).Send
metoda może przyjąć założenie, żeaddresses
nigdy nie będzienull
. Nie musi wykonywać tej kontroli, ponieważ nie ma możliwości zmiany wartości.Jeśli pozwolono by innym klasom pisać w
addresses
polu (tj. Gdyby tak byłopublic
), to założenie nie byłoby już aktualne. Każda inna metoda w klasie, która zależy od tego pola, musiałaby zacząć wykonywać jawne kontrole zerowe lub grozić awarią programu.Więc tak, to znacznie więcej niż „konwencja”; wszystkie modyfikatory dostępu członków klasy wspólnie tworzą zestaw założeń dotyczących tego, kiedy i jak ten stan można zmienić. Założenia te są następnie włączane do zależnych i współzależnych członków i klas, aby programiści nie musieli rozumować o całym stanie programu w tym samym czasie. Umiejętność przyjmowania założeń jest kluczowym elementem zarządzania złożonością oprogramowania.
Na te inne pytania:
Tak i nie. Kod bezpieczeństwa, podobnie jak większość kodów, będzie polegał na pewnych niezmiennikach. Modyfikatory dostępu są z pewnością pomocne jako drogowskazy dla zaufanych dzwoniących, których nie powinni z tym zadzierać. Szkodliwy kod nie ma znaczenia, ale złośliwy kod również nie musi przechodzić przez kompilator.
Oczywiście że tak. Ale refleksja wymaga, aby kod wywołujący miał ten poziom uprawnień / zaufania. Jeśli używasz złośliwego kodu z pełnym zaufaniem i / lub uprawnieniami administracyjnymi, oznacza to, że przegrałeś już tę bitwę.
Kompilator już to wymusza. Podobnie jest w środowisku uruchomieniowym w .NET, Javie i innych takich środowiskach - kod operacji użyty do wywołania metody nie powiedzie się, jeśli metoda jest prywatna. Jedyne sposoby obejścia tego ograniczenia wymagają zaufanego / podwyższonego kodu, a podniesiony kod zawsze może po prostu zapisywać bezpośrednio w pamięci programu. Jest egzekwowany w takim stopniu, w jakim można go egzekwować bez wymagania niestandardowego systemu operacyjnego.
źródło
_
określa kontrakt, ale go nie egzekwuje. Może to utrudniać nieznajomość umowy, ale nie utrudnia jej zerwania , szczególnie w porównaniu z żmudnymi i często restrykcyjnymi metodami, takimi jak Reflection. Konwencja mówi: „nie powinieneś tego łamać”. Modyfikator dostępu mówi „nie możesz tego złamać”. Ja nie chce powiedzieć, że jedno podejście jest lepsze niż inne - są one zarówno grzywny w zależności od filozofii, ale są jednak bardzo różne podejścia.private
/protected
access przypomina prezerwatywę. Nie musisz go używać, ale jeśli tego nie zrobisz, lepiej bądź cholernie pewien swojego czasu i partnera (-ów). Nie zapobiegnie to złośliwemu działaniu i może nawet nie zadziałać za każdym razem, ale zdecydowanie obniży ryzyko związane z niedbalstwem i zrobi to o wiele bardziej skutecznie niż powiedzenie „uważaj”. Aha, a jeśli masz, ale nie używaj go, nie oczekuj ode mnie żadnej sympatii.Ukrywanie informacji to znacznie więcej niż tylko konwencja; próba obejścia tego może w wielu przypadkach zakłócić funkcjonalność klasy. Na przykład, dość powszechną praktyką jest przechowywanie wartości w
private
zmiennej, ujawnianie jej za pomocą właściwościprotected
lubpublic
w tym samym czasie, aw module pobierającym sprawdzanie wartości null i wykonywanie wszelkich niezbędnych inicjalizacji (tj. Opóźnionego ładowania). Lub przechowuj coś wprivate
zmiennej, ujawniaj to za pomocą właściwości, a w seterze sprawdź, czy wartość się zmieniła i odpalPropertyChanging
/PropertyChanged
zdarzenia. Ale bez zobaczenia wewnętrznej implementacji nigdy nie dowiesz się wszystkiego, co dzieje się za kulisami.źródło
Ukrywanie informacji wywodzi się z odgórnej filozofii projektowania. Python został nazwany językiem oddolnym .
Ukrywanie informacji jest dobrze egzekwowane na poziomie klas w Javie, C ++ i C #, więc tak naprawdę nie jest to konwencja na tym poziomie. Bardzo łatwo jest uczynić klasę „czarną skrzynką” z publicznymi interfejsami i ukrytymi (prywatnymi) szczegółami.
Jak już wspomniałeś, w Pythonie programiści muszą przestrzegać konwencji o niestosowaniu tego, co ma być ukryte, ponieważ wszystko jest widoczne.
Nawet w Javie, C ++ lub C # w pewnym momencie ukrywanie informacji staje się konwencją. Nie ma kontroli dostępu na najwyższych poziomach abstrakcji zaangażowanych w bardziej złożone architektury oprogramowania. Na przykład w Javie można znaleźć użycie „.internal”. nazwy pakietów. Jest to czysto konwencja nazewnictwa, ponieważ nie jest łatwo wymusić tego rodzaju ukrywanie informacji za pomocą samej dostępności pakietów.
Jednym z języków, który dąży do formalnego zdefiniowania dostępu, jest Eiffel. W tym artykule wskazano na inne słabości kryjące informacje w językach takich jak Java.
Tło: Ukrywanie informacji zaproponował David Parnas w 1971 roku . Wskazuje w tym artykule, że wykorzystanie informacji o innych modułach może „katastrofalnie zwiększyć łączność struktury systemu”. Zgodnie z tym pomysłem brak ukrywania informacji może prowadzić do ciasno powiązanych systemów, które są trudne w utrzymaniu. Kontynuuje:
źródło
Ruby i PHP mają to i wymuszają w czasie wykonywania.
Punktem ukrywania informacji jest tak naprawdę pokazywanie informacji. Poprzez „ukrywanie” wewnętrznych szczegółów cel staje się widoczny z zewnętrznej perspektywy. Istnieją języki, które to obejmują. W Javie domyślnym dostępem jest pakiet wewnętrzny, w haX chroniony. Wyraźnie deklarujesz je jako publiczne, aby je ujawnić.
Chodzi o to, aby uczynić lekcje łatwymi w użyciu, ujawniając tylko bardzo spójny interfejs. Chcesz, aby reszta była chroniona, aby żaden sprytny facet nie przychodził i nie bałaganił twojego wewnętrznego stanu, aby oszukać klasę, by robiła to, co chce.
Również, gdy modyfikatory dostępu są egzekwowane na starcie, to można ich użyć do wymuszenia określonego poziomu bezpieczeństwa, ale nie sądzę, że jest to szczególnie dobre rozwiązanie.
źródło
pydoc
usuwa_prefixedMembers
z interfejsu. Zaśmiecone interfejsy nie mają nic wspólnego z wyborem słowa kluczowego sprawdzanego przez kompilator.Modyfikatory dostępu mogą z pewnością zrobić coś, czego konwencja nie może.
Na przykład w Javie nie możesz uzyskać dostępu do prywatnego członka / pola, chyba że użyjesz refleksji.
Dlatego jeśli napiszę interfejs dla wtyczki i poprawnie odmówię prawa do modyfikowania pól prywatnych poprzez odbicie (i ustawienia menedżera bezpieczeństwa :)), mogę wysłać jakiś obiekt do funkcji zaimplementowanych przez kogokolwiek i wiem, że nie może on uzyskać dostępu do jego pól prywatnych.
Oczywiście mogą istnieć pewne błędy bezpieczeństwa, które pozwolą mu to przezwyciężyć, ale nie jest to filozoficznie ważne (ale w praktyce tak jest zdecydowanie).
Jeśli użytkownik uruchamia mój interfejs w swoim środowisku, ma kontrolę, a tym samym może obchodzić modyfikatory dostępu.
źródło
Nie tyle zapisywanie autorów aplikacji przed błędami, co pozwalanie autorom bibliotek decydować, które części ich implementacji zobowiązują się utrzymywać.
Jeśli mam bibliotekę
Mogę chcieć zastąpić
foo
wdrożenie i ewentualnie zmienić sięfooHelper
radykalnie. Jeśli grupa osób zdecydowała się użyćfooHelper
pomimo wszystkich ostrzeżeń w mojej dokumentacji, być może nie będę w stanie tego zrobić.private
pozwala autorom bibliotek rozkładać biblioteki na możliwe do zarządzania metody (iprivate
klasy pomocnicze) bez obawy, że będą zmuszeni do zachowania tych wewnętrznych szczegółów przez lata.Na marginesie, w Javie
private
nie jest egzekwowany przez kompilator, ale przez weryfikator kodu bajtowego Java .W Javie nie tylko odbicie może zastąpić ten mechanizm. Istnieją dwa rodzaje
private
Java. Tego rodzajuprivate
uniemożliwia jednej klasie zewnętrznej dostęp do elementów innej klasy zewnętrznej,private
co jest sprawdzane przez weryfikator kodu bajtowego, ale także tych,private
które są używane przez klasę wewnętrzną za pomocą metody prywatnego syntetycznego akcesorium pakietu jak wPonieważ klasa
B
(naprawdę nazwanaC$B
) używai
, kompilator tworzy syntetyczną metodę akcesora, która pozwalaB
na dostępC.i
w sposób, który mija weryfikator kodu bajtowego. Niestety, ponieważClassLoader
pozwala ci stworzyć klasę zbyte[]
, dość łatwo jest dostać się do szeregowców, którzyC
wystawili się na klasy wewnętrzne, tworząc nową klasę wC
pakiecie, co jest możliwe, jeśliC
słoik nie został zapieczętowany.Właściwe
private
egzekwowanie wymaga koordynacji między modułami ładującymi, weryfikatorem kodu bajtowego i polityką bezpieczeństwa, która może uniemożliwić odbijający dostęp do szeregowych.Tak. „Bezpieczny rozkład” jest możliwy, gdy programiści mogą współpracować, zachowując jednocześnie właściwości bezpieczeństwa swoich modułów - nie muszę ufać autorowi innego modułu kodu, aby nie naruszył właściwości bezpieczeństwa mojego modułu.
Języki Object Objectabilities, takie jak Joe-E, wykorzystują ukrywanie informacji i inne środki umożliwiające bezpieczny rozkład:
Dokument połączony z tej strony podaje przykład, w jaki sposób
private
egzekwowanie umożliwia bezpieczny rozkład.źródło
Ukrywanie informacji jest jednym z głównych problemów dobrego projektowania oprogramowania. Sprawdź którykolwiek z artykułów Dave'a Parnasa z późnych lat 70. Zasadniczo, jeśli nie możesz zagwarantować, że stan wewnętrzny modułu jest spójny, nie możesz zagwarantować niczego na temat jego zachowania. Jedynym sposobem, w jaki możesz zagwarantować jego stan wewnętrzny, jest utrzymanie go w tajemnicy i zezwolenie na jego zmianę tylko za pomocą podanych przez ciebie środków.
źródło
Uczyniając ochronę częścią języka, zyskujesz coś: rozsądną pewność.
Jeśli zmienię zmienną na prywatną, mam uzasadnioną pewność , że zostanie zmieniona tylko przez kod w tej klasie lub jawnie zadeklarowanych przyjaciół tej klasy. Zakres kodu, który mógłby rozsądnie dotykać tej wartości, jest ograniczony i wyraźnie zdefiniowany.
Czy są teraz sposoby obejścia ochrony składniowej? Absolutnie; większość języków je ma. W C ++ zawsze możesz rzucić klasę na inny typ i szturchać jego bity. W Javie i C # możesz się w to odzwierciedlić. I tak dalej.
Jest to jednak trudne . To oczywiste, że robisz coś, czego nie powinieneś robić. Nie możesz tego zrobić przypadkowo (poza dzikimi zapisami w C ++). Musisz chętnie pomyśleć: „Dotknę czegoś, czego mój kompilator nie powiedział mi”. Musisz dobrowolnie zrobić coś nierozsądnego .
Bez ochrony składniowej programista może po prostu przypadkowo coś zepsuć. Musisz nauczyć użytkownika konwencji, a on musi przestrzegać tej konwencji za każdym razem . Jeśli tego nie zrobią, świat stanie się bardzo niebezpieczny.
Bez ochrony składniowej ciężar leży po stronie niewłaściwych ludzi: wielu ludzi korzystających z klasy. Muszą postępować zgodnie z konwencją, w przeciwnym razie nastąpi nieokreślona zła. Zadrapanie, że: może wystąpić nieokreślona zła .
Nie ma nic gorszego niż interfejs API, w którym jeśli zrobisz coś złego, wszystko i tak może działać. Zapewnia to fałszywe zapewnienie użytkownikowi, że zrobił właściwą rzecz, że wszystko jest w porządku itp.
A co z nowymi użytkownikami tego języka? Muszą nie tylko nauczyć się i przestrzegać faktycznej składni (wymuszonej przez kompilator), ale teraz muszą przestrzegać tej konwencji (wymuszonej przez swoich rówieśników w najlepszym wypadku). A jeśli nie, to nic złego się nie stanie. Co się stanie, jeśli programista nie zrozumie, dlaczego konwencja istnieje? Co się stanie, gdy powie „pieprzyć to” i po prostu wyśmiewa twoich szeregowych? A co on myśli, jeśli wszystko nadal działa?
Uważa, że konwencja jest głupia. I nigdy więcej go nie podąży. I powie wszystkim swoim przyjaciołom, żeby nie zawracali sobie głowy.
źródło