Pierwsze kroki

Dawno temu czytałem, pewien artykuł o nauce programowania. Można go streścić mniej więcej tak: na początku ludzie robią pewne założenie jak działa język programowania. Później stosunkowo niewielu z nich jest w stanie samemu je zmienić. Tak więc to, czy ktoś od razy złapie bakcyla, czy może się odbije, zależy od tego, czy założenia jakie poczynił na początku przypadkiem się pokrywają z tym jak dany język faktycznie działa.

Eksperyment był przeprowadzany na laboratoriach z języka C. Podobne, powrarzane później doświadczenia prawdopodobnie też. Jednym z pytań, które silnie pokazywało, kto się przebije przez kurs, a kto polegnie, wyglądało mniej więcej tak:

a = 10
b = 20
a = b

jakie na koniec są wartości a oraz b?

Dla większości programistów odpowiedź na to pytanie jest banalna: zarówno a jak i b mają wartość 20. Ale jednak, jeśli się nad tym zastanowić odpowiedź wcale nie jest banalna i oczywista.

Różne modele

Każdy z nas na matematyce poznał znak =. Jeśli go postawimy między dwoma wartościami, oznacza to, że uznajemy je za równe. Jeśli nie są, równe to zdanie logiczne jest fałszywe. Teraz, spróbujmy odczytać jeszcze raz powyższe zadanie używając wyniesionego z klasy rozumowania:

  1. a jest równe 10 (tak więc za każdym razem jak widzimy, a możmy myśleć 10 i na odwrót)
  2. b jest równe 20 (podobnie jak wyżej)
  3. postawiliśmy zdanie logiczne a jest równe b. Wiedząc, że pierwsza wartość to 10, a druga to 20, widzimy, że postawiono nam zdanie 10 = 20, które jest oczywistym fałszem
  4. uznajemy, że 2 pierwsze zdania opisują nam wartości a i b, zaś trzecie zdanie nie ma sensu.

Ale jak w takim razie programiści dochodzą do wniosku, że a i b są równe 20? Mniej więcej tak:

  1. nadajemy a wartość 10
  2. nadajemy b wartość 20
  3. nadajemy a wartość b, które zawiera 20 - od tej pory a posiada nową wartość!
  4. zarówno a jak i b posiadają wartość 20.

Pokazuje nam to zasadniczą różnicę w rozumowanie. W matematyce zmienna to pewna nazwa. Używamy jej w zastępstwie konkretnej wartości, ale w danym kontekście ta wartość jest niezmienna. Gdy podajemy wzór na pole trójkąta:

$s = \frac{ah}{2}$

nie uważamy, że a, b i s możemy dowolnie podmieniać - określamy natomiast pewien stały związek - jeśli w danym kontekście przez s oznaczymy pole trójkąta, przez a jego podstawę, zaś przez h wysokość, to aby wartości miały sens, muszą zachodzić stosunki opisane wzorem. Kiedy jednak już wyznaczymy kontekst - np. wskazując palcem na konkretny trójkąt - to w danym kontekście wartości nie zmienią się nigdy. Dla odróżnienia stała w matematyce również jest pewnym aliasem, jednak takim, który zachowuje swoją wartość niezależnie od kontekstu. Np. niezależnie od tego na jakie koło patrzymy liczba pi będzie zawsze tą samą liczbą.

Jeśli jednak zaczniemy programować, te 2 słowa zmienią jednak znaczenie. W programowaniu zmienna to kontener na wartość, którą możemy w dowolnym momencie nadpisać. Dlatego też możemy powiedzieć teraz a będzie oznaczało 10, a już za chwilę później a teraz a będzie zawierało wartość b. To co rozumiemy w matematyce przez zmienną - wartość ustalona, ale tylko w pewnym kontekście - jest w programowaniu stałą. To co w matematyce jest stałą często również.

