Nie śmiem się nazywać Prawdziwym Programistą, bo nigdy tak naprawdę nie brałem udziału w jakimś większym (wieloosobowym) projekcie IT w charakterze programisty. No chyba, że uznać SQL za "język programowania", ale w moim prywatnym słowniku SQL językiem programowania nie jest. W każdym razie nie takim "prawdziwym". Język programowania powinien, według mnie, mówić komputerowi JAK ma coś zrobić. A w SQL-u mówimy tylko CO chcemy uzyskać, natomiast JAK jest już poza naszym zasięgiem. Nawet biorąc pod uwagę tzw. 'query hints' dostępne w większości dialektów. Ale to już temat na kiedy indziej.
Tak czy siak, nie jestem Prawdziwym Programistą, ale liznąłem w życiu trochę tego i owego. Swego czasu udało mi się nawet ukończyć trzydniowy kurs programowania w Javie. Taki całkiem dla początkujących, dawno i nieprawda, niewiele z niego dziś pamiętam, ale papierek jest.
Mam niezłe pojęcie o C# i VB.NET. W miarę orientuję się, na czym polega MVC. Widziałem z bliska ZEND-a (brat swego czasu bawił się PHP całkiem na poważnie, włączając w to różne światowej klasy "przemysłowe" certyfikaty itd). Na studiach programowałem w C, C++ i MODULA 2. Do dziś od czasu do czasu zdarza mi się w pracy napisać parę linijek w C#. Próbuję opanować Pythona, chociaż dopóki nie wezmę udziału w prawdziwym, dużym projekcie, to trochę jak lizanie loda przez szybę: więcej z tego bałaganu, niż pożytku. Zjadłem zęby na VBA. Rozumiem też podstawy asemblera, chociaż ostatni raz, kiedy coś w nim pisałem, to były okolice 80486DX4, a więc ze dwa interglacjały temu nazad.
Czyli - ogólnie rzecz ujmując - mam jakie takie pojęcie na temat porozumiewania się z komputerem.
Dzisiejszy wpis dotyczyć będzie programowania obiektowego. Po naszemu OOP (czyli Object Oriented Programming). Przeciwieństwo FP (functional programming) czyli programowania strukturalnego (opartego na funkcjach).
Od czasu wynalezienia pierwszych komputerów jajogłowi zawsze dążyli do lepszej organizacji kodu źródłowego. Czym większy projekt, tym więcej trudnego do opanowania bałaganu, coś z tym trzeba zrobić.
OOP miało ten problem rozwiązać raz i na dobre. Obiekty należące do klas, z kilkoma uniwersalnymi własnościami:
1. Enkapsulacja. Obiekt to "czarna skrzynka" przechowująca swój stan w sposób niedostępny światu zewnętrznemu, komunikująca się z otoczeniem wyłącznie za pomocą metod (często ujednoliconych interfejsami)
2. Dziedziczenie. Obiekty należą do klas ("typów"), które mogą być uszczegóławiane w miarę zwiększania się złożoności rozwiązywanego problemu. Chcemy mieć pojazd? Super, tworzymy klasę Pojazd. Potrzebny nam samochód? Tworzymy klasę Samochód. Chcemy wyścigówkę lub ciężarówkę? A może rower? I tak dalej. Wszystko fajnie poukładane.
3. Polimorfizm. Czyli możliwość wykonania tej samej operacji na obiektach różnych typów (klas). W zależności od tego, czy metodę "wyślij" wykonujemy na obiekcie typu Wiadomość czy Rakieta, możemy zaimplementować różną logikę wewnątrz danej metody.
Te właściwości to tylko początek, wierzchołek góry lodowej OOP, ale to już wystarczy do pokazania kilku pułapek:
1. Brak możliwości przechowania wszystkich typów złożonych w jednym miejscu. W C możemy po prostu zdefiniować wszystkie typy w jednym pliku *.h i go zaimportować. W kodzie OOP musimy skakać od klasy do klasy, a definicje klas rzadko kiedy są zgromadzone w jednym miejscu.
2. Umieszczanie funkcji (metod) oraz danych (stanu) obiektu w jednym miejscu. Zasadniczo stan obiektu, określony przez wartości poszczególnych jego pól (zmiennych), to są dane. Dane te można zmieniać za pomocą metod. Trzymanie tego w jednym kociołku jest ciężko strawne - funkcje mówią JAK coś zrobić, a dane - w jakim stanie obiekt się właśnie znajduje. Dopóki mamy jeden czy pięć obiektów w hierarchii, wszystko w porządku, ale jak się ich pojawiają tysiące, zaczyna się robić niezły bajzel. Obiekty różnych klas (i różnych równolegle istniejących hierarchii) muszą się ze sobą komunikować, w związku z czym zaczyna nam się pojawiać mieszanka prywatnych, półprywatnych i publicznych pól i metod. Dołóżmy tu dziedziczenie - i już zaczynamy tracić perspektywę. Czy tu wykonuje się metoda z tej klasy, czy może z klasy rodzica? A może dziadka albo klasy zaprzyjaźnionej? I tak dalej. Czym więcej wewnętrznych stanów obiektów musimy synchronizować, tym większa szansa, że się to wszystko rozjedzie. Pojawiają sie metody dostępowe, które sztucznie "pompują" ilość kodu w każdej klasie. Pojawiają się klasy zarządzające innymi klasami (BlablaManager), pojawiają się klasy grupujące te klasy zarządzające (ManagerManager), pojawiają się osobne, "sztuczne" klasy wyłącznie do wykonywania czynności przypisanych do obiektów (Account vs AccountDoer) i nagle okazuje się, że żeby zmienić wartość jednego pola w jednym obiekcie, trzeba przejść przez kilkunastopoziomową kaskadę pozagnieżdżanych odwołań, których szczegółowa logika ginie gdzieś za horyzontem.
Zwolennicy FP mówią, że OOP co prawda sprawia, że kod jest bardziej czytelny, poprzez ukrycie "ruchomych części" kodu w czarnych skrzynkach klas, ale za to FP powoduje, że tych ruchomych części jest O WIELE mniej, co w dłuższej perspektywie jest łatwiejsze do utrzymania w jakim takim porządku.
Ciekawe w tym wsystkim jest to, że gdyby zebrać większą ilość zwolenników OOP i zamknąć ich w jednym dużym pomieszczeniu, żeby sobie ze sobą porozmawiali, ponawiązywali kontakty, to w bardzo krótkim czasie podzielą się na kilka wyraźnie wrogich grup. Jedni będą optować za tym, żeby klasa Message miała metodę Send(), inni, że metoda ta należy do klasy Sender, a jeszcze inni, że do obydwu. I tak dalej. Bo nigdy nie jest tak, że jakiś problem da się przedstawić dokładnie na jeden sposób, a elastyczność OOP sprawia, że powstało wiele różnych szkół na temat tego, jak hierarchia klas powinna odzwierciedlać świat zewnętrzny...
Oczywiście ktoś może powiedzieć (i będzie miał rację!), że za pomocą FP również można zbudować kilka różnych modeli dla tego samego zjawiska / procesu. Dowcip polega jednak na tym, że zmiany w modelu będą wymagały o wiele mniej narzutów programistycznych. Będą - prawie zawsze - dotyczyć bezpośrednio rozwiązywanego problemu, zamiast dodawać kolejne warstwy logiczne do i tak już skomplikowanego modelu opartego na OOP.
Dlaczego więc, mimo tych wszystkich wad, OOP jest dziś tak popularne? Dlaczego jest Świętym Graalem większości programistów? Dlaczego wiele języków programowania dodaje OOP (czasem trochę "na siłę") tylko po to, żeby móc się tym pochwalić w reklamie?
Przyczyn jest kilka.
Po pierwsze, OOP z założenia miało być proste do nauczenia się. I początki faktycznie były w miarę strawne, ale potem się to rozrosło w jakąś hydrę i teraz zrobiła się z tego cała osobna gałąź skomplikowanej wiedzy.
Po drugie, OOP miało sprawiać, że łatwiej będzie można przeprowadzić deduplikację kodu. Używanie tych samych klas w różnych miejscach. Niestety, ponieważ każdy rozwiązywany problem jest trochę inny, a także, ponieważ różni programiści mają różne podejście do kwestii modelowania problemów i ich rozwiązań, ten argument już też jest nieprawdziwy.
Po trzecie, wokół OOP zrobiło się dużo szumu - i to w czasach, kiedy jeszcze dwa powyższe argumenty jako tako działały. A szum ściąga tłum i robi się jeszcze więcej szumu. Wiadomo.
Po czwarte wreszcie, dzięki OOP udało się zbudować całą, ogromną gałąź przemysłu informatycznego, na której można zarabiać pieniądze - i to niemałe.
Jeśli o mnie chodzi, będę oczywiście używał tego, co akurat mam pod ręką, a więc jeżeli nie będzie innego wyjścia, będę pisał kod OOP. Ale za każdym razem, kiedy będę miał wybór, sięgnę po FP.
A teraz czekam cierpliwie, aż ten wpis przeczyta jakiś Prawdziwy Programista, a następnie wytknie mi wszystkie błędy i naiwności, a także powie mi dosadnie, co myśli o programowaniu strukturalnym.
Tylko skoczę przedtem po piwo i chipsy 😉
miałem kiedyś „Ogara” z silnikiem Jawy. Nie polecam.
Ja się na „Ogarze” uczyłem jeździć na dwóch kółkach. Ech, wspomnień czar…
Wydaje mi że Ogar miał tylko dwa koła, więc trudno byłoby ci się uczyć inaczej. ☺
OOP rulez. I tyle ;P