O paradygmatach programowania

O paradygmatach programowania

Wpis dla wszystkich zastanawiających się czy programowanie funkcyjne jest lepsze niż OOP, a czy może jest coś innego. Oto parę kwestii do uporządkowania, zanim zaczniemy sobie stawiać takie pytania.

poniedziałek, 26 lutego 2018

Informatyka

Trafiłem dzisiaj na forum 4programmers.net na wątek "Świetna dyskusja, dlaczego OOP ssie". Punktem rozpoczynającym dyskusję był odnośnik do pewnego bloga. Kojarzyłem go już wcześniej, jako blog, którego autor hmmm... że tak powiem, dość szybko przekonał mnie, że ma tendencję do lekkiego naginania tego i owego do swego światopoglądu :). Niestety taki styl pisania blogów się po prostu sprzedaje, czego dowodem jest ku mej ogromnej rozpaczy również i mój własny wpis. I ku mej jeszcze większej rozpaczy, zmuszony jestem wręcz na dzień dobry napisać także, iż tam oto są po prostu okopane obozy ostrzeliwujące się wzajemnie, a tu oto nadchodzi mądrość i pokój :).

Poznaj swe narzędzie

W porządku, teraz będzie już bardziej poważnie. Faktycznie, nie identyfikuję się po żadnej ze stron z bardzo prostego powodu: sensacja nie wyszła jeszcze na dobre nikomu, kto stoi przed wyborem i zastanawia się, co użyć. Wpis ten prezentuje ewolucję mojego własnego podejścia nie tylko na kwestię paradygmatów oprogramowania, ale i podejścia do tego typu rozważań. Podejścia, w którym rozkładam temat na czynniki pierwsze, porządkuję go, a później patrzę pod kątem mojego problemu, moich potrzeb i analizuję, co mi się przyda.

Zacznijmy może od jednego z cytatów przeciwko OOP zaprezentowanego na wspomnianym blogu:

Rich Hickey (2010)
SE Radio, Episode 158
"I think that large objected-oriented programs struggle with increasing complexity as you build this large object graph of mutable objects. You know, trying to understand and keep in your mind what will happen when you call a method and what will the side effects be."
"Sądzę, że w dużych, obiektowo zorientowanych programach zmagamy się ze wzrastającą złożonością, ponieważ konstruujemy ten ogromny graf zmieniających swój stan obiektów. Rozumiecie, mam na myśli próbę zrozumienia i ogarnięcia, co się stanie, kiedy wywołasz metodę i jakie będzie to miało skutki uboczne".

Moje pierwsze pytanie brzmi: czy to naprawdę wina programowania obiektowego, że gość sobie ten ogromny graf zmiennych obiektów skonstruował, bo mógł? Znam z własnego doświadczenia przykład chwalonego projektu, gdzie 60% klas było niezmiennych, zdecydowana większość pozostałych to reprezentowała krótkożyjące obiekty niewychodzące poza jeden wątek, a te długożyjące, wymagające synchronizacji można było policzyć na palcach. Różnica w jakości pracy z tym projektem, a innym napisanym w stylu podobnym do tego z cytatu jest porażająca. A przecież oba były stworzone z użyciem programowania obiektowego.

Dla mnie osobiście paradygmat programowania to narzędzie. Nie ma narzędzi uniwersalnych, są natomiast problemy do rozwiązania i paradygmaty programowania traktowane jako narzędzia, służą właśnie temu celowi. Jeśli mamy dwa narzędzia z tej samej bajki, możemy je jakoś zestawić ze sobą, np. lepszy młotek i gorszy młotek. Ale młotkiem nie da się rozwiązać każdego problemu. Chleba nim nie pokroimy, podobnie jak i nie otworzymy nim butelki wina (tzn. technicznie możemy, ale uzyskany efekt może być zastanawiający ;)). Ponadto, w programowaniu oprócz paradygmatów są też pewne uniwersalne zasady, których ignorowanie prowadzi do kłopotów. Przykładem takiej zasady jest to, że dostęp do zmieniających się danych w środowisku wielowątkowym trzeba jakoś synchronizować, a do danych niezmiennych - nie. Zasada ta funkcjonuje zawsze, niezależnie od obranego paradygmatu. Przenosząc to na język życia - jak upuścisz sobie coś twardego na stopę, to cię zaboli. Odnosi się to zarówno do młotka, jak i do noża, jak i do korkociągu.

