Czy są problemy z używaniem Reflection?


49

Nie wiem dlaczego, ale zawsze czuję, że „oszukuję”, kiedy używam refleksji - może to z powodu przeboju wydajności, o którym wiem, że biorę.

Część mnie mówi, że jeśli jest to część języka, którego używasz i może osiągnąć to, co próbujesz zrobić, to dlaczego nie użyć tego. Druga część mnie mówi, że musi istnieć sposób, aby to zrobić bez użycia refleksji. Chyba może to zależy od sytuacji.

Jakie są potencjalne problemy, na które muszę zwrócić uwagę podczas korzystania z refleksji i jak mam się nimi martwić? Ile wysiłku warto poświęcić na znalezienie bardziej konwencjonalnego rozwiązania?


2
Myślę, że to zależy od języka i środowiska. Niektórzy go popierają, a nawet zachęcają. Czasami podczas pracy w Javie chciałem uzyskać lepszą obsługę refleksji.
FrustratedWithFormsDesigner

18
Cóż, myślę, że nie jest tak trudno rozróżnić między „odbiciem oszustwa” a jego prawidłowym użyciem: kiedy sprawdzasz członków prywatnych lub używasz go do wywoływania metod prywatnych (do obejścia interfejsów), to bardzo prawdopodobne jest oszustwo. Kiedy używasz go do interakcji z typami, o których inaczej nie masz wiedzy, prawdopodobnie jest to w porządku (wiązanie danych, proxy itp.).
Falcon,

1
Jakiego języka używasz? Brainfuck nie ma odbicia, a Python jest po prostu inny.
Job

1
Nigdy też nie rozumiałem, dlaczego dostęp do prywatnych metod jest możliwy poprzez refleksję. Według mojej intuicji powinno to być zabronione.
Giorgio

3
Trudno uwierzyć, że tak źle sformułowane pytanie uzyskało 27 głosów pozytywnych bez żadnej edycji.
Aaronaught

Odpowiedzi:


48

Nie, to nie jest oszukiwanie - to sposób na rozwiązanie problemów w niektórych językach programowania.

Obecnie często nie jest to najlepsze (najczystsze, najprostsze, najłatwiejsze w utrzymaniu) rozwiązanie. Jeśli istnieje lepszy sposób, skorzystaj z niego. Czasami jednak nie ma. A jeśli tak, to jest to o wiele bardziej skomplikowane, wymagające dużego powielania kodu itp., Co czyni go niemożliwym (trudnym do utrzymania na dłuższą metę).

Dwa przykłady z naszego obecnego projektu (Java):

  • niektóre z naszych narzędzi testowych używają refleksji do ładowania konfiguracji z plików XML. Klasa, która ma zostać zainicjowana, ma określone pola, a moduł ładujący konfigurację używa odbicia, aby dopasować element XML nazwany fieldXdo odpowiedniego pola w klasie i zainicjować ten ostatni. W niektórych przypadkach może zbudować proste okno dialogowe GUI na podstawie zidentyfikowanych właściwości w locie. Bez refleksji zajęłoby to setki wierszy kodu w kilku aplikacjach. Tak więc refleksja pomogła nam szybko stworzyć proste narzędzie bez większego zamieszania i pozwoliła nam skupić się na ważnej części (testowanie regresji naszej aplikacji internetowej, analiza dzienników serwera itp.), A nie na nieistotnym.
  • jeden moduł naszej starszej aplikacji internetowej miał eksportować / importować dane z tabel DB do arkuszy Excel i odwrotnie. Zawierał dużo zduplikowanego kodu, gdzie oczywiście duplikacje nie były dokładnie takie same, niektóre z nich zawierały błędy itp. Korzystając z refleksji, introspekcji i adnotacji, udało mi się wyeliminować większość duplikacji, zmniejszając ilość kodu z ponad 5K do poniżej 2,4K, jednocześnie czyniąc kod bardziej niezawodnym i znacznie łatwiejszym w utrzymaniu lub rozszerzeniu. Teraz ten moduł przestał być dla nas problemem - dzięki rozsądnemu wykorzystaniu refleksji.

Najważniejsze jest, jak każde potężne narzędzie, również odbicie może być użyte do strzelania sobie w stopę. Jeśli nauczysz się, kiedy i jak (nie) z niego korzystać, może przynieść ci eleganckie i czyste rozwiązania trudnych problemów. Jeśli go nadużyjesz, możesz zmienić prosty problem w złożony i brzydki bałagan.


