(Anty)wzorce projektowe

7

Zbiorczy temat do narzekania na wzorce projektowe, które są antywzorcami, może macie jakieś ulubione? Ode mnie na początek:

  1. Anemic domain model - nieużywanie OOP, tam, gdzie OOP faktycznie pasuje
  2. Specification pattern - takie enterprise if-else, niepotrzebna abstrakcja
  3. Wrappowanie ORM (np. DbContext) i nazywanie tego UoW, w ogólności bezsensowne wrappery, zmniejszające funkcjonalność (raz miałem do czynienia z wrapperem na NHibernate, który wrappował sposób robienia query, tak że dało się zrobić może 1/10 tego co pozwala ISession)
4

Co do Ad 1 to bym dyskutował. Ad 2 - czy masz na myśli https://en.wikipedia.org/wiki/Specification_pattern czy zaszła pomyłka i chodzi o strategię?

Moje ulubione top 3:

  • Kod spaghetti (nie trzeba wyjaśniać) i ogólny syf w kodzie (np. Configuration klasy w Springu takie że nie znajdziesz początku i końca; yaml'e których nikt nie rozumie tydzień po napisaniu; dziwne języki konfiguracji ze słabą dokumentacją ale za to hipsterskie)
  • Napiszmy sami X choć na GitHubie jest już kilka sprawdzonych projektów co robią X
  • Użycie wielowątkowości tam gdzie nie jest koniecznie niezbędna, np. szał sprzed kilku lat z podejściem reactive everything a teraz płacz bo h... wie co się dzieje w kodzie. Użycie Future a potem głowienie się czemu się wyjątek nie zalogował.

Takie wzorce jak Singleton, traktuje teraz jako pryszcz, trzeba wycisnąć (tj. zrefaktorować do czegoś lepszego) i zapomnieć. To co wymieniłem nie da się łatwo naprawić bo często jest skopane od 1 dnia projektu lub z mentalnością zespołu jest coś nie tak.

5

zwykle jest przesada w jedną albo drugą stronę:

  • albo mieszanie wszystkiego razem, totalne spaghetti, kod bardzo uzależniony od iluś specyficznych bibliotek (bez żadnych warstw dodatkowych)
  • albo odwrotnie - czyli wrappowanie wszystkiego i robienie wrapperów na wrappery, i nawet używając potężnych bibliotek z dobrą dokumentacją robi się do tego wrappery, które przestają być tylko wrapperami, a zaczyna być inner platform effect, bo okazuje się, że coraz więcej różnych opcji trzeba zapewnić. I potem wchodząc do takiego projektu zamiast programować w bibliotece X (jak obiecali), pisze się w jakimś wewnętrznym frameworku bez dobrej dokumentacji te same rzeczy, które można by zaimplementować w bibliotece X.
8

OOP - największy buzzword programowania

3

z OOP to:

  • nadużywanie klas (np. w JS niektórzy używają klas jako przestrzeni nazw do programowania proceduralnego. Chyba się w Javie za dużo naprogramowali. Tylko to jest głupie choćby dlatego, że moduły w JS już są taką przestrzenią nazw).
  • niepotrzebne singletony. Zastanawiam się, jaki jest usecase singletona i jedyny sensowny przykład, jaki mi przychodzi do głowy, to leniwe ładowanie zasobów. Czyli mamy obiekt, który musi się połączyć z siecią i pociągnąć jakieś zasoby (które potem sobie zcachuje). Wtedy faktycznie można by zrobić funkcję, która albo ładuje zasoby albo zwraca zcache'owane. Pozostałe przypadki użycia singletona to po prostu zamaskowana zmienna globalna.
  • nadużywanie dziedziczenia (dziedziczenie rzadko jest dobrym pomysłem, a bardzo często ludzie je stosują).
  • niepotrzebne gettery/settery. Czyli ktoś bardzo chciałby traktować obiekty jako worki na dane, ale jednak naczytał się o jakiejś enkapsulacji i próbuje na siłę się wbić w paradygmat, w którym nawet nie pisze w danej chwili.
16
  1. Betonowanie testów, testowanie pojedynczych klas jako rzekomej jednostki, nadmierne mockowanie i przywiązanie do piramidy testów, testowanie crudów jednostkowo.
  2. Nadmierne dziedziczenie
  3. Sterowanie logiki przez wyjątki
  4. Stringly typing - wszystko jest Stringiem
8

Każdy wzorzec, który wymaga wyklepania, wygenerowania pewnej ilości kodu (powiedzmy więcej niż kilka linijek), wg pewnego schematu - tak, że istnieje albo można by do tego zrobić generator w IDE to antywzorzec.
Oczywiście, w niektórych językach się inaczej nie da, ale ludzie nie powinni za często pisać w takich językach.

Moje znienawidzone to mappery i "na grzyba" DTO. Jeśli mamy mappingi 1 do 1 między DTO, a encjami to jest to na grzyba.

1
0xmarcin napisał(a):

@Saalin: ja mam bardzo dobre doświadczenia ze specyfikacją, ale z C# gdzie wzorzec jest wbudowany w Entity Framework a język pozwala na tworzenie drzew AST z wyrażeń. I tam to całkiem dobrze działa bo można mieć invoiceSpec.And(inv => inv.Customer = customerId).And(inv => inv.OrderId = orderId) Wszystko dynamiczne z jednej strony a kompiluje się do SQLa z drugiej...

  1. Miałem na myśli bardziej abstrakcję nad Expression, tak jak tu https://enterprisecraftsmanship.com/posts/specification-pattern-c-implementation/
  2. Nawet jeśli używamy Expression bezpośrednio to też śmierdzi zbliżaniem się do Generic repository i metod w stylu Find(Specification/Expression).
5

Wzorce rozwiązują pewien znany problem w danym kontekście. Bez podania kontekstu dyskusja nie ma sensu. Temat zamykam (;P)

7

My tu rozmawiamy o antywzorcach. W ich przypadku słyszałem definicję: antywzorzec - rozwiązanie które na pierwszy rzut oka wydaje się poprawne ale w dłuższej perspektywie czasu przynosi więcej szkody niż pożytku.

3

Z takich rzeczy, które mnie zawsze denerwują:

  • traktowanie każdego kodu, jakby pisano biznesowego CRUDa. Inaczej się pisze ogólnodostępne API, inaczej się pisze streaming, jeszcze inaczej się pisze właśnie biznesowe CRUDy. Ba, nawet w obrębie CRUDa można znaleźć różnice (np. przy starcie aplikacji NPE z powodu braków w konfiguracji powinno kończyć się koncertowym System.exit(-1), natomiast np. przy obsłudze requesta brak pola powinien zwracać 500kę).
  • w Javie - zapominanie o package-private, zamiast tego walenie public wszędzie
  • złe projektowanie klas i potem wyciąganie do różnorakich utilsów rzeczy, które powinny być metodą klasy. Wiąże się to z jakąś taką niechęcią do przepisywania części kodu
8

Nazywanie tego samego w różny sposób (np. customer_id, cust_id, custId, customerId) i nazywanie różnych rzeczy w ten sam sposób (np. object_id)

3

Używanie głupich (w sensie naprawdę głupich nazw), tutaj nowy hipsterski projekt: https://github.com/Shopify/comma
Polecenie którym się odpala to , - tak dosłownie ,. Ktoś miał za dużo THC jak pisał...

1

Template method: dla mnie ten wzorzec to trochę nie wiem jak wydzielić interfejsy więc zrobię burdel w jednym.
Dziedziczenie: zamiast skupiać się na tym co dany kawałek robi (czyli to na czym polegają abstrakcje w językach OOP jak Java) skupiamy się na tym czymś coś jest, co jest błędne, bo rzeczy mają to do siebie, że mają wiele cech i zastosowań. Przykładowo kotek może być obrazkiem, przytulanką albo jedzeniem dla dinozaura. Po samych nazwach klas nie widać co coś robi.
Używanie klas na siłę: chcę zrobić zwykłą funkcję, ale nie mogę. Muszę nazwać ją FunkcjaXXX, gdzie XXX to handler, service, controller, monitor albo inny bzdet.

6

Według mnie, nie ma czegoś takiego jak "anti-pattern". Wzorce projektowe to tylko narzędzie, które do pewnych zadań się nadaje, a do innych pasuje jak pięść do nosa. Przykład y to:
Fizzbuzz enterprise edition. Przerost formy nad treścią. Chyba największy problem z tym zadaniem z rozmów kwalifikacyjnych polega na tym, że kandydaci zaczynają się zastanawiać, w jaki sposób zaimplementować coś tak banalnego "ładnie"
Inny przykład to powszechnie uznawany za antywzorzec Singleton Oczywiście można go zaimplementować lepiej, lub gorzej, ale praktycznie każda aplikacja ma coś, co musi być współdzielone pomiędzy wszystkimi modułami: repozytorium, event bus itp. Ale ponieważ ktoś kiedyś napisał, ze singleton jest zły, to teraz stosuje się jakieś wygibasy, żeby fakt istnienia globalnego obiektu ukryć za 20 adnotacjami, czy jakąś często upierdliwą w zastosowaniu funkcją frameworku.

4

@slsy: akurat template method to jest taka bardziej zakorzeniona strategia i ma sporo zastosowań.

Np. zamiast:

public interface Strategy {
    void process(final Request request, final StateA stateA, final StateB stateB, final StateC stateC);
}

public class Example {
    protected final StateA stateA;
    protected final StateB stateB;
    protected final StateC stateC;
    
    private final Strategy strategy;

    public void processRequest(final Request request) {
        startMethod();
        strategy.process(request, stateA, stateB, stateC);
        endMethod();
    }

    public void startMethod() {
        // do something
    }

    abstract void process(final Request request); // can use stateA, stateB, stateC here

    public void endMethod() {
        // do something
    }
}

lepiej jednak zrobić coś takiego:

public class Example {
    protected final StateA stateA;
    protected final StateB stateB;
    protected final StateC stateC;
    
    public void processRequest(final Request request) {
        startMethod();
        process(request);
        endMethod();
    }
    
    public void startMethod() {
        // do something
    }
    
    abstract void process(final Request request); // can use stateA, stateB, stateC here
    
    public void endMethod() {
        // do something
    }
}

zwłaszcza gdy zdecydowana część strategii nie wykorzysta wszystkich trzech stanów.

0

zwłaszcza gdy zdecydowana część strategii nie wykorzysta wszystkich trzech stanów.

@wartek01 i pięknie, o to chodzi w programowaniu, żeby było od razu wiadomo co od czego zależy + kod powinien mieć dostęp tylko do potrzebnego stanu. W przeciwnym przypadku mamy problem np. z testowanie (po cholerę ustawiam tą zmienną, skoro nikt jej nie używa) albo co gorzej: z zrozumieniem kodu (po co ten debil przesyła tą zmienną, skoro nikt jej nie używa). Jak przekazywanie trzech stanów boli to może trzeba podejść do problemu od innej strony.

1

Tego się spodziewałem po tym wątku - to co jest uznawane przez jednych za wzorzec, według drugich będzie antywzorcem, dlatego temat to "(Anty)wzorce" a nie "Antywzorce". To była też moja inspiracja do utworzenia tego wątku po tym jak zauważyłem obronę Active Record w dziale PHP. Poza tym praktycznie dla każdego wzorca znajdzie się artykuł w stylu "{wzorzec} considered harmful" np. https://ocramius.github.io/blog/fluent-interfaces-are-evil/

Charles_Ray napisał(a):

Wzorce rozwiązują pewien znany problem w danym kontekście. Bez podania kontekstu dyskusja nie ma sensu. Temat zamykam (;P)

0xmarcin napisał(a):

My tu rozmawiamy o antywzorcach. W ich przypadku słyszałem definicję: antywzorzec - rozwiązanie które na pierwszy rzut oka wydaje się poprawne ale w dłuższej perspektywie czasu przynosi więcej szkody niż pożytku.

Ja bym powiedział, że to jest coś po środku. Z jednej stron dla pewnych antywzorców znajdą się use case'y, ale częściej będzie to błąd designu.

4

ostatnio trafiłem na taką wskazówkę: premature abstraction is a root of all evil

0

Z mojego doświadczenia:

  • brak znajomości przez zespół i programistów więcej niż 1 paradygmatu / języka
  • brak czytania oficjalnych dokumentacji frameworków / bibliotek / języka gdy ktoś musi się przestawić do danej rzeczy
  • brak stosowania OOP i strukturyzowania kodu
  • brak stosowania dziedziczenia
  • wciskanie na siłę wszędzie tam gdzie nie trzeba FP
  • brak dbania o warstwę danych - logikę można bardzo łatwo zaorać i przepisać w krótkim czasie, ale jak ktoś ma dziwnie ułożone dane, nie otypowane, nie mające sensu do problemu - to potem trzeba robić wokół tego fikołki, a najczęściej wystarczy dobrze zaprojektować sobie dane do przetwarzania
  • brak spójności, słabe nazwy zmiennych
  • brak refactoringu tam gdzie to się to przydaje i upraszczanie rzeczy
1
MuadibAtrides napisał(a):

Z mojego doświadczenia:

  • brak znajomości przez zespół i programistów więcej niż 1 paradygmatu / języka
  • brak czytania oficjalnych dokumentacji frameworków / bibliotek / języka gdy ktoś musi się przestawić do danej rzeczy
  • brak stosowania OOP i strukturyzowania kodu
  • brak stosowania dziedziczenia
  • wciskanie na siłę wszędzie tam gdzie nie trzeba FP
  • brak dbania o warstwę danych - logikę można bardzo łatwo zaorać i przepisać w krótkim czasie, ale jak ktoś ma dziwnie ułożone dane, nie otypowane, nie mające sensu do problemu - to potem trzeba robić wokół tego fikołki, a najczęściej wystarczy dobrze zaprojektować sobie dane do przetwarzania
  • brak spójności, słabe nazwy zmiennych
  • brak refactoringu tam gdzie to się to przydaje i upraszczanie rzeczy

Od kiedy brak czytania dokumentacji to wzorzec? Idziemy nieubłaganie w kierunku "Programistycznych WTFów" (to kolejny post w tym tonie) , a tematem były wzorce, czyli coś co zostało przyjęte przez społeczność z aprobatą, było umyślnie używane, a okazuje się nie być tak dobre po czasie (jak Active Record choćby).

4

Od kiedy brak czytania dokumentacji to wzorzec? Idziemy nieubłaganie w kierunku "Programistycznych WTFów" (to kolejny post w tym tonie) , a tematem były wzorce, czyli coś co zostało przyjęte przez społeczność z aprobatą, było umyślnie używane, a okazuje się nie być tak dobre po czasie (jak Active Record choćby).

To mi przypomina XML i cały bajzel z nim związany XSLT i pokraczne schemy, oraz pisanie całych aplikacji jako transformacji XSLT.

Fast forward 2021: YAML - fajny format, ale pokraczny i ze słabą strukturą https://www.arp242.net/yaml-config.html

EDIT:
Warto też zerknąć na https://github.com/toml-lang/toml - nowy nabytek, ktoś przepisał na nowo stary format Ini i teraz robi się z tego hype...

5

Wiedza tajemna jak cos zrobic (szczegolnie jak chodzi o stawianie srodowiska) przekazywana z pokolenia na pokolenie.

2
slsy napisał(a):

Template method: dla mnie ten wzorzec to trochę nie wiem jak wydzielić interfejsy więc zrobię burdel w jednym.

Template method to wzorzec do tworzenia szeregu podobnych algorytmów różniących się jakąś częścią. Co to ma do wydzielania interfejsów?

Używanie klas na siłę: chcę zrobić zwykłą funkcję, ale nie mogę. Muszę nazwać ją FunkcjaXXX, gdzie XXX to handler, service, controller, monitor albo inny bzdet.

To chyba specyficzne jedynie dla pewnych języków, i to chyba raczej nieobiektowych.

piotrpo napisał(a):

Według mnie, nie ma czegoś takiego jak "anti-pattern". Wzorce projektowe to tylko narzędzie, które do pewnych zadań się nadaje, a do innych pasuje jak pięść do nosa.

Ok, podaj jedno zastosowanie God Object. :)