Nieporozumienia z paradygmatami

Myślę, że sporo nieporozumień bierze się z tego, że większość z nas pod pojęciami typu programowanie funkcyjne, programowanie obiektowe ma tak naprawdę mieszankę różnych, czasami niezbyt ze sobą powiązanych rzeczy. Gdy coś nie działa i narasta w nas frustracja, nie oceniamy tak naprawdę oryginalnego pojęcia, ale naszą własną mieszankę, którą mamy w głowie. Dyskutowanie z tej perspektywy jest średnio sensowne. Co innego, kiedy rozbierzemy poszczególne pojęcia na czynniki pierwsze i poobcinamy te wszystkie nadbudówki. Pomoże to nam nie tylko lepiej zrozumieć, o co chodzi, ale i podejmować lepsze decyzje.

Dobrą ilustracją tego procesu jest wspomniane już wcześniej programowanie obiektowe i moja droga do zrozumienia, o co w nim chodzi. Zacznę od przedstawienia obecnego stanu - jeśli miałbym wskazać, co jest paradygmatem przeciwnym do programowania funkcyjnego, to będzie nim... programowanie imperatywne. Oba paradygmaty dotyczą dokładnie tego samego zagadnienia: skąd komputer ma wiedzieć, co zrobić. W programowaniu imperatywnym koncentrujemy się na przedstawieniu dokładnego przepisu krok po kroku, zaś w funkcyjnym skupiamy się na definiowaniu, jak wygląda wynik wykonania pewnej funkcji. Programowanie obiektowe jest - z tej perspektywy - bytem pochodzącym z zupełnie innej bajki. Gdy mówimy o obiektach, jest nam obojętne, jak komputer ma dojść do określonego wyniku, bo OOP się tym w ogóle nie zajmuje - chodzi w nim raczej o zdefiniowanie ogólnej struktury naszego programu.

W praktyce OOP jest często łączone i rozpatrywane razem z programowaniem imperatywnym, być może z tego powodu, że to drugie królowało w czasach, gdy koncepcja obiektów się dopiero rodziła. Ale przecież co stoi na przeszkodzie, by pisać funkcyjnie oraz obiektowo jednocześnie? Dojście do tego zajęło mi kilka lat, ponieważ sam zaczynałem z takim zmiksowanym obrazem OOP w głowie. Dopiero stopniowo, poznając pewne zasady i kolejne paradygmaty zauważyłem, że pewne zasady kojarzone powszechnie z OOP są dużo bardziej uniwersalne, podobnie jak i cechy kojarzone np. z programowaniem funkcyjnym.

Kolejnym bodźcem była lektura kodu projektów napisanych w językach nieobiektowych (np. interpreter PHP napisany w C, jakieś projekty erlangowe). Ku mojemu zdziwieniu, znalazłem w nich partie kodu, w których przy pomocy dostępnych w danym języku konstrukcji zostały zamodelowane ni mniej, ni więcej, tylko obiekty. Puryści mogą się zarzekać, że jak to, że tam nie ma żadnych obiektów, ale prawda jest brutalna. Jeśli coś wygląda jak obiekt i zachowuje się jak obiekt, to najwidoczniej jest obiektem. Język wcale nie musi posiadać słowa kluczowego class itd. Mówi to nam tylko tyle, że widocznie do rozwiązania tego problemu koncepcja obiektów była najbardziej oczywista. Działa to też w innych kierunkach - w językach niefunkcyjnych także możemy znaleźć partie kodu, które będą napisane w sposób funkcyjny, gdyż akurat do tamtych problemów się takie podejście najlepiej sprawdza.

Dlatego właściwie postawione pytanie brzmi: jaki problem właściwie rozwiązujemy i czy musimy mieć do tego wsparcie ze strony języka programowania, czy wystarczy nam zamodelowanie pewnych koncepcji (np. obiektów) przy pomocy tego, co mamy? Jeśli w naszym programie w określonych miejscach pojawiają nam się obiekty, ale sumarycznie nie ma ich zbyt dużo i istota aplikacji leży gdzie indziej, to myślę, że nie potrzebujemy języka z OOP.

Ewolucja narzędzi