8
Odbicie +1 jest niezwykle przydatne do importu / eksportu danych, gdy masz obiekty pośrednie do celów konwersji.
Ed James

2
Jest również niezwykle przydatny w aplikacjach opartych na danych - zamiast używać ogromnego stosu if (propName = n) setN(propValue);, możesz nazwać swoje (tj.) Tagi XML tak samo jak właściwości kodu i uruchomić nad nimi pętlę. Ta metoda ułatwia także późniejsze dodawanie właściwości.
Michael K

Odbicie jest często używane w płynnych interfejsach, takich jak FluentNHibernate.
Scott Whitlock,

4
@Michael: Dopóki nie zdecydujesz się zmienić nazwy jednej z tych właściwości w klasie i nie odkryjesz, że twój kod eksploduje. Jeśli nie musisz utrzymywać porównywalności z rzeczami generowanymi przez Twój program, to dobrze, ale podejrzewam, że to nie większość z nas.
Billy ONeal,

1
@Billy, nie chciałem ci zaprzeczyć, zgadzam się z tym, co napisałeś. Chociaż przykład, który przytaczasz, to IMHO, bardziej podklatka ogólnej zasady „unikaj zmiany publicznych interfejsów”.
Péter Török

37

To nie oszustwo. Ale zwykle jest to zły pomysł w kodzie produkcyjnym z co najmniej następujących powodów:

  • Tracisz bezpieczeństwo typu kompilacji - pomocne jest, aby kompilator sprawdził, czy metoda jest dostępna w czasie kompilacji. Jeśli używasz refleksji, w czasie wykonywania pojawi się błąd, który może wpłynąć na użytkowników końcowych, jeśli nie przetestujesz wystarczająco dobrze. Nawet jeśli złapiesz błąd, trudniej będzie go debugować.
  • Powoduje to błędy podczas refaktoryzacji - jeśli uzyskujesz dostęp do członka na podstawie jego nazwy (np. Przy użyciu łańcucha zakodowanego na stałe), to nie zostanie to zmienione przez większość narzędzi do refaktoryzacji kodu i natychmiast otrzymasz błąd, który może być dość trudne do wyśledzenia.
  • Wydajność jest wolniejsza - odbicie w czasie wykonywania będzie wolniejsze niż w przypadku statycznie skompilowanych wywołań metod / wyszukiwania zmiennych. Jeśli robisz refleksję tylko od czasu do czasu, nie będzie to miało znaczenia, ale może to stać się wąskim gardłem wydajności w przypadkach, gdy wykonujesz połączenia przez odbicie tysiące lub milionów razy na sekundę. Kiedyś dostałem 10-krotne przyspieszenie w kodzie Clojure po prostu przez wyeliminowanie całego odbicia, więc tak, to jest prawdziwy problem.

Sugeruję ograniczenie użycia refleksji do następujących przypadków:

  • Do szybkiego prototypowania lub „wyrzucanego” kodu, gdy jest to najprostsze rozwiązanie
  • Do autentycznych przypadków użycia refleksji , np. Narzędzi IDE, które pozwalają użytkownikowi badać pola / metody dowolnego obiektu w czasie wykonywania.

We wszystkich innych przypadkach sugeruję wymyślenie podejścia, które pozwala uniknąć refleksji. Zdefiniowanie interfejsu za pomocą odpowiednich metod i wdrożenie go na zbiorze klas, w których chcesz wywołać metodę (metody), zwykle wystarcza do rozwiązania najprostszych przypadków.


3
+1 - istnieje kilka przypadków użycia do refleksji; ale przez większość czasu widzę, że programiści używają go, że piszą o projektach z poważnymi wadami. Zdefiniuj interfejsy i spróbuj usunąć zależność od odbicia. Podobnie jak GOTO, ma swoje zastosowania, ale większość z nich nie jest dobra. (Niektóre z nich są oczywiście dobre)
Billy ONeal

3
Pamiętaj, że powyższe punkty mogą dotyczyć Twojego ulubionego języka lub nie. Na przykład odbicie nie ma ograniczenia prędkości w Smalltalk, ani nie tracisz bezpieczeństwa w czasie kompilacji, ponieważ obliczenia są całkowicie spóźnione.
Frank Shearar,