Inny przykład to powszechnie uznawany za antywzorzec Singleton Oczywiście można go zaimplementować lepiej, lub gorzej, ale praktycznie każda aplikacja ma coś, co musi być współdzielone pomiędzy wszystkimi modułami: repozytorium, event bus itp. Ale ponieważ ktoś kiedyś napisał, ze singleton jest zły, to teraz stosuje się jakieś wygibasy, żeby fakt istnienia globalnego obiektu ukryć za 20 adnotacjami, czy jakąś często upierdliwą w zastosowaniu funkcją frameworku.

Singleton nie jest antywzorcem, ale jego samodzielne implementowanie jest. Od tego są kontenery DI, aby się tym zająć.

Saalin napisał(a):

Zbiorczy temat do narzekania na wzorce projektowe, które są antywzorcami, może macie jakieś ulubione? Ode mnie na początek:

  1. Anemic domain model - nieużywanie OOP, tam, gdzie OOP faktycznie pasuje
  2. Specification pattern - takie enterprise if-else, niepotrzebna abstrakcja
  3. Wrappowanie ORM (np. DbContext) i nazywanie tego UoW, w ogólności bezsensowne wrappery, zmniejszające funkcjonalność (raz miałem do czynienia z wrapperem na NHibernate, który wrappował sposób robienia query, tak że dało się zrobić może 1/10 tego co pozwala ISession)