Przy wyborze paradygmatu musimy mieć także na uwadze, że zmieniają się zarówno narzędzia, jak i sposoby korzystania z nich. Po pewnym czasie może okazać się, że niektóre koncepcje po prostu nie przeszły próby czasu. Dobrym przykładem jest dziedziczenie z programowania obiektowego, prezentowane w różnych kursach jako jedna z fundamentalnych koncepcji w OOP (ba, sam kiedyś tego typu kursy popełniałem). W praktyce czas pokazał, że przydatność dziedziczenia jest raczej niewielka. Co gorsza, jego nadużywanie rodzi problemy z utrzymywalnością kodu i powstała nawet Zasada Otwarte/Zamknięte, która to bardzo ładnie ujmuje. Wszystkie używane przez nas narzędzia (języki programowania, paradygmaty) pełne są takich pułapek. Mądry człowiek po prostu ich nie używa, jeśli nie musi i po sprawie. Przykładowo, programując w Javie nie stosuję nigdy pól statycznych, które nie mają modyfikatora final, ponieważ to prowadzi bezpośrednio to posiadania globalnego, zmiennego stanu w naszym programie, z którym niewiele da się później zrobić. Język ma to, nawet składnia jest bardziej przyjazna tworzeniu właśnie takich pól (mniej pisania), no ale doświadczenie mówi jasno: nie. Czy winię Javę za to, że została zaprojektowana tak, a nie inaczej? Nie. Powstające obecnie języki mogą to uwzględnić, natomiast zdziwię się mocno, jeśli za 5, 10 lat nie będą one miały swoich pułapek.

Zakończenie

Celem tego wpisu, jak nietrudno zauważyć, nie było powiedzenie "stosuj to, a tamtego nie". Programuję funkcyjnie, programuję obiektowo, programuję imperatywnie, stosuję metaprogramowanie, języki domenowe, deklaratywne... - odpowiedź nasuwa się sama: to zależy. Pokazałem, skąd się to zależy bierze. Przyswojenie sobie tej lekcji nie jest łatwe; u mnie było to kilka lat ewolucji, jednak wierzę, że właśnie takie podejście sprawdza się najlepiej. Co więcej, uwalnia ono od wpływu krzykaczy przedstawiających kolejne cudowne rozwiązanie na wszystkie problemy świata, a przez to pozwala uniknąć rozczarowań, których widziałem już naprawdę dużo.

Tomasz Jędrzejewski

Programista Javy, lider techniczny. W wolnych chwilach podróżuje, realizując od kilku lat projekty długodystansowych wypraw pieszych.

Autor zdjęcia nagłówkowego: Ben Stassen, CC-BY-2.0

zobacz inne wpisy w temacie

Informatyka

poprzedni wpis Java, Stringi i hasła następny wpis Chainsaw się rozwija

Komentarze (1)

av

Kaczus

"Kolejnym bodźcem była lektura kodu projektów napisanych w językach nieobiektowych (np. interpreter PHP napisany w C, jakieś projekty erlangowe). Ku mojemu zdziwieniu, znalazłem w nich partie kodu, w których przy pomocy dostępnych w danym języku konstrukcji zostały zamodelowane ni mniej, ni więcej, tylko obiekty."

Bo wiesz, jak studiowałem w zamieżchłych czasach, to prowadzący mówił, że obiektowo programować można i w asemblerze, a nieobiektowo i w Javie. To, że któryś język ułatwia nam a drugi nie takie programowanie, nie skreśla go całkowicie z takiego czy innego podejścia do programowania. Zresztą w języku C powstało wiele obiektowych bibliotek, choćby stara biblioteka BOOPSI używana w systemie AmigaOS powyżej wersji 2. Swojego czasu zresztą przedstawiłem jak pewne koncepcje obiektowe, lub z obiektowością kojarzone można użyć w języku C: http://kaczus.ppa.pl/art/c_obiektowo,3.html

Skomentuj

Od 3 do 40 znaków.

Wymagany, anonimizowany po zatwierdzeniu komentarza.

Odpowiedz na pytanie.

Edycja Podgląd

Od 10 do 8000 znaków.

Wszystkie komentarze są moderowane i muszą być zatwierdzone przed publikacją.

Klikając "Wyślij komentarz" wyrażasz zgodę na przetwarzanie podanych w nim danych osobowych do celów moderacji i publikacji komentarza, zgodnie z polityką prywatności: polityka prywatności