Odbicie w D nie ma wspomnianej wady. Argumentowałbym więc, że obwiniacie implementację w niektórych językach bardziej niż samą refelekcję. Niewiele wiem o smalltalk, ale według @Frank Shearar nie ma też tych wad.
deadalnix

2
@deadalinx: Do jakiego minusu się odwołujesz? W tej odpowiedzi jest kilka. Problem wydajności jest jedyną rzeczą zależną od języka.
Billy ONeal,

1
Powinienem dodać, że drugi problem - błędy spowodowane podczas refaktoryzacji - jest prawdopodobnie najpoważniejszy. W szczególności poleganie na czymś o konkretnej nazwie ulega natychmiastowemu zerwaniu, gdy zmienia się jej nazwa. Błędy spowodowane w ten sposób mogą mieć mniej niż charakter informacyjny, chyba że jesteś bardzo ostrożny.
Frank Shearar,

11

Refleksja to kolejna forma metaprogramowania, równie ważna, jak parametry oparte na typie, które można obecnie zobaczyć w większości języków. Odbicie jest potężne i ogólne, a programy odblaskowe charakteryzują się wysokim stopniem łatwości konserwacji (oczywiście przy prawidłowym stosowaniu), a nawet więcej niż programy czysto obiektowe lub proceduralne. Tak, płacisz cenę za wydajność, ale chętnie wybrałbym wolniejszy program, który jest łatwiejszy w utrzymaniu w wielu, a nawet większości przypadków.


2
+1, to pomaga mi zrozumieć, czym jest odbicie . Jestem programistą C ++, więc nigdy tak naprawdę nie zastanawiałem się nad tym. To pomaga. (Chociaż przyznaję, że z mojej perspektywy Reflection wydaje się próbą pogodzenia deficytu języka. My, faceci C ++, używamy do tego szablonów. Myślę, że można powiedzieć to samo. :)
greyfade

4
Ponowna wydajność - w niektórych przypadkach można użyć interfejsu API odbicia w celu zapisania w pełni kodu wydajności. Na przykład w .NET możesz napisać IL do bieżącej aplikacji - stąd twój kod „refleksyjny” może być tak samo szybki jak każdy inny kod (z wyjątkiem tego, że możesz usunąć wiele if / else / cokolwiek, jak już to zrobiłeś wymyślił dokładne zasady)
Marc Gravell

@greyfade: W pewnym sensie szablony odzwierciedlają tylko czas kompilacji. Możliwość robienia tego w czasie wykonywania ma tę zaletę, że umożliwia tworzenie potężnych możliwości w aplikacji bez konieczności ponownej kompilacji. Wymieniasz wydajność na elastyczność, akceptowalny kompromis przez dłuższy czas, niż możesz się spodziewać (biorąc pod uwagę twoje doświadczenie w C ++).
Donal Fellows

Nie rozumiem twojej odpowiedzi. Wydaje się, że mówisz, że programy korzystające z odbicia są łatwiejsze w utrzymaniu niż te, które tego nie robią, tak jakby odbicie było lepszym modelem programowania, który powinien zastępować programy obiektowe i proceduralne, jeśli wydajność nie jest potrzebna.
DavidS

8

Na pewno wszystko zależy od tego, co próbujesz osiągnąć.

Na przykład napisałem aplikację do sprawdzania multimediów, która wykorzystuje wstrzykiwanie zależności w celu ustalenia, jaki rodzaj mediów (pliki MP3 lub JPEG) należy sprawdzić. Powłoka musiała wyświetlić siatkę zawierającą istotne informacje dla każdego typu, ale nie wiedziała, co zamierza wyświetlić. Jest to zdefiniowane w zestawie, który czyta ten typ nośnika.

Dlatego musiałem użyć refleksji, aby uzyskać liczbę kolumn do wyświetlenia oraz ich typy i nazwy, aby poprawnie ustawić siatkę. Oznaczało to również, że mogłem zaktualizować wstrzykiwaną bibliotekę (lub utworzyć nową) bez zmiany innego kodu lub pliku konfiguracyjnego.

Jedynym innym sposobem byłoby posiadanie pliku konfiguracyjnego, który musiałby zostać zaktualizowany po zmianie rodzaju sprawdzanego nośnika. Wprowadziłoby to kolejny punkt niepowodzenia dla aplikacji.