Active Record, generyczne repozytorium, ogólnie fasady na wszystko (czyli wrappery).

Głównym problemem jest moim zdaniem to, że:

  1. Jedna grupa ludzi rozumie, że wzorce istnieją i są potrzebne, ale dała sobie wmówić, że to jakiś trudny temat, jakieś skomplikowane konstrukcje, których trzeba się nie wiadomo ile uczyć, aby móc stosować. W efekcie ludzie zakuwają teorię, aby tylko przejść rozmowę kwalifikacyjną, a potem i tak nie używają wzorców, bo tak rzekomo łatwiej. Żeby było śmieszniej, to oni wymyślają swoje własne wzorce (czasami zgodne ze znanymi, czasami bardziej koślawe), bo przecież też napotykają na powtarzalne problemy. Co daje ten ich opór? Nie wiem.
  2. Druga grupa ludzi po przeczytaniu teorii przegięła w drugą stronę, i próbuje wsadzać każdy wzorzec wszędzie. A takie wsadzanie byle gdzie to proszenie się o chorobę weneryczną. Taki kod jest równie zły, jak proceduralny od tych z pierwszej grupy.
  3. Trzecia grupa ludzi - taka, która nie rozumie po co w ogóle wzorce są, więc je po prostu hejtuje. Tak jak część samochodziarzy hejtuje automatyczne skrzynie biegów.

