2021-12-15 / Bartłomiej Kurek
Od czego zacząć? #5 (meritum, wiedza/skill)

W procesie nauki danej technologii (języki, systemy) często zdajemy się na "ułatwiacze", które zapewniają nam w krótkim czasie pierwsze rezultaty. Narzędzia ułatwiające pierwsze kroki są pomocne, choć często zapominamy o kroku wstecz pozwalającym nam odkrywać istotę rzeczy od podstaw. W tym artykule omówię kilka przykładów podstaw, które ludzie często pomijają, co z kolei może czasem prowadzić do braku pełniejszego spojrzenia na technologię i braku pewności w sytuacji, kiedy mierzymy się z wyzwaniami.

Przykład: Python (--version)

Jeden z warsztatów zacząłem kiedyś "z grubej rury":

Dobra, na początek każdy uruchamia interpreter Python w konsoli
i mówi mi jaką ma wersję Python oraz jaka to konsola. 

Okazuje się, że nie wszyscy (tu młodzi programiści Python jednego z banków) wiedzą jak sprawdzić wersję interpretera. Jedni używają PyCharm i nie do końca wiedzą w którym okienku "settings" sprawdzić wersję. Mają "środowisko wirtualne", jakoś udało się je zrobić z automatu, "działa, nie ruszam". Inne osoby przy instalacji Python klikały "next, next, ok" i edytor kodu jakoś to uruchamia. "No... python3". Ale jaka wersja? Inni zainstalowali Anaconda i nie wiadomo teraz co i gdzie w "menu start" systemu znaleźć i uruchomić. Jeszcze inni mają np. Jupyter Notebook, który ma wiele zalet (data science), ale być może nie do końca nadaje się jako narzędzie na warsztat, którego zakres obejmował wtedy m.in. wątki (threading), czy pytest.

Odchodząc jednak od doboru środowiska - warto czasem zrobić krok wstecz i (być może piewszy raz w życiu):

  • sprawdzić wersję:
$ python --version
Python 3.9.9
  • uruchomić kod w sesji interaktywnej interpretera:
$ python -q
>>> "hello"
'hello'
>>> print("hello")
hello
  • uruchomić skrypt:
$ python hello_world.py
  • sprawdzić dostępne opcje interpretera
$ python --help
  • stworzyć środowisko wirtualne
$ python -m venv something

Przykład: Python (telefon.txt)

Na co dzień korzystamy z narzędzi wizualnych, menedżerów plików, rozbudowanych środowisk.
Pewne szkolenie z języka Python (na poziomie "średniozaawansowanym", dla ludzi na co dzień pracujących z Python) prowadziłem przy użyciu PyCharm. Na wstępie - dokonując szybkiego omówiena środowiska - stworzyłem nowy plik, a w nim umieściłem swój numer telefonu w wywołaniu funkcji print. Początkowe print("hello world!\n") przybrało zatem postać:

print("+48xxxxxxxxx")

Zrządzenie losu sprawiło, iż zapisując plik nazwałem go "telefon.txt" (wpisując pełną nazwę wraz z rozszerzeniem). PyCharm nie uaktywnił tym samym przycisku "play" uruchamiającego program (w końcu to plik *.txt). Zapytałem wtedy uczestników szkolenia czy mogę wykonać w Pythonie kod zawarty w pliku *.txt. Uczestnicy odpowiedzili, że nie. Uruchomiłem wtedy terminal, a następnie uruchomiłem ten kod najpierw pod interpreterem Python, a następnie interpreterem Perl.

$ cat > telefon.txt
print("+48xxxxxxxxx")
$ python telefon.txt
+48xxxxxxxxx
$ perl telefon.txt
+48xxxxxxxxx$

Interpreter Python to program stworzony w C, który uruchamiamy przekazując mu nazwę pliku z kodem. Nazwa pliku nie ma znaczenia. Rozszerzenia plików są dla narzędzi graficznych w celu powiązania ikon i akcji ("dwuklik", przycisk "play", itp.).