1
„Na pewno wszystko zależy od tego, co próbujesz osiągnąć.” +1
DaveShaw

7

Refleksja to niesamowite narzędzie, jeśli jesteś autorem biblioteki , a zatem nie ma wpływu na przychodzące dane. Połączenie refleksji i metaprogramowania pozwala twojej bibliotece płynnie współpracować z dowolnymi wywołującymi, bez konieczności przeskakiwania przez generowanie kodu itp.

Staram się jednak zniechęcać do refleksji w kodzie aplikacji ; w warstwie aplikacji powinieneś używać różnych metafor - interfejsów, abstrakcji, enkapsulacji itp.


Takie biblioteki są często trudne w użyciu i najlepiej ich unikać (np. Spring).
Sridhar Sarnobat

@Sridhar, więc nigdy nie korzystałeś z interfejsu API serializacji? Czy jakaś ORM / mikro-ORM?
Marc Gravell

Użyłem ich bez własnego wyboru. Demoralizowanie napotykało problemy, których mogłem uniknąć, gdyby nie powiedziano mi, co mam robić.
Sridhar Sarnobat

7

Refleksja jest fantastyczna do tworzenia narzędzi dla programistów.

Ponieważ pozwala środowisku kompilacji na sprawdzenie kodu i potencjalne wygenerowanie odpowiednich narzędzi do manipulowania / inicjowania kontroli kodu.

Jako ogólna technika programowania może być użyteczna, ale jest bardziej krucha, niż większość ludzi sobie wyobraża.

Jednym z naprawdę przydatnych zastosowań do refleksji (IMO) jest to, że sprawia, że ​​pisanie ogólnej biblioteki strumieniowej jest bardzo proste (o ile opis klasy nigdy się nie zmienia (wtedy staje się bardzo kruchym rozwiązaniem)).


Rzeczywiście wiosna jest „bardziej krucha, niż większość ludzi sobie wyobraża”.
Sridhar Sarnobat

5

Użycie refleksji jest często szkodliwe w językach OO, jeśli nie jest używane z dużą świadomością.

Nie pamiętam, ile złych pytań widziałem na stronach StackExchange, gdzie

  1. Językiem używanym jest OO
  2. Autor chce dowiedzieć się, jaki typ obiektu został przekazany, a następnie wywołać jedną z jego metod
  3. Autor nie zgadza się z tym, że refleksja jest niewłaściwą techniką i oskarża każdego, kto uważa to za „nieumiejętność pomocy”

Tutaj typowymi przykładami.

Najważniejsze jest to, że OO

  1. Grupujesz funkcje, które potrafią manipulować strukturą / koncepcją przy pomocy samej rzeczy.
  2. W dowolnym punkcie kodu powinieneś wiedzieć wystarczająco dużo o typie obiektu, aby wiedzieć, jakie odpowiednie funkcje ma on, i poprosić go o wywołanie konkretnej wersji tych funkcji.
  3. Zapewniasz prawdziwość poprzedniego punktu, określając poprawny typ danych wejściowych dla każdej funkcji i sprawiając, że każda funkcja nie wie więcej (i nie robi więcej) niż jest to istotne.

Jeśli w którymkolwiek momencie kodu punkt 2 nie jest poprawny dla przekazanego obiektu, to co najmniej jeden z nich jest prawdziwy

  1. Podano niewłaściwe wejście
  2. Twój kod ma słabą strukturę
  3. Nie masz pojęcia, co do cholery robisz.

Słabo wykwalifikowani programiści po prostu tego nie rozumieją i wierzą, że można im przekazać dowolną część kodu i zrobić to, co chcą z (zakodowanego na stałe) zestawu możliwości. Ci idioci często używają refleksji .

W przypadku języków OO odbicie powinno być potrzebne tylko w przypadku działania meta (moduły ładujące klasy, wstrzykiwanie zależności itp.). W tych kontekstach potrzebna jest refleksja, ponieważ zapewniasz ogólną usługę wspomagającą manipulację / konfigurację kodu, o której nic nie wiesz z dobrego i uzasadnionego powodu. W prawie każdej innej sytuacji, jeśli sięgasz po refleksję, robisz coś złego i musisz zadać sobie pytanie, dlaczego ten fragment kodu nie wie wystarczająco dużo o przekazanym mu obiekcie.