A koncepcja jest bardzo prosta - to po prostu nazwy na rozwiązania pewnych powtarzalnych problemów. Osiągamy w ten sposób dwa cele - nie wynajdujemy koła na nowo, a także usprawniamy komunikację między programistami.

A tak w ogóle, to nie jestem pewien, czy wzorce muszą być związane z OOP. Powtarzalne problemy zdarzają się niezależnie od paradygmatu.

3
somekind napisał(a):

Singleton nie jest antywzorcem, ale jego samodzielne implementowanie jest. Od tego są kontenery DI, aby się tym zająć.

Grubo, akurat kontenery DI to dla mnie jeden z najgorszych antywzorców. Ale, że już dawno w javie nie piszę to przestałem narzekać.

A tak w ogóle, to nie jestem pewien, czy wzorce muszą być związane z OOP. Powtarzalne problemy zdarzają się niezależnie od paradygmatu.

Wzorce są raczej związane z językiem.
Np. w wielu nowych językach singleton jest wbudowany w składnię. Podobnie jak np. lepsze wsparcie do DI (lepsze niż tylko konstruktor).

W językach funkcyjnych jest zwykle dostępny wyższy poziom abstrakcji, więc to co się klepie wg potwarzalnego schematu w javie/ C++/C# jest po prostu dostępne jako funkcja biblioteczna - przestaje być wzorcem.