Okazuje się, że są to 2 całkowicie różne filozofie podejścia do programowania i, co istotne, każda z nich posiada języki dla których tylko ona jest poprawna. Być może studenci, który odpadli na pierwszym semestrze poradziliby sobie lepiej, gdyby trafili na język z tej drugiej szkoły.

Podejście imperatywne

Zmienność (inaczej: mutowalność) wartości zmiennych oraz obiektów leży u podstaw programowania imperatywnego. W tej filozofii wywodzącej się z teoretycznego modelu maszyny Turinga (tego gościa od Enigmy, granego w Grze Tajemnic przez Benedicta Cumberbatcha) opisujemy kolejny kroki jakie ma wykonać maszyna/nasz program: weź te dane stąd, zrób z nimi to i tamto, jeśli wartość zmiennej jest taka, a taka zrób to i to, w przeciwnym razie tamto.

# znajdź 5 pierwszych 5-literowych imion
names = ["Jackson", "Aiden", "Lucas", "Liam", "Noah",
         "Ethan", "Mason", "Caden", "Oliver", "Elijah"]

result = []             # utwórz pusty wynik
for name in names:      # dla każdego imienia z listy
  if len(name) == 5:    # sprawdź, czy imię ma 5-liter
    result.append(name) # jeśli tak dodaj je do wyniku
  if len(result) >= 5:  # jeśli zebraliśmy już 5 imion
    break               # zakończmy szukanie

print(result)

Jest to model dość mało przyjemny dla człowieka. Zmusza do zejścia na poziom maszyny i rozumowania w takich kategoriach w jakich operuje komputer. (Do pewnego stopnia może wyjaśniać to dlaczego, niektórzy programiści są jakoś skrzywieni).

Ma ono tylko jedną zaletę - ponieważ myślisz jak komputer i zapisujesz rozkazy w natywnym języku komputera, programy mają szansę być niezwykle wydaje w działaniu. Każda optymalizacja wymaga zrozumienia, jakie dokładnie operacje wykonuje komputer, co jest (względnie) łatwe, jeśli opis działania oraz stan faktyczny nie różnią się specjalnie.

Ta jedyna zaleta, często jest jednak decydująca.

Podejście deklaratywne

Podejście deklaratywne opiera się na definiowaniu zależności między kolejnymi wynikami pośrednimi:

# znajdź 5 pierwszych 5-literowych imion
names = ["Jackson", "Aiden", "Lucas", "Liam", "Noah",
         "Ethan", "Mason", "Caden", "Oliver", "Elijah"]

# weź wartość każdego 5-literowego imienia z listy
# zaś z powstałej listy weź 5 pierwszych elementów
result = [name for name in names if len(name) == 5][:5]

print(result)

Często (po opanowaniu składni i konwenci) okazuje się być krótsze, czytelniejsze i mniej podatne na błędy niż podejście imperatywne. Niestety, jest też nieco mniej wydajne - skupia się bowiem na czytelności dla człowieka, a nie komputera.

Dość dobrze łączy się z programowanie funkcyjnym, pod które podstawy teoretyczne podłożył rachunek Lambda Churcha (nawiasem mówiąc promotora doktoratu Turinga).

Wszyscy programiści zaczynali tak samo?

Pierwszymi językami na które wystawieni są studenci informatyki były przez długi czas C oraz Java. Od niedawna niektóre uczelnie zdecydowały się zaczynać naukę od języków takich jak Python.

Pojawia się tutaj pewien problem: oba języki są raczej imperatywne, Java od względnie niedawna wspiera programowanie funkcyjne i deklaratywne, a wielu wykładowców opiera się na programach napisanych 10, 15 lat temu.

Tymczasem dla wielu ludzi model deklaratywny może być łatwiejszym do rozpoczęcia nauki. Jeśli przez całe życie byli uczeni w szkołach, że = oznacza jest równe, a nie przypisz nawet najprostsze obliczenia mogą być da nich mylące. (Podobnie jest z ludźmi, który zbyt długo programowali w modelu imperatywnym. Są tak przyzwyczajeni do raz nauczonych zasad, że wielu osobom ciężko się było przestawić na np. programowanie funkcyjne). Z mojego punktu widzenia oznacza to, że wiele osób mogło się odbić tylko dlatego, że zaczęło od “złego” języka!