3

Alternatywą, w przypadkach, gdy domena klas odbijanych jest dobrze zdefiniowana, jest użycie odbicia wraz z innymi metadanymi do wygenerowania kodu , zamiast używania odbicia w czasie wykonywania. Robię to za pomocą FreeMarker / FMPP; istnieje wiele innych narzędzi do wyboru. Zaletą tego jest to, że powstaje „prawdziwy” kod, który można łatwo debugować itp.

W zależności od sytuacji może to pomóc w stworzeniu o wiele szybszego kodu - lub po prostu rozlaniu kodu. Pozwala uniknąć wad refleksji:

  • utrata bezpieczeństwa typu kompilacji
  • błędy spowodowane refaktoryzacją
  • wolniejsza wydajność

wspomniano wcześniej.

Jeśli refleksja wydaje się być oszustwem, może być tak, ponieważ opierasz się na wielu domysłach, których nie jesteś pewien, a twoje jelita ostrzegają, że jest to ryzykowne. Pamiętaj, aby zapewnić sposób na ulepszenie metadanych nieodłącznie odzwierciedlonych za pomocą własnych metadanych, w którym możesz opisać wszystkie dziwactwa i specjalne przypadki klas, które możesz napotkać.


2

To nie jest oszustwo, ale jak każde narzędzie, powinno być używane do tego, co ma rozwiązać. Refleksja z definicji pozwala ci sprawdzać i modyfikować kod poprzez kod; jeśli to właśnie musisz zrobić, to odbicie jest narzędziem do pracy. Refleksja dotyczy przede wszystkim meta-kodu: kodu, który celuje w kod (w przeciwieństwie do zwykłego kodu, który celuje w dane).

Przykładem dobrego zastosowania refleksji są ogólne klasy interfejsu usługi sieciowej: Typowym rozwiązaniem jest oddzielenie implementacji protokołu od funkcjonalności ładunku. Mamy więc jedną klasę (nazwijmy ją T), która implementuje ładunek, a drugą, która implementuje protokół ( P). Tjest dość proste: dla każdego połączenia, które chcesz wykonać, po prostu napisz jedną metodę, która zrobi wszystko, co powinna. Pmusi jednak mapować wywołania usługi internetowej na wywołania metod. Uczynienie tego mapowania ogólnym jest pożądane, ponieważ pozwala uniknąć nadmiarowości i sprawia, że ​​można go Pwielokrotnie używać. Refleksja zapewnia środki do sprawdzania klasy Tw czasie wykonywania i wywoływania jej metod na podstawie ciągów przekazywanych Pprzez protokół usługi sieci Web, bez wiedzy klasy na temat kompilacjiT. Stosując zasadę „kod o kodzie” można argumentować, że klasa Pma kod w klasie Tjako część swoich danych.

Jednak.

Refleksja daje również narzędzia do obejścia ograniczeń systemu typów językowych - teoretycznie można przekazać wszystkie parametry jako typ objecti wywołać ich metody poprzez refleksje. Voilà, język, który ma wymuszać silną dyscyplinę pisania statycznego, zachowuje się teraz jak język dynamicznie pisany z późnym wiązaniem, tyle że składnia jest znacznie bardziej rozbudowana. Każde wystąpienie takiego wzoru, które do tej pory widziałem, było brudnym włamaniem i niezmiennie możliwe byłoby rozwiązanie w systemie typów językowych, które byłoby bezpieczniejsze, bardziej eleganckie i wydajniejsze pod każdym względem .

Istnieje kilka wyjątków, takich jak kontrolki GUI, które mogą być powiązane z różnymi niepowiązanymi typami źródeł danych; nakazanie, aby Twoje dane implementowały określony interfejs tylko po to, abyś mógł go powiązać, nie jest realistyczne, a programista nie implementuje adaptera dla każdego typu źródła danych. W takim przypadku bardziej przydatne jest zastosowanie odbicia w celu wykrycia rodzaju źródła danych i dostosowanie powiązania danych.


2

Jednym z problemów, jakie mieliśmy z Reflection, jest dodanie Obfuscation do miksu. Wszystkie klasy otrzymują nowe nazwy i nagle ładowanie klasy lub funkcji po nazwie przestaje działać.