0
somekind napisał(a):

Ok, podaj jedno zastosowanie God Object. :)

Proszę bardzo - implementacja FizzBuzz w Javie. Nie ma najmniejszego powodu, żeby rozkładać to na klasy odpowiedzialne za wejście, walidację wejścia, wyjście, logikę, logowanie...

Singleton nie jest antywzorcem, ale jego samodzielne implementowanie jest. Od tego są kontenery DI, aby się tym zająć.

Jeżeli piszesz coś małego, to wpychanie na siłę frameworku z DI, czy nawet jakiejś biblioteki jest bez sensu. Zresztą we frameworku ktoś to jednak samodzielnie zaimplementował.

Głównym problemem jest moim zdaniem to, że:

  1. Jedna grupa ludzi rozumie, że wzorce istnieją i są potrzebne, ale dała sobie wmówić, że to jakiś trudny temat, jakieś skomplikowane konstrukcje, których trzeba się nie wiadomo ile uczyć, aby móc stosować. W efekcie ludzie zakuwają teorię, aby tylko przejść rozmowę kwalifikacyjną, a potem i tak nie używają wzorców, bo tak rzekomo łatwiej. Żeby było śmieszniej, to oni wymyślają swoje własne wzorce (czasami zgodne ze znanymi, czasami bardziej koślawe), bo przecież też napotykają na powtarzalne problemy. Co daje ten ich opór? Nie wiem.