Oczywiście, możemy się sprzeczać, że ktoś kto ma ambicje być dobrym programistą, powinien zdobyć doświadczenie z każdym paradygmatem. I jest to prawda. Jednak zdecydowanie łatwiej jest nam pokonywać trudności, jeśli mamy już jakieś sukcesy na koncie pozwalające nam wierzyć, że się na uda.

Twój pierwszy język nie jest zobowiązujący. Jeśli próbujesz na różne sposoby i “nie trybi” spróbuj z innym. A nuż okaże się, że po prostu ten konkretny ci nie pasował? Ja sam odbiłem się od JavaScript wielokrotnie, w międzyczasie ucząc się języków, które są uważane ze trudniejsze (C, Java, C++, Scala). Z kolei współcześnie, wiele osób właśnie od JavaScript zaczyna i wciąga się tak bardzo, że praktycznie nie widzą potrzeby nauczenia się innego języka!

Wybór języka

Jeśli wiesz po co ci język (koniec, końców to tylko narzędzie) będzie łatwiej ci się na jakiś zdecydować. (I tak nie skońszy się na jednym, więc nie jest to decyzna na całe życie, nie przejmuj się):

  • jeśli chcesz zrobić stronę internetową - tą część którą widać, a nie tą która pyta bazę, mus to język opisu dokumentów (HTML), designu strony (CSS) oraz język programowania, w którym opiszesz jej zachowanie (praktycznie obowiązkowy JavaScript),
  • jeśli chcesz zrobić całość serwisu internetowego: Python (z frameworkiem Django) albo Ruby (z frameworkiem Rails), są dobrym punktem wyjścia,
  • jeśli chcesz programować kontrolery, płytki drukowane, obliczenia graficzne, to C, C++, od niedawna również Rust,
  • jeśli interesuje cię wyłącznie stabilość finansowa i dużo ofert pracy, to Java albo C#,
  • jeśli interesuje cię tylko robienie pluginów do Wordpressa albo Magento, wybierz PHP, ja jednak wybrałbym godność i inny język,
  • jeśli chcesz robić w branży gier komputerowych to zmień zdanie.

Po opanowaniu jednego języka, wszystkie inne (z tego samego paradygmatu) są już proste.

Jak się uczyć?

Zrób tyle lekcji i ćwiczeń podręcznika/samouczka, żebyś był w stanie robić coś samemu. Później zacznij własny, mały, projekt i staraj się użyć w nim wszystkiego. Czytaj podręcznik dalej i próbuj stosować nowe rzeczy w swoim projekcie.

Opanuj angielską terminologię jak najszybciej, na polskich stronach będzie zdecydowanie mniej informacji niż na angielskich, większość nieakturalna. Korzystaj z angielskich forów programistycznych oraz stron Q&A takich jak StackOverflow. Reddit i fora /r/learnprogramming i /r/cscareerquestions też mogą pomóc. Sprawdź w aplikacji Meetup jakie grupy zainteresowań działają w twojej okolicy i przejdź się pogadać i popytać.

Na końcu, załóż konto na Twitterze i nie bój się zagadywać do ludzi w społeczności. Są bardzo pomocni, zaś twoja aktywność pomoże budować sieć kontaktów.

Na koniec

Jeśli czujesz, że programowanie cię nie interesuje w żadnym stopniu, nie ma musu. To dobry powód, żeby sobie odpuścić. Jednak w żadnym wypadku nie uważaj, że musisz dać sobie siana, bo masz za małe IQ albo coś z tobą nie tak. Jeśli widzisz, że inni ludzie łapią pewne rzeczy szybciej, prawie na pewno nie są od ciebie 10x bystrzejsi. Po prostu robili już kiedyś coś podobnego. Próbuj dalej i w końcu “zaskoczy”!