Programiści nadal potrzebują skromności

Wiecie zapewne, kim jest Edsger Dijkstra. To holenderski pionier informatyki, naukowiec. Jak sam o sobie pisał, był prawdopodobnie pierwszym informatykiem w Holandii. Miał bardzo duży wkład w rozwój dziedziny. Pracował nad zagadnieniami matematycznymi (np. twierdzenie Dijkstry o trójkątach), algorytmicznymi (bardzo znany algorytm Dijkstry do znajdowania najkrótszych ścieżek w grafie), kompilatorami, systemami operacyjnymi czy obliczeniami rozproszonymi.

Wpadłem na wykład, który Dijkstra wygłosił z okazji otrzymania Nagrody Turinga w 1972 roku za wkład w rozwój informatyki. Został spisany i opublikowany na łamach magazynu „Communications of the ACM” pod tytułem „Skromny programista” („The humble programmer”). Jest dostępny online.

To tekst bardzo długi, ale również bardzo ciekawy. Długi, bo Dijkstra opisuje zarówno początki swojej kariery, jak i ewolucję informatyki, sprzętu i oprogramowania aż do momentu, w którym powstał artykuł. Chciałbym go Wam streścić, ale też podzielić się swoimi odczuciami po przeczytaniu tego tekstu. Wszystko po to, by zastanowić się, czy programista współczesny ma fundamentalnie te same problemy, co jego kolega sprzed prawie 50 lat. Co się zmieniło, co pozostało takie samo?

Kiedyś było inaczej

Programowanie na pierwsze komputery było trudne. Maszyny te były wielkich rozmiarów, a przy tym powolne, miały mało zasobów. W zasadzie każdy komputer był inny, przez co pisany software był nieprzenośny. Trudno było wykorzystać nawet pierwsze języki programowania, takie jak Fortran, Lisp czy ALGOL, które pojawiły się pod koniec lat 50. Dijkstra opisuje, że pisanie programów na te komputery wymagało stosowania wielu trików, które pozwalałyby w pewnym stopniu obejść (czy raczej skompensować) ograniczenia sprzętowe. Chodziło w tym o to, by wykorzystać jak najlepiej możliwości komputera, który był w tym czasie na wagę złota.

Dlatego pierwsi informatycy często sądzili, że rozwiązaniem ich kłopotów będą potężniejsze, lepiej ustandaryzowane maszyny. Dzięki nowym zasobom hacki stosowane na pierwszych komputerach miały pójść w niepamięć. Po prostu mocy miało być tyle, że nie trzeba będzie tak drastycznie optymalizować programów.

W kolejnych latach maszyny stawały się z jednej strony dużo szybsze, a z drugiej dużo tańsze. I okazało się, że - pomimo mocy obliczeniowej o rzędu wielkości większej - problemów zaczęło szybko przybywać.

Dopóki komputery były kosztownymi i nieporadnymi maszynami było dla nich niewiele zastosowań uzasadnionych ekonomicznie. Jednak dzięki rozwojowi elektroniki możliwości stały się na tyle duże, że warto było je zastosować na wielu innych polach. Tego scenariusza nie przewidziało wiele osób w początkach lat 60., więc przepis na problemy był gotowy.

Kryzys oprogramowania

W 1968 roku na konferencji NATO Software Engineering Conference (brzmi poważnie, prawda?) w Garmisch padł termin „Software Crisis”. Objawem kryzysu było to, że coraz trudniej było pisać użyteczne i wydajne oprogramowanie w rozsądnym czasie. Większość projektów była bardzo mocno opóźniona i miała olbrzymie problemy z utrzymaniem jakości.

Główną przyczyną było właśnie to, że komputery mocno poszły do przodu, a techniki programistyczne i programiści za nimi nie nadążyli. Przy użyciu doskonale znanych im technik, które dobrze sprawdzały się na pierwszych komputerach, mieli duże problemy z wykorzystaniem możliwości nowych komputerów.