Potwierdzam. Ostatnio prowadziłem trochę rekrutacji i wzorce projektowe ludzie mają obczajone (jedni lepiej, drudzy gorzej). Ale już pytanie "jak byś dodał do dowolnej kolekcji możliwość nasłuchiwania zmian przez inne obiekty w aplikacji" budzi trwogę. Czyli ogólnie ujmując wiedza o wzorcach jest, umiejętności korzystania z tej wiedzy brak.

  1. Trzecia grupa ludzi - taka, która nie rozumie po co w ogóle wzorce są, więc je po prostu hejtuje. Tak jak część samochodziarzy hejtuje automatyczne skrzynie biegów.

Nie znam dobrej książki o wzorcach projektowych. W sensie jest trochę pozycji opisujących na UMLach jak coś zaimplementować, ale przykłady KIEDY ich używać, często są tak mierne, że mam wątpliwości co do wiedzy autora. W każdym razie moja skromna wiedza na ten temat zgromadziła się w taki sposób, że przeczytałem coś tam, programowałem bez ich użycia, ale zacząłem dostrzegać w bibliotekach, frameworkach użycie konstrukcji o których czytałem. Teraz mam wrażenie, że już coś tam wiem, chociaż wciąż nie mogę sobie wyobrazić pisania takiej fabryki abstrakcji. A analogia do automatycznej skrzyni biegów dobra - to samo tłumaczenie wolę mieć kontrolę.

5

Proszę bardzo - implementacja FizzBuzz w Javie. Nie ma najmniejszego powodu, żeby rozkładać to na klasy odpowiedzialne za wejście, walidację wejścia, wyjście, logikę, logowanie..

xD W Javie do tego nie trzeba żadnego obiektu. Tak naprawde to statyczne metody a klasa to tylko wrapper na to, bo w Javie nie możesz mieć funkcji... To nie jest GodObject...

Jeżeli piszesz coś małego, to wpychanie na siłę frameworku z DI, czy nawet jakiejś biblioteki jest bez sensu. Zresztą we frameworku ktoś to jednak samodzielnie zaimplementował.

Zasadniczo nie trzeba korzystać z frameworków żeby miec kontener DI. Można napisać samodzielnie, piszę to absolutnie nieironicznie. Po prostu chodzi o to że raz robisz new X() a później to X przekazujesz do konstruktorów.

0

@scibi_92: Faktycznie dałem przykład z trąby, ale OK. Niech to będzie bardzo prosty serwis w Springu udostępniający pojedynczą metodę pure, typu dodawanie dwóch parametrów.

Zasadniczo nie trzeba korzystać z frameworków żeby miec kontener DI. Można napisać samodzielnie, piszą to absolutnie nieironicznie. Po prostu chodzi o to że raz robisz new X() a później to X przekazujesz do konstruktorów.

Kwestia semantyczna - jeżeli chcesz mieć kontener DI, to on musi odpowiadać za tworzenie twojej klasy i tworzenie/dostarczanie wszystkich zależności. Jasne, że można sobie coś takiego napisać. Goście od Springa np. napisali.

3

Serio podajesz serwis z jedna metoda i brakiem zaleznosci jako przyklad rzekomo GodObject?

4
WeiXiao napisał(a):

ostatnio trafiłem na taką wskazówkę: premature abstraction is a root of all evil

Hłe hłe. Całkiem niedawno miałem dosyć burzliwą dyskusje z zespołem aby nie chować wszystkiego domyślnie za interfejsem. O ile większość osób się zgadzała z argumentacją, to pewne kontargumenty które stosował jeden senior (zarówno stanowiskiem jak i wiekiem) były tylko pustymi sloganami. Aż myślałem żeby tutaj wątek o tym założyć.

1 użytkowników online, w tym zalogowanych: 0, gości: 1