$ mv telefon.txt telefon
$ python telefon
+48xxxxxxxxx

Dla jednych sprawa oczywista, dla innych nie. Plik to bajty w pamięci/na dysku.
Środowiska graficzne nie implementują interpreterów/kompilatorów, a jedynie automatyzują ich uruchomienie. Jeśli nie jesteśmy tego świadomi, to bez rozbudowanego środowiska nie jesteśmy później w stanie pracować z kodem bez tego narzędzia, a inne środowiska (np. kontenery) zdają się wprowadzać nas w "dziwny" świat "nieprzyjaznych" rozwiązań. Środowiska wizualne/graficzne są pomocne, ale warto zrozumieć co robią. Jeśli uczymy się Python, dobrze jest najpierw dowiedzieć się "czym jest Python". Nauka obsługi środowiska programistycznego to odrębna sprawa.

Przykład: Python (interpreter)

Wiele osób nie zna interpretera python. Wydając komendę python, czy też python.exe, wywołujemy interpreter. Jeśli nie podamy mu żadnego pliku do wykonania, uruchomi się w trybie interaktywnym (REPL - Read Evaluate Print Loop). Tryb ten pozwala nam wpisywać i wykonywać wpisany kod bezpośrednio.

Dokumentacja Python zawiera najważniejszy z dostępnych w sieci tutorial dla tego języka.

Pierwszy punkt tego tutoriala informuje czym jest Python, skąd się wzięła nazwa, wspomina o środowiskach/narzędziach.
W drugim punkcie - przystępując do "technikaliów" - zaczynamy właśnie od poznania interpretera.

$ python3
Python 3.9.9 (main, Nov 16 2021, 10:24:31)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 2 ** 3
>>> x
8
>>> x ** (1/3)
2.0
>>> class X:
...     pass
...
>>> type(X())
<class '__main__.X'>
>>> type(type(X()))
<class 'type'>
>>> type(type(type(type(X()))))
<class 'type'>

Python ma wiele opcji, które możemy przekazać przy uruchomieniu, a które w środowiskach graficznych konfigurujemy w okienkach z opcjami (checkbox, dropdown, input, ["apply"], ["ok"], ["x"]). To wszystko to opcje interpretera (skrócony listing):

$ python3 --help
usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-b     : issue warnings about str(bytes_instance), str(bytearray_instance)
         and comparing bytes/bytearray with str. (-bb: issue errors)
-B     : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
-c cmd : program passed in as string (terminates option list)
[...]

Python to język introspektywny. Czym jest introspekcja, jak ją najłatwiej zademonstrować?
Interpreter Python implementuje tryb interaktywny (obsługę linii poleceń). Pozwala nam on m.in dopełniać nazwy symboli (klawisz [TAB], jak w shellu), czy "zajrzeć do wnętrza" danego obiektu:

>>> D = {}
>>> D.
D.clear(       D.fromkeys(    D.items(       D.pop(         D.setdefault(  D.values(
D.copy(        D.get(         D.keys(        D.popitem(     D.update(
>>> D.

Możemy wylistować symbole wewnątrz klasy/obiektu, lub wywołać pomoc dla danego typu/symbolu/obiektu.

$ python3 -q
>>> class X:
...     "Dokumentacja klasy"
...     def __init__(self, *args, **kwargs):
...         "Metoda __init__"
...         pass
...
>>> dir(X)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Pomoc (funkcja help()).

>>> help(X)
Help on class X in module __main__:

class X(builtins.object)
 |  X(*args, **kwargs)
 |
 |  Dokumentacja klasy
 |
 |  Methods defined here:
 |
 |  __init__(self, *args, **kwargs)
 |      Metoda __init__
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

Help dla funkcji print():

Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

Help dla obiektu:

>>> help(3.14)
Help on float object:

class float(object)
 |  float(x=0, /)
 |
 |  Convert a string or number to a floating point number, if possible.

[...]

Przykład: SQL

Zgłosiła się kiedyś do mnie osoba, której celem było zdobycie zatrudnienia na stanowisku "analityk SQL". Wymaganiem pracodawcy były: znajomość języka SQL i umiejętność pracy z bazą danych PostgreSQL. Osoba ta dosyć szybko zrezygnowała, gdyż zaczęliśmy od absolutnych podstaw, czyli instalacji PostgreSQL i uruchomienia podstawowego klienta bazy postgres dostarczonego w tej instalacji - narzędzia psql.

$ psql postgres
psql (14.1 (Debian 14.1-1))
Type "help" for help.

postgres=> select 2 + 2;
 ?column?
----------
        4
(1 row)

postgres=> select now();
              now
-------------------------------
 2021-12-15 16:50:25.429613+01
(1 row)

postgres=> \d
Did not find any relations.
postgres=> CREATE TABLE test(id SERIAL PRIMARY KEY, content TEXT NOT NULL);
CREATE TABLE
postgres=> \d
            List of relations
 Schema |    Name     |   Type   | Owner
--------+-------------+----------+-------
 public | test        | table    | me
 public | test_id_seq | sequence | me
(2 rows)

postgres=> \d test
                             Table "public.test"
 Column  |  Type   | Collation | Nullable |             Default
---------+---------+-----------+----------+----------------------------------
 id      | integer |           | not null | nextval('test_id_seq'::regclass)
 content | text    |           | not null |
Indexes:
    "test_pkey" PRIMARY KEY, btree (id)

Każdy uczy się inaczej, każdy ma inne oczekiwania i wyobrażenia.
Okazuje się, że można się rozminąć w oczekiwaniach już na starcie - chcieć uczyć się podstaw SQL, a oczekiwać, że ściągniemy bazę Salesforce i w jakimś kolorowym narzędziu "wyklikamy analitykę". Istnieją oczywiście narzędzia umożliwiające realizację złożonych zadań, automatyzujących operacje w danej dziedzinie, ale wypadałoby najpierw poznać podstawowe informacje o bazie danych i znać język SQL w zakresie umożliwiającym wykorzystanie go w tych wysokopoziomowych narzędziach. Po drodze zatem są choćby podstawy takie jak: schemat, tabele, indeksy, constraints, widoki, podstawowe operacje CRUD, klauzule, agregaty. Dalej oczywiście mogą być funkcje, triggery, kursory, itd. Kwestia czego tak naprawdę chcemy się nauczyć i na bazie jakiej wiedzy zdobywać chcemy kolejne umiejętności. Samo nic się nie zrobi. Każdy z nas ma inne wyobrażenia i doświadczenia. Ja na hasło "analityk SQL" przywodzę w myślach "obraz" języka SQL, ktoś inny może przywoływać obraz arkusza kalkulacyjnego. Oba wyobrażenia są docelowo poprawne, trzeba jednak posiadać sprecyzowane oczekiwania.

Przykład: budowanie programów

Sporym utrudnieniem dla osób początkujących bywa fakt, że proces nauki danej technologii jest związany z konkretnym środowiskiem. O ile uczymy się składni i mechanizmów danego języka, to wiele rzeczy w tym procesie jest pomijanych. Kiedy przystępujemy do realizacji jakiegoś zadania/programu, często okazuje się, że w obliczu prostych komunikatów błędów "stoimy przed ścianą" i nie wiemy jak pójść dalej.

Zatem trywialny przykład.
Mamy program w C:

void main() { }

Nie mamy VisualStudio ani żadnego guzika "kompiluj"/"uruchom". Jak skompilować ten program?

$ cc main.c -o program

A teraz mamy program:

#include <openssl/md5.h>
#include <string.h>

void main()
{
    const unsigned char *data = "hello world!\n";
    unsigned char result[MD5_DIGEST_LENGTH];
    MD5(data, strlen(data), result);
}

Próbujemy go skompilować:

$ cc main.c -o program
/usr/bin/ld: /tmp/ccYKPP3A.o: in function `main':
main.c:(.text+0x31): undefined reference to `MD5'
collect2: error: ld returned 1 exit status

Co teraz?

$ cc main.c -o program -l crypto

Omówienie ogólnej budowy programów przedstawiam w części kolejnej.

Przykład: Git

Git to program konsolowy. Dzisiaj w opisach pakietów występuje jako:

$ dpkg -l git | tail -1
ii  git            1:2.33.0-1   amd64        fast, scalable, distributed revision control system

Kiedyś w opisach występował jako "stupid content tracker".
Obsługi Git możemy się uczyć bezpośrednio w terminalu lub z poziomu zintegrowanych środowisk. W tym drugim przypadku nauczymy się obsługi git wewnątrz środowiska, a sam git może wydawać się skomplikowany ze względu na mnogość opcji i akcji występujących w kontekstowych menu. Większość akcji w tych środowiskach ma jakiś wizualny "flow", zatem "dzieje się dużo rzeczy". Podchodząc do sprawy od podstaw - git to narzędzie konsolowe, a do jego sprawnej obsługi wystarczy przejść przez krótki tutorial demonstrujący podstawowe operacje. Zacząć jest najlepiej od pustego katalogu i inicjalizacji repozytorium. Później dodajemy pliki, zatwierdzamy zmiany, oglądamy rezultaty. Tworzymy "branche", zmieniamy zawartość plików, doprowadzamy do konfliktu i przechodzimy przez proces merge (składania zmian z różnych wersji). Następnie dodajemy "remote" i wysyłamy tam zmiany. Przyzwoity warsztat z obsługi git - obejmujący najistotniejsze scenariusze i wyjaśnienia - zajmuje godzinę, a na spokojnie dwie - łącznie ze stworzeniem i zestawieniem zdalnego repozytorium na którejś z popularnych platform (GitHub, GitLab, czy Gitea (self-hosted)).

Git jest systemem kontroli wersji, pomaga nam śledzić i składać w całość zmiany w plikach, przechowuje historię, do której możemy wracać. Posługując się narzędziem git - takim jakim jest - uczymy się git. Nauka "nakładek git" to zupełnie inna sprawa. PyCharm ma swój workflow, Emacs ma Magit (bardzo wychwalany), inne narzędzia zapewniają obsługę git po swojemu. Niemniej, są to narzędzia wywołujące akcje git (poprzez biblioteki lub po prostu wywołując za nas sam program git). Różnicą jest tutaj to co poznajemy. Albo poznajemy system git, albo workflow danego programu. Z kwestią workflow wiąże się polityka projektu (np.: rebase, czy nie rebase?). Jeśli umiemy obsłużyć program git, to będziemy sprawnie, pewnie i bez przeszkód poruszać się w każdym workflow i w każdym środowisku.

Osobiście od zawsze używam po prostu git w shellu. To zaledwie kilka komend "na krzyż". Tak, mógłbym włączyć magit, ale nigdy tego nie robię. Najwygodniej, najprościej, przejrzyście i "explicite" jest w shellu: git taki, jakim jest.
"A stupid content tracker".

$ git ci -a -m "basics: 5"
[devel 390cffc] posts/basics/od-czego-zaczac/5-meritum/index.md
 1 file changed, 16 insertions(+)

$ git push
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 4 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 2.08 KiB | 2.08 MiB/s, done.
Total 8 (delta 5), reused 0 (delta 0), pack-reused 0
To ssh://git.codeasap.pl:22/codeasap/blog.git
   84d74c7..390cffc  devel -> devel

Przykład: shell

Istota kolejnego przykładu nie odnosi się do samych narzędzi, a do braków wynikających z pomijania podstaw.

Autentyczny przebieg zdarzeń. Dialog:
- Chcę się nauczyć automatyzować zadania w bashu.
- Czy na co dzień masz styczność z shellem, pracujesz z shellem?
- Tak, na co dzień pracuję w firmie XXX i piszę różne drobne skrypty, grepy, zliczanie, itd. Chcę się nauczyć automatyzować większe zadania.
- To zróbmy może na początek parę podstawowych przykładów, będę wiedział na czym stoimy, OK?
- Jasne, OK.
- To zacznijmy od czegoś prostego. Oblicz w shellu "2 + 2".
- Yyyy...

Demonstruję zatem program "2 + 2".

$ expr 2 + 2
4

Kontynuacja dialogu:
- O widzisz, nie znałem.
- Nie każdy się z tym zetknął, ale już znasz - łatwo poszło. Umiałbyś zatem zrobić "2 * 2"?
- No spróbuję:

$ expr 2 * 2
expr: syntax error: unexpected argument ‘katalog’

Yyyy...
- No tak, musisz "wyeskejpować asterisk", tę gwiazdkę. Shell rozwija ją na wszystkie pliki (glob).
- Aaaa, ok.

$ expr 2 \* 2
4

Kontynuacja dialogu:
- Elegancko. To teraz stwórz zmienną x o wartości 4, a następnie zmienną y o wartość 3 razy x. Wypisz wartość zmiennej y, a następnie usuń je obie.
- Yyyyy...

Często dążymy do konkretnych celów, do realizacji których nie potrzebujemy szczegółowej wiedzy. Zresztą - nie da się wszystkiego wiedzieć ani pamiętać. Dlatego ważnym "skillem" jest wnioskowanie. Kiedy korzystamy z różnych narzędzi, próbujmy analizować co tak naprawdę robimy. Zrozumienie co już robimy pozwoli nam na dedukcję i ekstrapolację dobrze znanych nam już mechanizmów na inne konteksty użycia. Jeśli umiemy rm *, ls a*, czy grep something *.txt, to możemy dostrzec i wywnioskować, że to taka sama "gwiazdka w shellu".

$ x=4
$ y=$(expr 3 \* $x)
$ echo $y
12
$ unset x y
$ echo $x

$ echo $y

$ 

Podsumowanie

Powoli

Przykłady można mnożyć, jednak moim celem jest zwrócenie uwagi na kwestie pomagające nam w procesie nauki - czy to dotyczących środowisk, czy wykorzystywania już posiadanych przez nas umiejętności. Mnie kiedyś udzielono cennej, krótkiej i treściwej rady:

Opłaca się robić powoli.

Synteza

Ucząc się różnych technologii chcemy widzieć zadowalające rezultaty w możliwie najkrótszym czasie. Zdajemy się więc na różne "tricki" i obejścia. Nie ma w tym nic złego o ile nie zapominamy o istocie rzeczy - chcemy wiedzieć. Kształtowanie wiedzy podlega takim samym prawidłom jak programowanie: "prawo abstrakcji". Dlatego nie ma niczego złego w szukaniu różnych rozwiązań - na zasadzie: "liznąć" trochę tego, trochę tamtego, samo się uleży i uformuje (synteza wiedzy). Ważne, aby to co poznaliśmy, było zrozumiałe. Wszystko wymaga czasu i praktyki. Duże konstrukty składają się z mniejszych klocków.

Wiedza i skillset

Kompetencje można podzielić na wiedzę i skill. Sam skill pozwala nam powtarzać to, co już umiemy. Często jednak brakuje nam wiedzy, by wykorzystać własny "skillset" w innym kontekście. Sam skillset - niepoparty wiedzą - może nas doprowadzić do: "nie umiem, nigdy nie robiłem". Wiedza może prowadzić do: "jeszcze nie umiem, ale wiem na czym polega, wiem jak ma działać, zaraz ogarnę tutejszą składnię i to zrobię". Dlatego - być może:

Opłaca się uczyć powoli.

Oraz:

Jeśli nie umiesz czegoś wytłumaczyć pięciolatkowi, to znaczy, że tego nie rozumiesz.