1

To całkowicie zależy. Przykładem czegoś, co byłoby trudne do zrobienia bez refleksji, jest replikacja ObjectListView . Generuje również kod IL w locie.


1

Refleksja jest podstawową metodą tworzenia systemów opartych na konwencjach. Nie zdziwiłbym się, gdybym był często używany w większości platform MVC. Jest to główny składnik ORM. Możliwe, że już używasz komponentów zbudowanych z niego każdego dnia.

Alternatywą dla takiego zastosowania jest konfiguracja, która ma swój własny zestaw wad.


0

Refleksja może osiągnąć rzeczy, których po prostu nie da się zrobić praktycznie inaczej.

Na przykład zastanów się, jak zoptymalizować ten kod:

int PoorHash(char Operator, int seed, IEnumerable<int> values) {
    foreach (var v in values) {
        seed += 1;
        switch (char) {
            case '+': seed += v; break;
            case '^': seed ^= v; break;
            case '-': seed -= v; break;
            ...
        }
        seed *= 3;
    }
    return seed;
}

Pośrodku wewnętrznej pętli znajduje się kosztowny klaps testowy, ale wyodrębnienie go wymaga przepisania pętli raz dla każdego operatora. Refleksja pozwala nam uzyskać wydajność na równi z wyodrębnieniem tego testu, bez powtarzania pętli kilkanaście razy (a tym samym poświęcając łatwość konserwacji). Wystarczy wygenerować i skompilować potrzebną pętlę w locie.

Tak naprawdę dokonałem tej optymalizacji , chociaż sytuacja była nieco bardziej skomplikowana, a wyniki były niesamowite. Poprawa wydajności o rząd wielkości i mniejsza liczba wierszy kodu.

(Uwaga: początkowo próbowałem przekazać odpowiednik Func zamiast char, i było to nieco lepsze, ale nie prawie 10-krotnie uzyskane odbicie).


1
Lepszą optymalizacją jest zamiast tego podanie funktora. Prawdopodobnie zostanie wprowadzony w czasie wykonywania przez kompilator JIT i jest łatwiejszy w utrzymaniu niż odbicie.
Billy ONeal,

Byłoby to łatwiejsze w utrzymaniu i faktycznie próbuję najpierw, ale nie było wystarczająco szybko. JIT zdecydowanie tego nie inklinował, prawdopodobnie dlatego, że w moim przypadku była druga pętla wewnętrzna nad operacjami do wykonania.
Craig Gidney

Zwracam również uwagę, że to, co tam masz, to kod samodopasowujący się, który nie jest tak naprawdę odzwierciedleniem. Jeden dostęp go poprzez API odbicie w większości języków, które obsługują odbicie, ale można to zrobić tak po prostu w językach nie wspierających refleksję (np byłoby możliwe w C ++)
Billy Oneal

Chciałem uniknąć standardowego przykładu „równości strukturalnej”. Refleksja nie jest konieczna do samodzielnego modyfikowania kodu, ale z pewnością pomaga.
Craig Gidney

Nie całkiem. Możesz to zrobić w językach bezrefleksyjnych.
Billy ONeal,

-2

Nie ma mowy, żeby oszukiwał ... Zamiast tego umożliwia serwerom aplikacji prowadzenie klas stworzonych przez użytkownika o wybranej przez siebie nazwie, zapewniając tym samym elastyczność użytkownika, a nie oszukiwanie go.

A jeśli chcesz zobaczyć kod dowolnego pliku .class (w Javie), istnieje kilka darmowych dekompilatorów!


Dekompilacja nie jest funkcją refleksji.
Billy ONeal,

tak, to nie jest ... Ale chciałem powiedzieć, że jeśli masz ochotę oszukiwać podczas korzystania z odbicia (które dostarczają szczegółów dowolnej klasy w java), to jesteś w błędzie. odbicie bcoz jest bardzo przydatną koncepcją i jeśli otrzymujesz szczegóły klasa jest tym, co cię dotyczy, niż można to łatwo zrobić za pomocą dowolnego dekompilatora ...
ANSHUL JAIN

2
Refleksja to przydatna koncepcja w niektórych przypadkach, tak. Ale 99,9% czasu widzę, że jest używany Widzę, że był używany do włamania się do błędu projektowego.
Billy ONeal,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.