Te nowe komputery to tzw. trzecia generacja, która była rozwijana w latach 1964 -1971. Tu układy pojedynczych tranzystorów zostały zastąpione układami scalonymi. To umożliwiło duży przyrost mocy, zmniejszenie rozmiarów i znacznie podniosło niezawodność. Wraz z nimi zaczęły się pojawiać systemy operacyjne zarządzające tym, co aktualnie jest wykonywane na procesorze (w poprzedniej generacji jedyną rolą systemu zarządzanie ograniczało się do wczytania kolejnego programu z taśmy). W pamięci mogło być wiele programów równocześnie i, gdy któryś czekał np. na I/O, system mógł w tym czasie przydzielić procesor innemu. Od tej pory zasoby trzeba było dzielić między programami, co spowodowało sporo zamieszania.

Dijkstra co prawda zwraca uwagę na to, że komputery trzeciej generacji nie były najlepiej dopracowanymi maszynami, jednak jako głównych winowajców kryzysu wskazuje programistów. Twierdzi, że stracili kontrolę nad softwarem.

Skromny programista

Dijkstra wskazał, że czas zmienić to, jak ludzie kodują. Trzeba zrobić krok wstecz i zamiast szukać ciekawych sztuczek koderskich przypomnieć sobie, po co w ogóle piszemy kod. Napisał:

We must not forget that it is not our business to make programs; it is our business to design classes of computations that will display a desired behavior.

To miało się przełożyć na swego rodzaju rewolucję w kodowaniu, której głównym punktem było pisanie i projektowanie oprogramowania, które programista jest w stanie objąć intelektualnie. Tylko wtedy jest szansa na to, że nie straci nad nim kontroli. Zasadność takiej rewolucji podpierał sześcioma argumentami.

Pierwszym argumentem jest to, że gdy programista zacznie kierować się w stronę rozwiązań, które bez problemu obejmuje intelektualnie, to będzie wybierać spośród alternatyw, z którymi dużo łatwiej sobie poradzić.

Po drugie, jeżeli kierujemy się w stronę rozwiązań prostych, to drastycznie ograniczamy przestrzeń rozwiązań, czyli liczba alternatywnych rozwiązań spada. Łatwiej więc wybrać jedną z nich.

Po trzecie, konieczna jest lepsza metoda sprawdzania poprawności programów. Dijkstra tłumaczy, że testowanie oprogramowania po jego napisaniu jest skuteczne tylko w udowadnianiu obecności bugów, a nie ich braku. Trzeba więc wykazać poprawność rozwiązania przed jego implementacją. Dodatkowo, ponieważ miał bardzo mocne podstawy matematyczne, dość prawdopodobne jest, że chodzi tu o udowodnienie poprawności napisanego algorytmu. W czasach, gdy większością naszej pracy jest zrobienie kolejne CRUD-owej apki, chyba najbliższe by tu było TDD.

Kolejny argument dotyczy samej wielkości programów, które ze względu na wymagania muszą być coraz dłuższe. Jeżeli programista chce utrzymać kontrolę nad softem, to musi wprowadzić abstrakcje. To abstrakcje stworzą kolejne warstwy, dzięki którym system jako całość będzie nadal możliwy do zrozumienia i będzie mógł się dalej rozrastać.

Piąty argument dotyczy narzędzi, a w szczególności języków programowania, jakich używamy. Dijkstra zauważył, że język czy notacja jakiej używamy do wyrażania naszych myśli wpływa na to, co w ogóle możemy wyrazić. Np. w językach, które zachęcają do robienia „sprytnych sztuczek” o wiele łatwiej znajdziemy zagmatwane kawałki kodu. Programista świadomy powinien unikać tego rodzaju kodu jak ognia. Dlatego też Dijkstra preferuje proste języki programowania, w których składnia jest bardzo jednoznaczna. Wyczekuje też pojawienia się kolejnej generacji języków programowania, które będą tę prostotę wyrażać. Bez odpowiednich narzędzi ciężko będzie rozwijać bardziej skomplikowane oprogramowanie.

Ostatnia myśl dotyczy wprowadzenia hierarchii do kodu, czyli podzielenia go na dobrze zdefiniowane kawałki. Sama hierarchia miała wynikać też z konieczności rozłożenia rozwiązywanego problemu na czynniki. Tu - jak sam mówi - nie ma dowodów, że to jedyna droga, ale ma też silną wiarę w to, że trzeba iść w tym kierunku. Tu pada bardzo ciekawa myśl:

The only problems we can really solve in a satisfactory manner are those that finally admit a nicely factored solution. At first sight this view of human limitations may strike you as a rather depressing view of our predicament, but I don't feel it that way. On the contrary, the best way to learn to live with our limitations is to know them.

I to dla mnie jest najważniejsza myśl z tego wykładu. Nie jesteśmy wyjątkowi, mamy duże ograniczenia i tylko poznanie ich może pomóc nam sobie z nimi radzić. Dijkstra temat przemyślał bardzo dokładnie i mimo, że dokonał wielu znakomitych rzeczy w informatyce, nazywa siebie skromnym programistą.

Echa przeszłości

Czy to, co działo się w latach 60. i 70., jest w ogóle dla nas istotne?

W końcu mamy wielokrotnie mocniejsze komputery, sprawne systemy operacyjne, nowoczesne języki programowania oraz 50 lat więcej doświadczenia w tworzeniu oprogramowania. Dziesiątki zasad i setki wzorców projektowych bronią nas przed nami samymi.

Mimo tego, takie małe „kryzysy oprogramowania” dzieją się na różną skalę w każdym obszarze, gdzie następuje gwałtowny wzrost. Czy początki budowania dynamicznych serwisów internetowych nie nosiły znamion utraty kontroli nad tym, co się dzieje? Albo era, gdy zaczęto wykorzystywać JS/jQuery do budowania dużych aplikacji frontendowych, co sprawiło, że wielu programistów przechodziło przez tak zwane callback hell?

W przypadku weba sytuacja doprowadziła do przeniesienia patternu MVC znanego z aplikacji desktopowych do frameworków webowych (co po kilku latach znowu dało się nam we znaki). W przypadku JS zaowocowało to powstaniem frameworków takich, jak Angular, Vue czy biblioteki React. To wszystko, by łatwiej radzić sobie z większymi aplikacjami i móc je ogarnąć intelektualnie.

Wpadamy ciągle na stare problemy w nowym wydaniu, próbując przykładać do nich rozwiązania aktualnie modne, albo odgrzać pomysły z przeszłości i ubrać je w nowe warstwy abstrakcji.

Oczywiście to nie jest tak, że nie zrobiliśmy postępu przez 50 lat. W naszym arsenale mamy do dyspozycji dużo więcej technik, a z niektórymi problemami poradziliśmy sobie na tyle dobrze, że już mało kto o nich pamięta. Przykładowo kwestia zarządzania pamięcią - kluczowa kilkadziesiąt lat temu, dziś w zasadzie nie istnieje w większości języków.

Wspomagają nas frameworki, dzięki którym nie musimy wymyślać koła na nowo. W wielu dziedzinach ustaliliśmy „prawidłowy” sposób pisania rzeczy na tyle dobrze, że klepanie kodu stało się mocno odtwórcze. Gdy zapuszczamy się na nieznane terytoria pamiętamy o SOLID, czystym kodzie i wielu innych dobrych praktykach. Mimo tych wszystkich wspaniałości współcześnie równie łatwo stracić kontrolę nad systemem, jak pół wieku temu. Zasady, reguły i frameworki nie stworzą same z siebie całości, która zagwarantowała by bezproblemowy rozwój systemu. To tylko elementy, które my musimy pieczołowicie poskładać.

Człowiek jest taki sam

Jedna rzecz pozostała dokładnie taka sama - ludzki umysł nadal ma swoje ograniczenia. Nie wygląda też na to, że coś tu zmienimy. I to jest ok - tak długo, jak przyznajemy się do tego i mamy w sobie wynikającą z tego skromność.

Gdy weźmiemy postać Dijkstry i jego dorobek, to wydaje się, że musiał być geniuszem, by osiągnąć to wszystko. Dlatego słowa o skromności z jego ust są bardzo znaczące.

Jeżeli macie dłuższą chwilę to zachęcam do przeczytania całego wykładu.