Cyfrodziewczyny kontra Brotopia

Udało mi się w końcu usiąść do bloga. Kolejna część pisania Ego w Elixirze poczeka, a teraz obiecana i wyczekiwana, przez co poniektórych recenzja podwójna. Zanim jednak przejdziemy do mięska, muszę was ostrzec. Poniższe książki traktują o podobnym temacie, ale nie można ich bezpośrednio porównywać. Opisują dwie różne epoki w dwóch różnych systemach politycznych. Nie mam też tutaj zamiaru rozstrzygać czy PRLowski komunizm był lepszy dla kobiet w kontekście rynku pracy, czy też współczesny neoliberalizm na rynku IT w amerykańskim wykonaniu jest zły. Zostawię ten temat wykopowym ekspertom.

Okładka Brotopia
Tytuł: Brotopia
Autor: Emily Chang
Rok: 2019 (2018 EN)
ISBN: 978-83-954-1060-4

O czym jest ta książka? Jeżeli przyjmiemy prostą interpretację, to będzie ona o silnie zmaskulinizowanym zawodzie, w którym kobiety są niemile widziane. Będzie to też historia kilkunastu kobiet, które spotkały się z różnego rodzaju zachowaniami, które w cywilizacji zachodniej kwalifikuje się jako molestowanie seksualne. Mówię tu o cywilizacji zachodniej, ponieważ istnieją różnice w postrzeganiu tego, co jest molestowaniem lub niestosownym zachowaniem u nas, w USA, w krajach UE, czy w Japonii. I raczej mam tu na myśli drobnostki niż przestępstwa.

Emily Chang jest znana ze swojego feminizmu i wspierania mniejszości. Czy to rzutuje na książkę? Do pewnego stopnia, ponieważ wybrała ona taki, a nie inny temat. Z drugiej strony w spójny i sensowny sposób przedstawia fakty. Jej bohaterkami są kobiety, które są w jakiś sposób znane w Dolinie Krzemowej. Czy to jako kobiety sukcesu jak Susan WojcickiW, czy też jako sygnalistka, które zaczęły „kręcić Małysza” w temacie molestowania jak Susan FowlerW. Pod tym względem autorka gra fair i pokazuje, że nie wszystko jest OK niezależnie od poziomu korpo hierarchii, na którym jesteśmy.

Sama książka zaczyna się od historii tesów predyspozycji William Cannona i Dallis Perriego i tego jak wpłynęły one na kształt rynku pracy w IT. Niestety jest tu bardzo dużo uproszczeń, które mogą prowadzić do błędnych wniosków, że przez źle skonstruowane testy, branża wyparła kobiety. Co prawda są też wspomniane „komputery jako zabawki dla chłopców”, ale nadal brakuje tu kompletnej analizy zjawiska. Co ciekawe w Cyfrodziewczynach dużo lepiej widać przyczyny, które w USA „zamyka się” w hasłach o niezbyt sensownym teście kompetencji i samo nakręcającej się nerdoizacji branży. O tym jednak za chwilę. Znajdziemy tutaj też opisaną późniejszą historię przekształcania się IT w „męską” dziedzinę. Pierwsza część to też opis historii powstania tzw. Mafii Paypala, debunk mitu merytokracji czy też opis znanego z innych branży mechanizmu zatrudniania przez polecenia. Całość ładnie zamknięta historią Google, które miało dobre chęci, sensowne procesy, a i tak się było wyjebało na rowerku „diversity”.

Kolejna część książki (część nie jest tożsama z rozdziałem), to zbiór historii związanych z molestowaniem, dyskryminacją i poniżaniem kobiet. Będzie tutaj o niestosownych zachowaniach na imprezach integracyjnych, ale też o tym jak płeć wpływa na ocenę przez VC, czy o tym, w jaki sposób załatwia się interesy. Mamy więc historię Ubera, gdzie szef był społecznym prymitywem, ale miał niezłe kontakty. Mamy kluby ze stritizem, gdzie załatwia się biznesy. Mamy w końcu opis różnych pseudodziałań ze strony korporacji, które udają, że chcą rozwiązać problem. Co ciekawe mamy tutaj też widoczny podział na różne podbranże naszej Kochanej Branżuni. Inaczej sprawy załatwia się w startupach, inaczej w korpo, a jeszcze inaczej wśród twórców gier.

Na koniec autorka funduje nam krótki rozdział, gdzie opisuje, co branżunia mogłaby zrobić, by było lepiej. Jest tu też poruszony „problem” mniejszości zarówno etnicznych jak i seksualnych. Rzecz w tym, że cały pomysł na naprawę sytuacji opiera się o błędne założenia, że możemy poświęcić jakość kadr oraz jakość produktu w imię budowania różnorodności. Niestety taki mechanizm nie będzie działał, bo jest to strategia przegrywająca na rynku. Cóż, amerykanie są dziwni, ale ta dziwność powoduje, że ich biznesy dobrze działają.

Czy warto?

Moim zdaniem warto. Książka jest dobra, choć jeżeli ktoś chciałby ją przełożyć jeden do jednego na nasze Polskie warunki, to będzie to bezsensowne. Daje jednak wgląd w pewne mechanizmy, które rządzą rynkiem w USA. W naszym zhomogenizowanym społeczeństwie, w którym za „inność” etniczną uważa się mówienie jo zamiast tak, wiele problemów po prostu nie istnieje. Ewentualnie ich siła rażenia jest stosunkowo niewielka. Książka jest też ciekawym studium samego biznesu. Różnic, które występują pomiędzy USA i Europą, a które mogą powodować, że nasze pomysły będą działać inaczej tu i tam.

Okładka Cyfrodziewczyny
Tytuł: Cyfrodziewczyny
Autor: Karolina Wasilewska
Rok: 2020
ISBN: 978-83-662-3287-7

Reportaż, zbiór wywiadów, czy powieść historyczna? Trudno powiedzieć. Cyfrodziewczyny, to kilkanaście zredagowanych rozmów, które ułożone według chronologii budują nam obraz polskiego IT od wczesnych lat 50-tych, aż do upadku PRLu. Pośrednio też możemy poznać współczesne losy bohaterek. Całość zaczyna się od skrótowej historii komputerów na zachodzie i kończy się mniej więcej na wspomnianym tu wcześniej teście kompetencji zawodowych. Mając taką „bazę”, autorka przechodzi do pierwszych polskich maszyn, czyli ARAL i ARR. Koniec końców wstęp do naszej historii kończy się powstaniem EMAL-a i pojawieniem się ogłoszeń o pracy na UW i PW…

Później jesteśmy prowadzeni przez historię pierwszych polskich komputerów jak XYZ, serii ZAM i UMC. Każda z tych historii to też historia jakiejś grupy kobiet, które trafiły do zakładów badawczo-rozwojowych prosto po studiach. Mamy też ELWRO i historię rywalizacji pomiędzy Warszawą i Wrocławiem. Jednak wraz z upływem czasu widać, że polityka coraz bardziej wchodzi w IT. Mamy tu też mały debunk sławnego K202. W dużym skrócie – jak zawsze polityka dała o sobie znać. I to nawet nie na zasadzie, że ZSRR coś tam chciało, bo przy okazji i tak był problem z projektem RIAD, czyli takim pan-demoludowym komputerem. Tutaj wyszła zwykła przepychanka między ludźmi i ich animozje.

I w sumie to jest tyle… Serio. Można by się rozpisywać, o poszczególnych etapach, ale ta książka, to dobre opracowanie historii. Zakończenie, omawia obecny udział kobiet w IT. Jest trochę marudzenia o różnicach w wychowaniu czy promocji różnych projektów, ale to takie wypełniacze.

Czy warto?

Tak. Szczególnie w pakiecie z Brotopią. Inaczej nie będzie można zrozumieć tej pierwszej. Jest to też ciekawa lekcja historii informatyki oraz źródło dobrych historyjek, które tak naprawdę nie różnią się wiele od współczesnych problemów z którymi musimy się mierzyć 😀

Lekser Ego w Elixirze część III – komentarze i liczby – wideo

Zgodnie z zapowiedzią dziś jest nagranie. Nie jest pro, nie jest nawet amateur, ale jest 😀

ps. blog nie wyświetla się na jvm-bloggers 🙁 Będzę walczyć o powrót

Lekser Ego w Elixirze część II – preoptymalizacje

Jarek i Wiktor udostępnili swój kod na githubie. Pierwsza rzecz, która rzuciła mi się w oczy, to ilość pracy, jaką wykonali poza kamerą. Ich rozwiązanie wspiera już kod w wielu linijkach oraz liczby. To nadal są proste zagadnienia, ale nie są trywialne. Dlatego też, w tym wpisie zajmiemy się pewnymi preoptymalizacjami, które ułatwią nam pracę w przyszłości.

Motywacja

Oryginalne założenie było takie, by implementacja w Elixirze była jak najbliższa tej w Javie. Dzięki temu osoby, które czytają mój kod, a nie znają Elixira, będą w stanie zrozumieć konstrukcję programu, patrząc i porównując go z programem napisanym w Javie. Jednak nie jest to dobra strategia. Lekser zaczyna się komplikować, a przenoszenie kodu strukturalno-obiektowego do świata funkcyjnego nie jest dobrym pomysłem. Z wielu powodów, ale najważniejszy to, że w efekcie otrzymujemy kod, który jest słaby. Dlatego ugryzę ten problem trochę inaczej. Kod będę tworzył w Elixirze. Zachowam spójność nazewniczą, o ile będzie to możliwe. Jednak przede wszystkim postaram się, by kolejne kroki były małe. Pozwoli to na lepsze wytłumaczenie różnych zagadnień i łatwiejszą analizę kodu przez czytelników.

Zacznijmy więc zabawę, ale najpierw małe przypomnienie. Kod wyjściowy wygląda w następujący sposób:

Listing 1. Kod wyjściowy

defmodule Ego.Lexer do
  
  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> token
  end

  defp token(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp token([], accumulator, buffer, _), do: accumulator ++ [:eof]

  defp token(charlist, accumulator, buffer, :common) do
    [h | t] = charlist

    case h do
      "(" -> token(t, accumulator ++ read_buffer(buffer) ++ [:open_bracket], [])
      ")" -> token(t, accumulator ++ read_buffer(buffer) ++ [:close_bracket], [])
      " " -> token(t, accumulator ++ read_buffer(buffer), [])
      "\"" -> token(t, accumulator ++ read_buffer(buffer), [], :text)
      _ -> token(t, accumulator, buffer ++ [h])
    end
  end

  defp token(charlist, accumulator, buffer, :text) do
    [h | t] = charlist

    case h do
      "\"" -> token(t, accumulator ++ read_buffer(buffer), [], :common)
      _ -> token(t, accumulator, buffer ++ [h], :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.join("") |> String.to_atom()]
  end
end

Odwrotna kolejność

Pierwszą drobną optymalizacją będzie sposób, w jaki tworzę listę tokenów. Dotychczasowy kod (Listing 1.) działał w taki sposób, że gdy mamy już gotowy token, to zostaje on dołączony na końcu akumulatora. Jest to łatwa do zrozumienia konstrukcja, która jednak nie jest optymalna. W Erlangu operator ++ działa w ten sposób, że kopiuje argument po lewej stronie, by wykorzystać go w nowej liście. Lepszym rozwiązaniem jest dodawanie nowych elementów na początku listy, a następnie wywołanie Enum.reverse, by uzyskać końcowy rezultat. Opisane jest to w artykule The Seven Myths of Erlang Performance. W efekcie otrzymamy:

Listing 2. Zmiana sposobu tworzenia akumulatora

defmodule Ego.Lexer do

  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> token
    |> Enum.reverse
  end

  defp token(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp token([], accumulator, buffer, _), do: [:eof] ++ accumulator

  defp token(charlist, accumulator, buffer, :common) do
    [h | t] = charlist

    case h do
      "(" -> token(t, [:open_bracket] ++ read_buffer(buffer) ++ accumulator, [])
      ")" -> token(t, [:close_bracket] ++ read_buffer(buffer) ++ accumulator   , [])
      " " -> token(t, read_buffer(buffer) ++ accumulator, [])
      "\"" -> token(t, read_buffer(buffer) ++ accumulator, [], :text)
      _ -> token(t, accumulator, [h] ++ buffer)
    end
  end

  defp token(charlist, accumulator, buffer, :text) do
    [h | t] = charlist

    case h do
      "\"" -> token(t, read_buffer(buffer) ++ accumulator, [], :common)
      _ -> token(t, accumulator, [h] ++ buffer, :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.reverse |> Enum.join("") |> String.to_atom()]
  end
end

Jak widać, zmiana dotknęła wiele miejsc w kodzie. Jednak dzięki temu, że Elixir opiera się o dopasowanie wzorców, nie musieliśmy zmieniać API. Operację odwrócenia realizujemy w dwóch miejscach. W funkcji tokenize odwracamy listę tokenów, by otrzymać wynik. W funkcji read_buffer odwracamy bufor przed jego zrzuceniem, bo bufor też jest akumulatorem, tyle tylko, że lokalnym.

No właśnie…

Nazewnictwo

Kolejny etap to doprowadzenie do porządku nazewnictwa. Funkcja read_buffer wydaje się idealnym przykładem. Zadanie tej funkcji to odczytanie zawartości bufora, bo chcemy go opróżnić, ale… opróżniamy bufor w innym miejscu. Taka mała podpucha, dla kogoś, kto lubi refaktoryzaować nazwy dla samej refaktoryzacji nazw. Prawdziwym problemem jest funkcja token, która produkuje listę tokenów. Odwróconą. Jak ją nazwać? Na obecnym etapie tokens będzie ok. Kolejnym drobnym problemem jest zmienna buffer, w metodzie token(s) dopasowanej do pustego ciągu znaków. Jest nieużywana, więc można ją zastąpić znakiem _. Ostatnia rzecz związana z nazewnictwem, będzie polegać na pozbyciu się niepotrzebnego przypisania [h | t] = charlist. To są w sumie drobne poprawki, które nie wpływają na kod.

Listing 3. Zmiana nazw

defmodule Ego.Lexer do

  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> tokens
    |> Enum.reverse
  end

  defp tokens(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp tokens([], accumulator, _, _), do: [:eof] ++ accumulator

  defp tokens([h | t], accumulator, buffer, :common) do
    case h do
      "(" -> tokens(t, [:open_bracket] ++ read_buffer(buffer) ++ accumulator, [])
      ")" -> tokens(t, [:close_bracket] ++ read_buffer(buffer) ++ accumulator   , [])
      " " -> tokens(t, read_buffer(buffer) ++ accumulator, [])
      "\"" -> tokens(t, read_buffer(buffer) ++ accumulator, [], :text)
      _ -> tokens(t, accumulator, [h] ++ buffer)
    end
  end

  defp tokens([h | t], accumulator, buffer, :text) do
    case h do
      "\"" -> tokens(t, read_buffer(buffer) ++ accumulator, [], :common)
      _ -> tokens(t, accumulator, [h] ++ buffer, :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.reverse |> Enum.join("") |> String.to_atom()]
  end
end

Nie wygląda to źle. Jednak jak zawsze w przypadku nazw można tu coś usprawnić. Kolejna zmiana jest znacznie ciekawsza.

Reprezentacja znaków – Charlisty

W Javie jest tak, że String i Character rozróżniamy na podstawie rodzaju cudzysłowu użytego do stworzenia obiektu. String to cudzysłów podwójny, zwany polskim cudzysłowem apostrofowym. Character to cudzysłów pojedynczy, zwany brytyjskim. Istotne jest tutaj to, że String może zawierać wiele znaków, a Charakter to pojedynczy znak. W Elixirze jest trochę inaczej.

Co to jest Charlist?

Podwójny cudzysłów definiuje nam String. Pojedynczy definiuje tzw. Charlist, czyli listę znaków. Jest to lista, która zawiera liczby całkowite odpowiadające kodom poszczególnych znaków. I tak znaki ( i ) mają kody odpowiednio 40 i 41, więc zapis `()` będzie odpowiadać [40, 41]. Pojedynczy znak może być reprezentowany jako liczba lub też jako lista. Jest to trochę zagmatwane i jak zaraz zobaczycie, może prowadzić do „dziwnych” konstrukcji. Najłatwiej jednak przyjąć, że w Javie będzie to po prostu List<Character>.
Ale przecież String, który podzielimy na znaki, też jest listą. Tak, jest listą. Jednak w tym przypadku nie mamy rozróżnienia na znaki wielobajtowe i jednobajtowe. W efekcie nie możemy łatwo odsiać pewnych znaków. Z drugiej strony nie musimy tak przejmować się znakami wymagającymi znaku ucieczki jak np. znak końca linii.

Refaktoryzacja do Charlist

Zmiana jest dość prosta, ale znowuż, dotykamy praktycznie całego kodu.

Listing 4. Wprowadzamy charlist

defmodule Ego.Lexer do
  def tokenize(program) when is_binary(program) do
    program
    |> String.to_charlist()
    |> tokens
    |> Enum.reverse()
  end

  defp tokens(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp tokens([], accumulator, _, _), do: [:eof] ++ accumulator

  defp tokens([h | t], accumulator, buffer, :common) do
    case [h] do
      '(' -> tokens(t, [:open_bracket] ++ read_buffer(buffer) ++ accumulator, [])
      ')' -> tokens(t, [:close_bracket] ++ read_buffer(buffer) ++ accumulator, [])
      ' ' -> tokens(t, read_buffer(buffer) ++ accumulator, [])
      '"' -> tokens(t, read_buffer(buffer) ++ accumulator, [], :text)
      _ -> tokens(t, accumulator, [h] ++ buffer)
    end
  end

  defp tokens([h | t], accumulator, buffer, :text) do
    case [h] do
      '"' -> tokens(t, read_buffer(buffer) ++ accumulator, [], :common)
      _ -> tokens(t, accumulator, [h] ++ buffer, :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.reverse() |> List.to_string() |> String.to_atom()]
  end
end

Clue zmiany leży w warunku case. Można ją przeprowadzić na dwa sposoby. W powyższym zapisałem case [h], ponieważ chcę dopasować h do kolejnych list znaków stworzonych za pomocą pojedynczego cudzysłowa. Zmiast tego mogę użyć dopasowania do wartości liczbowych, ale takie rozwiązanie oznacza, że będę mósiał znać kody znaków. Ta zmiana prowadzi nas do kolejnej, która jest pierwą z dwóch zmian „na przyszłość”.

Case na cond i co to jest if

W Elixirze istnieje kilka różnych struktur, które można zbiorczo nazwać instrukcjami warunkowymi. Różnią się one składnią i możliwościami, ale docelowo są to różne odmiany dobrze znanego if. Sam if istrnije w Elixirze i służy do zapisu pojedynczego warunku. Jego negacją jest unless. Nie interesują nas one, bo nie mają tu zastosowania. Kolejną instrukcją jest case. Dopasowuje on argument, do kolejnych wzorców. Możemy wykorzystywać strażników do opisania wzorca. Jest to chyba najpowszechniejsza instrukcja warunkowa. Rzecz w tym, że pracuje ona na dopasowaniu, a nie na warunku więc nie zawsze możemy ją wykorzystać. Mówiąc prościej jest ona odpowiednikiem javowego switch (upraszczająć), a my chcemy mieć coś w rodzaju if else if i pracować na funkcjach zwracających wartśc logiczną. Do tego właśnie służy cond. Wykona on kolejne funkcje do momentu aż nie otrzyma wartości true. Czasami może oznaczać to spadek wydajności, ale w naszym przypadku nie będzie to aż tak istotne.

Kod po zmianach będzie więc wyglądać następujaco:

Listing 5. Zmiana case na cond

defp tokens([h | t], accumulator, buffer, :common) do
  cond do
    '(' === [h]-> tokens(t, [:open_bracket] ++ read_buffer(buffer) ++ accumulator, [])
    ')' === [h]-> tokens(t, [:close_bracket] ++ read_buffer(buffer) ++ accumulator, [])
    ' ' === [h]-> tokens(t, read_buffer(buffer) ++ accumulator, [])
    '"' === [h]-> tokens(t, read_buffer(buffer) ++ accumulator, [], :text)
    true -> tokens(t, accumulator, [h] ++ buffer)
  end
end

defp tokens([h | t], accumulator, buffer, :text) do
  cond do
    '"' === [h] -> tokens(t, read_buffer(buffer) ++ accumulator, [], :common)
    true -> tokens(t, accumulator, [h] ++ buffer, :text)
  end
end

Na tym kończą sie proste refaktoryzacje, które nie dodawały nowych elementów do kodu. Jeden z użytkowników 4programmers, WeiXiao, stwierdził w komentarzu na mikro, że kod jest zwięzły. Czas go trochę rozsmarować i dodać struktury, które będą przechowywać nam więcej informacji.

Token jako struktura

Do tej pory w implementacji Jarka i Wiktora poszczególne tokeny były reprezentowane przez konkretne obiekty. Moja implementacja była pozbawiona tego elementu, ponieważ w znaczny sposób upraszczało to pisanie posta. Ot lenistwo… Teraz jednak wprowadzę odpowiednią strukturę, która będzie przechowywać dane.

Struktura Token

Elixir nie ma klas. Jest funkcyjny więc ich nie potrzebuje. W zamian ma jednak struktury, które pozwalają na operowanie nazwanymi składowymi mapy. Efektywnie jest to mapa doposażona w kilka udogodnień jak np. ograniczenie nazw kluczy do pewnego zbioru. Struktury definiuje się w modułach dzięki czemu nadal możemy w ramach tej samej przestrzeni nazw tworzyć funkcje. Zanim jednak utworzymy jakieś funkcje stwórzmy strukturę.

Listing 6. Moduł Ego.Token

defmodule Ego.Token do
  @enforce_keys  [:kind, :value]
  defstruct [:kind, :value]
end

Na razie nie potrzeba nam nic więcej. Javowy enum Kind zastąpimy atomami i być może walidacją. Jedna uwaga. Atrybut modułu @enforce_keys pozwala na określenie obowiązkowych pól w strukturze. Bez niego jeżeli stworzymy strukturę, to nieprzypisane pola będą nil.

Refaktoryzacja

Czas na wprowadzenie nowego formatu tokenów do kodu. Zaczniemy od przepisania testów, tak by obsługiwały nowy format.

Listing 7. Poprawione testy

defmodule Ego.LexerTest do
  use ExUnit.Case
  import Assertions
  alias Ego.Lexer
  alias Ego.Token

  @moduletag :capture_log

  doctest Lexer

  test "()" do
    result = Lexer.tokenize("()")

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end

  test "( )" do
    result = Lexer.tokenize("( )")

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end

  test "(Print Hello)" do
    result = Lexer.tokenize("(Print Hello)")

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :atom, value: 'Print'},
      %Token{kind: :atom, value: 'Hello'},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end

  test "(Print \"Hello World\")" do
    result = Lexer.tokenize("(Print \"Hello World\")")

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :atom, value: 'Print'},
      %Token{kind: :string, value: 'Hello World'},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end

  test "(Print \"Hello ( ) World\")" do
    result = Lexer.tokenize("(Print \"Hello ( ) World\")")

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :atom, value: 'Print'},
      %Token{kind: :string, value: 'Hello ( ) World'},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end

  test "(Print \"Hello (\n) World\")" do
    result =
      Lexer.tokenize("""
      (Print \"Hello (
      ) World\")
      """)

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :atom, value: 'Print'},
      %Token{kind: :string, value: 'Hello (\n) World'},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end

  test "(\u0061\u0301)" do
    result = Lexer.tokenize("(\u0061\u0301)")

    assert_lists_equal(result, [
      %Token{kind: :open_bracket, value: '('},
      %Token{kind: :atom, value: '\u0061\u0301'},
      %Token{kind: :close_bracket, value: ')'},
      %Token{kind: :eof, value: ''}
    ])
  end
end

Oczywiście wszystko będzie od razu czerwone, bo zmiana jest bardzo głęboka. Poprawny więc kod. Będziemy to robić po kawałku, ponieważ zmian jest dużo i warto je omówić.

Na pierwszy ogień idzie przywrócenie do życia testów związanych z obsługą nawiasów oraz znaku końca pliku. To są bardzo proste zmiany, które szybko rozwiązują nam problem.

Listing 8. Podstawowe elmenty

defmodule Ego.Lexer do

  alias Ego.Token

  def tokenize(program) when is_binary(program) do
    program
    |> String.to_charlist()
    |> tokens
    |> Enum.reverse()
  end

  defp tokens(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp tokens([], accumulator, _, _), do: [%Token{kind: :eof, value: ''}] ++ accumulator

  defp tokens([h | t], accumulator, buffer, :common) do
    cond do
      '(' === [h] -> tokens(t, [%Token{kind: :open_bracket, value: '('}] ++ read_buffer(buffer) ++ accumulator, [])
      ')' === [h] -> tokens(t, [%Token{kind: :close_bracket, value: ')'}] ++ read_buffer(buffer) ++ accumulator, [])
      ' ' === [h] -> tokens(t, read_buffer(buffer) ++ accumulator, [])
      '"' === [h] -> tokens(t, read_buffer(buffer) ++ accumulator, [], :text)
      true -> tokens(t, accumulator, [h] ++ buffer)
    end
  end

  defp tokens([h | t], accumulator, buffer, :text) do
    cond do
      '"' === [h] -> tokens(t, read_buffer(buffer) ++ accumulator, [], :common)
      true -> tokens(t, accumulator, [h] ++ buffer, :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.reverse() |> List.to_string() |> String.to_atom()]
  end
end

Kolejny krok to poprawki w funkcji read_buffer, tak by zamiast ciągu znaków emitowała odpowiedni token.

Listing 9. Obsługa atomów i zrzut bufora

defp read_buffer([]), do: []

defp read_buffer(buffer) do
  value = buffer |> Enum.reverse()
  [%Token{kind: :atom, value: value}]
end

No i nie działa… Dotychczas wykorzystywaliśmy tę funkcję do produkcji elixirowych atomów. To było OK, bo implementacja była prosta, ale z drugiej strony ukrywaliśmy w ten sposób klasę atomów, które reprezentują ciąg znaków. Wszystko było atomem! Teraz mamy kilka różnych klas do których przyporządkowujemy byty, więc nasza funkcja pełniąca rolę emitera musi to jakoś obsługiwać. Na całe szczęście jest to bardzo prosta zmiana, ponieważ potrafimy opisać z jaką klasą mamy do czynienia. Gdzie? Mówi nam o tym parametr mode funkcji tokens. W efekcie kod będzie wyglądał w następujący sposób:

Listing 10. Działający kod

defmodule Ego.Lexer do

  alias Ego.Token

  def tokenize(program) when is_binary(program) do
    program
    |> String.to_charlist()
    |> tokens
    |> Enum.reverse()
  end

  defp tokens(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp tokens([], accumulator, _, _), do: [%Token{kind: :eof, value: ''}] ++ accumulator

  defp tokens([h | t], accumulator, buffer, :common) do
    cond do
      '(' === [h] -> tokens(t, [%Token{kind: :open_bracket, value: '('}] ++ read_buffer(buffer, :atom) ++ accumulator, [])
      ')' === [h] -> tokens(t, [%Token{kind: :close_bracket, value: ')'}] ++ read_buffer(buffer, :atom) ++ accumulator, [])
      ' ' === [h] -> tokens(t, read_buffer(buffer, :atom) ++ accumulator, [])
      '"' === [h] -> tokens(t, read_buffer(buffer, :atom) ++ accumulator, [], :text)
      true -> tokens(t, accumulator, [h] ++ buffer)
    end
  end

  defp tokens([h | t], accumulator, buffer, :text) do
    cond do
      '"' === [h] -> tokens(t, read_buffer(buffer, :string) ++ accumulator, [], :common)
      true -> tokens(t, accumulator, [h] ++ buffer, :text)
    end
  end

  defp read_buffer([], _), do: []

  defp read_buffer(buffer, kind) do
    value = buffer |> Enum.reverse()
    [%Token{kind: kind, value: value}]
  end
end

Jest prawie dobrze. Ostatnią refaktoryzacją, którą można zrobić, to wyciągnięcie funkcji fabrykujących do modułu Ego.Token. Tym samym zmieni się też funkcja read_buffer, która będzie tak jak dotychczas zwracać jedynie wartość bufora. Niestety od razu widać pewien problem. Pierwotnie read_buffer zwracało wartość opakowaną w listę albo pustą listę. Dzięki temu łatwo było nam dodawać listy. Teraz zwracamy wartość lub pustą listę (sic!). Zatem możemy doprowadzić do stuacji, gdzie zaczniemy emitować tokeny, które będą reprezentować puste listy (bufor był pusty) i nie będą EOF. Dotyczy to tokenów reprezentujących atom i ciąg znaków. Ale i na to będzie prosta rada. Widać ją w funkcji tokenize. Wypłaszczamy listę tokenów. W efekcie nasz końcowy kod wygląda w następujący sposób:

Listing 11. Ego.Token po zmianach

defmodule Ego.Token do
  @enforce_keys [:kind, :value]
  defstruct [:kind, :value]

  def open_bracket(), do: %Ego.Token{kind: :open_bracket, value: '('}
  def close_bracket(), do: %Ego.Token{kind: :close_bracket, value: ')'}
  def eof(), do: %Ego.Token{kind: :eof, value: ''}
  def atom([]), do: []
  def atom(value), do: %Ego.Token{kind: :atom, value: value}
  def string([]), do: []
  def string(value), do: %Ego.Token{kind: :string, value: value}

end

Listing 11. Ego.Lexer po zmianach

defmodule Ego.Lexer do
  import Ego.Token

  def tokenize(program) when is_binary(program) do
    program
    |> String.to_charlist()
    |> tokens
    |> Enum.reverse()
    |> List.flatten()
  end

  defp tokens(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp tokens([], accumulator, _, _), do: [eof()] ++ accumulator

  defp tokens([h | t], accumulator, buffer, :common) do
    cond do
      '(' === [h] -> tokens(t, [open_bracket()] ++ [atom(read_buffer(buffer))] ++ accumulator, [])
      ')' === [h] -> tokens(t, [close_bracket()] ++ [atom(read_buffer(buffer))] ++ accumulator, [])
      ' ' === [h] -> tokens(t, [atom(read_buffer(buffer))] ++ accumulator, [])
      '"' === [h] -> tokens(t, [atom(read_buffer(buffer))] ++ accumulator, [], :text)
      true -> tokens(t, accumulator, [h] ++ buffer)
    end
  end

  defp tokens([h | t], accumulator, buffer, :text) do
    cond do
      '"' === [h] -> tokens(t, [string(read_buffer(buffer))] ++ accumulator, [], :common)
      true -> tokens(t, accumulator, [h] ++ buffer, :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    buffer |> Enum.reverse()
  end
end

I gotowe.

Podsumowanie

Refaktoryzacje były proste i skomplikowane. Dały one elastyczniejszy kod, który będzie można znacznie łatwiej rozszerzać. Taka mała ciekawostka. Wczorajsza wersja miała 37 linii kodu. Dzisiejsza ma tyle samo. Przy czym nie pociągnąłem jej elixirowrym formaterem w domyślnych ustawieniach. Zmieniłem długość linii na 120 znaków.

Kolejne kroki to uzupełnienie mojego kodu o to, co Jarek i Wiktor dodali poza kamerą. Będzie wsparcie dla liczb i komentarzy. Kod jak zwykle na githubie.

Lexer Ego w Elixirze

Jarek Pałka i Wiktor Sztajerowski zaczęli z nudów cykl wykładów o tym jak stworzyć język programowania. Na tapetę trafiło jarkowe Ego, czyli język programowania przeznaczony do ćwiczenia programowania w dziwnych paradygmatach. Pierwszy wykład poświęcili na napisanie prostego lekseraW języka. Całość do obejrzenia poniżej:

A ja postanowiłem, że pobawię się ich zabawką w trochę inny sposób. Napiszę w elixirze lekser, który będzie umiał dokonać analizy leksykalnej Ego z takim samym wynikiem, co oryginalne rozwiązanie. Zaczynamy!

Konfiguracja

By rozpocząć naszą zabawę, należy na początek utworzyć projekt za pomocą mixa. Następnie dla własnej wygody dodałem jedną zależność – bibliotekę assertions, która pozwoli w prosty sposób porównywać listy w testach. W efekcie mój mix.exs wygląda w następujący sposób:

Listing 1. Konfiguracja projektu

defmodule Ego.MixProject do
  use Mix.Project

  def project do
    [
      app: :ego,
      version: "0.1.0",
      elixir: "~> 1.10",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  def application do
    [extra_applications: [:logger]]
  end

  defp deps do
    [{:assertions, "~> 0.10", only: :test}]
  end
end

Mając taką konfigurację, możemy przystąpić do pisania!

Prosty program – ()

Podobnie jak Jarek i Wiktor rozpocznę od najprostszego programu. W Ego będzie to program złożony z nawiasu otwierającego i zamykającego:

Listing 2. Prosty program w Ego

()

Program ten po przetworzeniu przez lekser powinien wygenerować listę, na której znajdują się trzy tokeny. Pierwszy reprezentuje nawias otwierający, drugi nawias zamykający, a trzeci jest znacznikiem końca pliku. Test, który opisuje to zachowanie, wygląda w następujacy sposób:

Listing 3. Test programu ()

test "()" do
  result = Lexer.tokenize("()")
  assert_lists_equal(result, [:open_bracket, :close_bracket, :eof])
end

Uwaga! Tutaj troszeczkę oszukuję, bo używam elixirowych atomów wprost. Wynika to z chęci utrzymania kodu we w miarę zwięzłej postaci. Oczywiście można ten kod uszczegółowić w podobny sposób jak na filmie, ale ciężko by się go czytało pod postacią blogonotki.

By wypełnić ten test, musimy oczywiście napisać kod. Na początek będzie on bardzo prosty:

Listing 4. Pierwsze elementy leksera

defmodule Ego.Lexer do
  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> token
  end

  defp token(charlist, accumulator \\ [])
  defp token([], accumulator), do: accumulator ++ [:eof]

  defp token(charlist, accumulator) do
    [h | t] = charlist

    case h do
      "(" -> token(t, accumulator ++ [:open_bracket])
      ")" -> token(t, accumulator ++ [:close_bracket])
      _ -> token(t, accumulator)
    end
  end
end

Co my tu mamy? W linii 2 strażnik sprawdza, czy mamy do czynienia z ciągiem znaków. Uroki słabego typowania są urocze… Następnie w liniach 15 i 16 emitujemy odpowiednie tokeny, a w linii 17 ignorujemy inne znaki. Linia 8 jest oczywista, bo jeżeli lista znaków do przetworzenia jest pusta, to znaczy, że osiągnęliśmy koniec pliku i trzeba wyemitować EOF.

Wariacja i kolejny test – ( )

W tym miejscu pójdę trochę inną drogą nich chłopaki, bo dodam obsługę białych znaków.

Listing 5. Test programu ( )

test "( )" do
  result = Lexer.tokenize("( )")
  assert_lists_equal(result, [:open_bracket, :close_bracket, :eof])
end

Inaczej mówiąc, jeżeli trafię na spację, to mogę ją olać. Ten test już przejdzie, bo warunek z 17 linijki nie jest po nic. Jednak delikatnie zmienię kod:

Listing 6. Małe zmiany – uwzględniamy spację

defmodule Ego.Lexer do
  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> token
  end

  defp token(charlist, accumulator \\ [])
  defp token([], accumulator), do: accumulator ++ [:eof]

  defp token(charlist, accumulator) do
    [h | t] = charlist

    case h do
      "(" -> token(t, accumulator ++ [:open_bracket])
      ")" -> token(t, accumulator ++ [:close_bracket])
      " " -> token(t, accumulator)
      _ -> token(t, accumulator)
    end
  end
end

Nic się praktycznie nie zmieniło, ale to tylko pozory. W ten sposób mamy już obsługiwane (prawie) wszystkie symbole jednoznakowe.

Symbole wieloznakowe

Kolejnym krokiem jest obsługa symboli wieloznakowych. Zaczniemy tradycyjnie od napisania testu:

Listing 7. Test programu (Print Hello)

test "(Print Hello)" do
  result = Lexer.tokenize("(Print Hello)")
  assert_lists_equal(result, [:open_bracket, :Print, :Hello, :close_bracket, :eof])
end

Sprawa wydaje się być bardzo prosta. Musimy dodać do naszego leksera bufor, w którym będziemy zbierać kolejne znaki i w momencie gdy trafimy na znak z pierwszej grupy, to wyemitujemy odpowiedni token.

Listing 8. Obsługa symboli wieloznakowych

defmodule Ego.Lexer do
  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> token
  end

  defp token(charlist, accumulator \\ [], buffer \\ [])
  defp token([], accumulator, buffer), do: accumulator ++ [:eof]

  defp token(charlist, accumulator, buffer) do
    [h | t] = charlist

    case h do
      "(" -> token(t, accumulator ++ read_buffer(buffer) ++ [:open_bracket], [])
      ")" -> token(t, accumulator ++ read_buffer(buffer) ++ [:close_bracket], [])
      " " -> token(t, accumulator ++ read_buffer(buffer), [])
      _ -> token(t, accumulator, buffer ++ [h])
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.join("") |> String.to_atom()]
  end
end

Tu się robi ciekawie. Po pierwsze przyjmujemy, że trafiając na któryś ze znaków „specjalnych”, opróżniamy bufor i emitujemy token. Po drugie bufor może być pusty, a zatem nie możemy emitować pustego tokenu. W Elixirze :”” jest poprawnym atomem, więc trzeba tu trochę pohakować. W ten sposób mamy już prosty lekser, który możemy zacząć komplikować. Wzorem Jarka i Wiktora dodajmy obsługę ciągów znaków.

Ciągi znaków

Wspomniałem, że mamy już obsługę prawie wszystkich symboli jednoznakowych. Pozostał nam jeszcze jeden symbol – cudzysłów. Jego obsługa skomplikuje nam lekser, ale też pozwoli na opisanie całej klasy problemów. Zacznijmy jednak od napisania testów:

Listing 9. Test programu (Print Hello)

test "(Print \"Hello World\")" do
  result = Lexer.tokenize("(Print \"Hello World\")")
  assert_lists_equal(result, [:open_bracket, :Print, :"Hello World", :close_bracket, :eof])
end

test "(Print \"Hello ( ) World\")" do
  result = Lexer.tokenize("(Print \"Hello ( ) World\")")
  assert_lists_equal(result, [:open_bracket, :Print, :"Hello ( ) World", :close_bracket, :eof])
end

test "(Print \"Hello (\n) World\")" do
  result = Lexer.tokenize("""
rint \"Hello (
  ) World\")
  """)
  assert_lists_equal(result, [:open_bracket, :Print, :"Hello (\n) World", :close_bracket, :eof])
end

Te trzy małe testy pokrywają użycie znaków (,), spacji i znaku nowej linii w ciągu znaków. Jednocześnie wymuszają na nas duże zmiany w implementacji leksera. Od tego momentu lekser będzie musiał działać w dwóch trybach. Pierwszy zwykły tryb, to wszystko to co dotychczas napisaliśmy. W momencie gdy trafi na znak , lekser przejdzie w tryb procesowania tekstu, którego nie opuści do momentu, gdy po raz kolejny trafi na znak . Proste, prawda?

Listing 10. Obsługa ciągów znaków

defmodule Ego.Lexer do
  def tokenize(program) when is_binary(program) do
    program
    |> String.split("", trim: true)
    |> token
  end

  defp token(charlist, accumulator \\ [], buffer \\ [], mode \\ :common)
  defp token([], accumulator, buffer, _), do: accumulator ++ [:eof]

  defp token(charlist, accumulator, buffer, :common) do
    [h | t] = charlist

    case h do
      "(" -> token(t, accumulator ++ read_buffer(buffer) ++ [:open_bracket], [])
      ")" -> token(t, accumulator ++ read_buffer(buffer) ++ [:close_bracket], [])
      " " -> token(t, accumulator ++ read_buffer(buffer), [])
      "\"" -> token(t, accumulator ++ read_buffer(buffer), [], :text)
      _ -> token(t, accumulator, buffer ++ [h])
    end
  end

  defp token(charlist, accumulator, buffer, :text) do
    [h | t] = charlist

    case h do
      "\"" -> token(t, accumulator ++ read_buffer(buffer), [], :common)
      _ -> token(t, accumulator, buffer ++ [h], :text)
    end
  end

  defp read_buffer([]), do: []

  defp read_buffer(buffer) do
    [buffer |> Enum.join("") |> String.to_atom()]
  end
end

Jest prawie dobrze. Czwarty parametr funkcji steruje nam trybem pracy. W podobny sposób możemy implementować kolejne tryby np. tryb komentarza. To, co jest jednak nie do końca dobre, to wzrost liczby parametrów. Z drugiej strony możemy wprowadzić jakąś strukturę, która będzie opisywać aktualny tryb pracy i na jej podstawie uruchamiać różne strategie obsługi znaków.

Podsumowanie

Na koniec kilka słów podsumowania. Po pierwsze kod jest w miarę zwięzły. Po drugie trochę inaczej obsługuję błędy, ale to już jest kwestia języka i tego w jaki sposób chcemy informować użytkownika, że napisał coś głupiego. Po trzecie cała ta zabawa zapoczątkowana przez Jarka i Wiktora jest tylko zabawą i nie ma na celu stworzenia jakieŋ poważnego produktu.
Dla mnie osobiście jest to ciekawe doświadczenie. Nigdy nie miałem okazji napisania kompilatora, a ten cykl pozwoli mi na podjęcie wyzwania. Kolejne wersje kodu będą dostępne w repozytorium

O stringów w postgresie porównywaniu

Pracując na jednym z projektów, trafiliśmy na „ciekawe inaczej” wymaganie. Chodziło o to, by dodać wyszukiwanie po adresie email. Prosta sprawa, ale nie do końca.

Gmail a wielkość znaków

W adresie e-mail co do zasady ważna jest wielkość znaków. No, chyba że jesteś gmailem, to wtedy nie. Gmail jest usługą, która próbuje być cwana. Po pierwsze gmail nie rozróżnia wielkości znaków. Po drugie gmail nie uznaje kropek w adresie. Ma to pewne konsekwencje dla osób, które są przywiązane do kropek.
Po trzecie, większość naszych klientów wykorzystuje gmaila jako swój główny adres w naszym systemie lub ichni system korpo-poczty wykorzystuje gmaila.
Tu pojawił się problem.

Zrobisz selecta i będzie dobrze

Najprostszym rozwiązaniem naszego zadania byłoby stworzenie kodu, który uruchamia pod spodem zapytanie jak to tutaj:

Listing 1. Rozwiązanie naiwne

Select * from accounts where email_address='admin@example.com'

Przy czym problem z tym zapytaniem leży w sposobie porównywania wartości. Dobrze ilustruje to poniższy test:

Listing 2. Test rozwiązania naiwnego

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class AccountRepositoryTest {

	@Autowired
	AccountRepository repository;

	@Test
	@Order(1)
	void readAndWriteSameCase() {
		Account s = new Account("admin@example.com");
		repository.save(s);
		assertThat(repository.findById("admin@example.com")).isNotEmpty();
	}
	@Test
	@Order(3)
	void readAndWriteNotSameCase() {
		assertThat(repository.findById("Admin@example.com")).isNotEmpty();
	}

}

Pierwszy test, readAndWriteSameCase, zakończy się sukcesem. Drugi będzie czerwony. Dlaczego?

Postgres jest OK

W przeciwieństwie do MySQLa Postgres prawidłowo rozróżnia wielkość liter. Tym samym proste porównanie dwóch wartości typu VARCHAR będzie zwracało false jeżeli wartości te różnią się wielkością liter właśnie.
Zidentyfikowaliśmy więc nasz „problem”, którego źródło leży, w niezrozumieniu tego jak działa baza danych, której używamy. Należy zatem pomyśleć o możliwych rozwiązaniach.

Rozwiązania i „rozwiazania”

Poniżej przedstawię kilka rozwiązań, które mają różny poziom dziwności, poręczności i słuszności.

Po stronie Javy

Pierwszym, bardzo naiwnym, ale też często spotykanym rozwiązaniem będzie umieszczenie „gdzieś w logice” wywołania toLowerCase. Nie jest to złe. Nie jest to też jakoś mądre. Jest za to bardzo naiwne i powinno być używane w ostateczności.

Czysty SQL – lower

Kolejnym rozwiązaniem jest takie przemodelowanie naszych zapytań, by używać funkcji lower z SQLa. Nasze przykładowe zapytanie mogłoby wtedy wyglądać tak:

Listing 3. Użycie lower

Select * from accounts where lower(email_address)=lower('admin@example.com')

Podobnie jak poprzednie, to rozwiązanie jest bardzo naiwne. Nie sprawdzi się w przypadku, gdy używamy JPA. Chyba że ręcznie ogarniemy każde zapytanie, gdzie potrzebujemy tego rodzaju porównań albo potrafimy posłużyć się hibernetową adnotacją @Formula. Co też nie do końca jest proste i ma swoje wady.

Czysty SQL – operator ~*

Postgres posiada operator ~*, który służy do porównania dwóch ciągów za pomocą wyrażeń regularnych POSIX. Po lewej stronie jest ciąg znaków, a po prawej wyrażenie:

Listing 4. Użycie ~

Select * from accounts where email_address ~* 'admin@example.com'

Operator ten ma kilka wersji:

  • ~ – Porównanie za pomocą wyrażenia regularnego, biorące pod uwagę wielkość znaków
  • ~* – Porównanie za pomocą wyrażenia regularnego, niebiorące pod uwagę wielkości znaków
  • !~ – Porównanie za pomocą wyrażenia regularnego, biorące pod uwagę wielkość znaków, zwraca true jeżeli nie ma dopasowania.
  • !~* – Porównanie za pomocą wyrażenia regularnego, niebiorące pod uwagę wielkości znaków, zwraca true jeżeli nie ma dopasowania.

I podobnie jak w poprzednim przypadku użycie tego operatora wymaga albo użycia SQL wszędzie gdzie to konieczne, albo kombinowania z adnotacjami.

Czysty SQL – rozszerzenie citext

Rozszerzenie to wprowadza do Postgresa typ citext, który zachowuje się jak text, ale ignoruje wielkość znaków. To rozwiązanie jest najmniej inwazyjne w kontekście kodu. Należy jedynie dołączyć do naszego projektu bibliotekę hibernate-types, w której znajduje się już gotowe wsparcie dla tego typu.

Podsumowanie

Opisany problem jest, paradoksalnie, nieoczywisty. We współczesnych systemach dokłada się dość szybko silniki wyszukiwania albo „hackuje” ten problem we wczesnym stadium życia projektu. W ten sposób powstają niezbyt zrozumiałe konstrukcje, których zadaniem jest zbędne weryfikowanie danych wejściowych. Co prawda nie trudno jest sobie wyobrazić tego typu problemy w systemach, gdzie ręcznie wprowadza się sygnatury dokumentów. Z drugiej strony jesteśmy przyzwyczajeni do pisania całej masy walidatorów, których jedynym zadaniem jest naprawianie błędów, wynikających z naszej niewiedzy. Pytanie czy to dobre podejście?

Hackerzy, oddajcie mi moje 400mln

W ostatnich dniach pojawiła się ciekawa informacja. Grupa czeskich aktywistów, w trakcie hackatonu, stworzyli prototyp elektronicznego systemu sprzedaży winiet. O samym projekcie możecie poczytać tutaj. W dużym skrócie z bodajże 18 elementów specyfikacji w czasie imprezy zaimplementowano 16, a te, których nie zaimplementowano to elementy powiązane z dostępem do informacji niejawnych. I można by w tym miejscu krzyknąć heap heap hura i podskakiwać z radości. W końcu „nasi” pokazali tym państwowym darmozjadom, że można lepiej wydać 400 mln koron. Można do tego dołożyć jeszcze, że dokopano Asseco, co jest rzeczą godną pochwały. Tyle, że nie do końca…

Rodzaje projektów i co z tego podziału wynika

Inżynieria oprogramowania jest dość ciekawą działką w kontekście relacji państwo-oprogramowanie, ale jeszcze ciekawszą działką jest światek starupowo-korporacyjny. Jedną z zasad w tym świecie jest ta, która mówi, że oprogramowania nie powinno się pisać, a kupować.

Kupuj, nie pisz

Nie mówi tylko o jakie oprogramowanie chodzi. Państwo może spokojnie kupować oprogramowanie użytkowe. Wręcz powinno, bo ciężko mi sobie wyobrazić, by ktoś chciał rozwijać swój OS albo pakiet biurowy. Nawet Korea Północna nie stworzyła swojego państwowego OSa od zera, a jedynie zbudowała własne distro Debiana.

Ale przecież, można wziąć darmowego Linuxa?

Można. Można też wziąć Libre Office. Tyle tylko, że później takiego darmowego Linuxa trzeba utrzymywać.

To się biurwy nauczą!

Tak. Nauczą się dokładnie w tym samym momencie, gdy ty nauczysz się ich pracy. Coś za coś. Szeroko rozumiana branżunia ma niestety problem z postrzeganiem pracy z komputerem. Dla nas wszystko jest proste i można to szybko ogarnąć. Nawet skomplikowane koncepcje „same wchodzą”. Mamy wprawę w modelowaniu procesów i tłumaczeniu ich na programy. To jednak za mało, bo nie mamy głębokiej wiedzy, która idzie wraz z tymi procesami. Szczególnie jeżeli procesy te dotyczą np. procedury administracyjnej. Podsumowując, niech biurwy potrafią sprawnie biurwować, a nie klepać skrypty w bashu.

Kupujesz sprzęt i wsparcie

W idealnym przypadku państwo powinno kupować sprzęt oraz wsparcie do oprogramowania o otwartym kodzie. Do tego dochodzą szkolenia, które powinny jednak być specyficznie zaplanowane. Szkolenia powinny być realizowane wewnątrz organizacji przez osoby zatrudnione w tej organizacji. Niektóre z tych osób powinny szkolić się na zewnątrz (u twórców), a swoją wiedzę przekazywać innym szkoleniowcom. Dzięki temu państwo zachowuje znaczną niezależność od zewnętrznych szkoleń, a jednocześnie może wpływać na program szkolenia, tak by dostosować je do potrzeb pracowników.

Rozszerzaj to co kupiłeś

Drugim podejściem jest zakup oprogramowania, które jest najbliżej wymagań i jego modyfikacja. Część procesów i potrzeb, jakie ma państwo, można ogarnąć za pomocą modyfikacji dostępnego oprogramowania. Wynika to z faktu, że niektóre procesy realizowane przez państwo nie różnią się od typowych procesów biznesowych. Publikowanie dokumentów w BIPie, choć musi spełniać pewne reguły, nie jest niczym innym jak tworzenie treści za pomocą CMSa. Serio. To, co zrobili Czesi, to przecież sklep z winietami postawiony na otwartym CMSie.

Pisz rzeczy specyficzne

Takich rzeczy jest zazwyczaj niewiele. Ich specyficzność nie leży w procesach lub wymaganiach jako takich, ale w sposobie obsługi, czy dodatkowych cechach. Co prawda rejestr PESEL nie odbiega od typowego rejestru kadrowego. Ba można powiedzieć, że jest znacznie prostszy. Jednak jego specyfika leży w informacjach, które przechowuje oraz w sposobie dostępu do nich.
Osobną grupę stanowią procesy objęte klauzulami poufności czy tajności. W ich przypadku to wystarczy, by wyłączyć je z analizy „czy jest coś, co można dostosować”. Przynajmniej na poziomie oficjalnego przetargu.

Urzędy nie powinny pisać oprogramowania

Urzędy, a nie państwo. Dlaczego? Cóż urzędy jako takie mają dwa piony. Pion merytoryczny, który zazwyczaj składał się z kompetentnych ludzi i pion polityczny, który składa się z ludzi „dających twarz” takim, a nie innym decyzjom. Piony te przenikają się w mniejszym bądź większym stopniu. Przy czym w ostatnich 15 latach to raczej pion polityczny wpycha swoich w pion merytoryczny, a nie na odwrót.

// offtopic

Jeżeli uważasz, że kompetentny urzędnik, to oksymoron, to masz bardzo prymitywne postrzeganie świata. Serio.

//koniec offtopa

Urzędy pisać oprogramowania nie powinny, ponieważ nie mają do tego kompetencji. Byłem, widziałem. Rolą urzędów powinno być specyfikowanie wymagań dla oprogramowania, którego nie będzie się kupować w wersji pudełkowej.

Co może zrobić państwo?

Państwo ma kilka dróg, którymi może realizować swoje potrzeby w zakresie informatyzacji.

Tylko kupować

Państwo może kupować oprogramowanie w ramach jakiejś procedury. Czasami oznacza to, że kupi gotowce w pudełkach (systemy operacyjne, pakiety biurowe). Czasami oznacza to, że kupi zmodyfikowane oprogramowanie otwarte. Czasami rzeczywiście kupi coś napisanego od zera.
Wadą tego rozwiązania jest praktyczne zamknięcie kodu i specyfikacji. Wiele lat walki twórców Janosika z ZUSem o otwarcie specyfikacji systemów ZUS doskonale to ilustruje. To nie jest dobra droga.

Kupować gotowce, rozwijać samodzielnie

Ten model jest w pewnym sensie przeciwieństwem poprzedniego. Zakupy ograniczamy do minimum. Kupujemy tylko to, co nie będzie modyfikowane. Systemy operacyjne, pakiety biurowe, oprogramowanie do obsługi systemu „numerkowego” czy kadrowego. Cała reszta prac jest przeniesiona do odpowiednich ośrodków akademickich. Państwo samodzielnie rozwija potrzebne oprogramowanie w swoich firmach. Tam też są prowadzone prace w zakresie R&D czy badań podstawowych.
Taki model jest możliwy i działał przez wiele lat w USA. Zaletą takiego podejścia jest zabezpieczenie państwa przed problemami dostawców. Całość oprogramowania, które wymaga jakiejś pracy, a nie jedynie instalacji z pudełka, jest własnością państwa. Źródła nie są może powszechnie dostępne, ale też nie muszą. Wadą jest duża bezwładność takiego systemu i koszty. Jeżeli chcemy być na bieżąco, to trzeba inwestować w badania. Te nie są tanie. W dodatku złe decyzje projektowe będą się ciągnąć przez dziesięciolecia.

Kupować gotowce i Otwarta specyfikacja

Poza wspomnianymi już kilkukrotnie pudełkami cała reszta oprogramowania nie jest rozwijana przez państwo. Zamiast tego państwo definiuje, ogłasza i utrzymuje specyfikacje. Utrzymuje też infrastrukturę potrzebną do testowania zgodności oraz certyfikuje producentów w procedurze administracyjnej.
Tu mała uwaga, procedura administracyjna ma to do siebie, że nie bada „racji” stron, a jedynie sprawdza, czy spełnione są wymagania. Spełniasz wymagania, oto twój certyfikat i nie ważne, że napisałeś to w php4.
Zaletą tego modelu jest odpowiednia separacja odpowiedzialności pomiędzy biznes-państwo, a dostawców. Pozwala to też na uniknięcie sytuacji, gdzie dostawca nie chce ujawnić specyfikacji. Ta jest zawsze jawna. Eliminujemy w ten sposób monopolistów.
Oczywiście model ten ma też wady. Oprogramowanie nadal trzeba kupować. Nadal trzeba płacić za jego utrzymanie i rozwój. Pozostaje też problem jakości specyfikacji i jej szczegółowości. Specyfikacje też będą się starzeć z punktu widzenia technologii, co oznacza konieczność ich aktualizacji.

Hackatony nie są rozwiązaniem

Problem to sposób, w jaki państwo powinno zamawiać oprogramowanie. Na hackatonach można stworzyć bardzo dużo bardzo fajnego oprogramowania. Można pokazać, że taka czy inna koncepcja daje ciekawe możliwości albo zmaterializować dość mglisty pomysł. Takie imprezy to świetne miejsce by państwo i specjaliści IT wymieniali się pomysłami, ideami, wskazywali sobie wzajemnie problemy. To w takim miejscu powinna siedzieć fundacja Panoptykon, by móc mówić o problemach ochrony prywatności.
Hackatony jednak nie są miejscami, gdzie stworzy się konkretne rozwiązanie. Po imprezie w Czechach co prawda anulowano kontrakt, a władze chcą wdrażać rezultaty pracy uczestników. Jednak nie wyobrażam sobie, żeby teraz te czterdzieści kilka osób z dnia na dzień rzuciło pracę i ruszyło po kraju, by szkolić użytkowników. Albo, żeby rozdysponowało pomiędzy siebie kilka telefonów i ustaliło grafik dyżurów nocnych i weekndowych, bo system wymaga wsparcia 24/7. I co najważniejsze uczestnicy nie zrobią tego za darmo.
Media podając kwotę przetargu, rzucają konkretną liczbę. Nigdy nie starają się rozbić jej na poszczególne pozycje. Przemilczają też, ile trwa utrzymanie, czy cena obejmuje szkolenia i wdrożenie użytkowników. To wszystko kosztuje co najmniej tyle samo co „czyste programowanie”.

I o ile programowanie na hackatonie jest dobrą zabawą, to nikt nie będzie w tej formie utrzymywać oprogramowania.

Legendarny Osobomiesiąc – książka nie dla menadżerów

Okładka Legendarny Osobomiesiąc
Tytuł: Legendarny Osobomiesiąc. Opowieści o inżynierii oprogramowania. Wydanie II
Autor: Frederick P. Brooks Jr.
Rok: 2019 (1995 EN)
ISBN: 978-83-283-5090-8

Zgodnie z obietnicą krótka recenzja jednej z najciekawszych książek o zarządzaniu projektami IT, jaka kiedykolwiek powstała.

Szczypta historii

Autorem książki jest Frederick BrooksW, który był menadżerem projektu OS/360W z przyległościami. Zatem mamy tu do czynienia z człowiekiem, który walczył w okopach IT w czasach, gdy duża część z nas nie za bardzo miała jeszcze możliwość zaistnieć w jakiejkolwiek formie biologicznej. Pierwsze wydanie książki było w 1975, a drugie w 1995. I właśnie z tym wydaniem „na dwudziestolecie” mamy do czynienia. Polskie tłumaczenie ukazało się w 2019 roku.
Książka jest stara i ma to przeróżne konsekwencje. Siadając do lektury, należy o tym pamiętać.

Konsekwencja pierwsza – język

Legendarny Osobomiesiąc jest napisany w specyficzny sposób. Stare książki już tak mają, że język, którym posługują się autorzy, jest trudniejszy w odbiorze. Jeżeli dodamy do tego swoistą manierę używania słów dłuższych niż konieczne oraz czasami osobliwą składnię, to efekt końcowy jest „skomplikowany”.
Tak też jest tutaj. Książka jest napisana w bardzo „barokowym stylu”, który odszedł do lamusa wraz z nastaniem blogów i popularyzacją krótszych form cyfrowych.

// offtopic

Szczytem językowych wygibasów tamtej epoki jest Domain Driven Design Erica Evansa z WSZECHOBECNYM nadużywaniem capslocka i dziwnych elementów składu.

// koniec offtopa

Dodatkowo wiele z poruszanych w niej problemów nie istnieje już w zbiorowej świadomości IT. Tym samym niektóre elementy zdają się dziwne i bezsensowne.
Język książki determinuje też sposób jej tłumaczenia. Niewdzięczna to praca, bo z jednej strony należy zachować oryginalny charakter, a z drugiej aż prosi się o uwspółcześnienie. Jednakże tłumacz ciała nie dał i jego pracę oceniam na całkiem, całkiem.

Konsekwencja druga – kontekst

Jeżeli damy tę książkę osobie, która nie ma pojęcia, jak wygląda współczesny cykl rozwoju oprogramowania, to bardzo szybko okaże się, że mamy poważny problem. Pojawią się pytania w tylu „ile czasu zajmuje ci debuggowanie”, „czy mam gotową osobną stację roboczą do debuggowania”, „ile linii kodu dostarczamy w ciągu roku”. Ciekawe, prawda? Brooks pisał książkę, gdy Intel pokazał światu procesor 8080W, czyli gdy wybuchła rewolucja mikrokomputerów. Drugie wydanie pojawiło się w roku premiery Windowsa’95. Zatem autor ma bardzo ograniczony zasób wiedzy w porównaniu do nas. Dzisiaj.
Oczywiście można podjąć próbę translacji pewnych aspektów inżynierii oprogramowania z dawnych czasów na nam współczesne. Jest to jednak trud nie tyle daremny ile bezsensowny.
Drugie wydanie zostało co prawda uzupełnione o 20 lat doświadczeń, a co za tym idzie m.in. dyskusję nad klasycznym już dzisiaj artykułem Brooksa pt. „There is no silver bullet”. Jednak nadal istnieje 25-letnia wyrwa w wiedzy pomiędzy współczesnością, a czasami drugiego wydania.

Konsekwencja trzecia – wnioski autora

To samo dotyczy wniosków, które przedstawia autor. Z dzisiejszej perspektywy wiemy, że wiele z zaproponowanych rozwiązań nie sprawdziło się, albo spowodowało poważne problemy. Mam tutaj na myśli przede wszystkim wprowadzenie „wszechwiedzącego” i niekodującego architekta, próby formalizacji na poziomie globalnym (dla danej organizacji), czy też założenie, że zespoły programistów nie są w stanie sprawnie się komunikować.
Oczywiście Brooks miał rację w kilku kwestiach. Programowanie wizualne nie zawojowało rynku. Podobnie jak automatyczne tworzenie kodu na podstawie specyfikacji.
Nadal kluczową rolę w procesie tworzenia oprogramowania odgrywa przygotowanie specyfikacji. Nadal mamy problemy z sensownym testowaniem naszych programów, choć proces debuggowania zastąpiliśmy metodykami w rodzaju TDD. Nadal też nie mamy idealnej metody komunikacji ani niczego co pozwoliłoby na precyzyjne planowanie czasu pracy nad zadaniami.
W końcu w książce tej mamy sformułowane Prawo BrooksaW wraz z późniejszą jego dyskusją i weryfikacją empiryczną, która jest wspomniana w drugim wydaniu. Jest to też największa wartość tej książki, która nie ulegnie dezaktualizacji.

Dla kogo ta książka?

Na pewno nie dla młodych menadżerów. Z racji wieku nie będzie to książka, która cokolwiek im da. Jednocześnie każdy doświadczony menadżer powinien ją przeczytać. Pozwoli mu to zrozumieć, w jaki sposób jego decyzje mogą wpływać na zespół. Jaka jest ich rola w zespole. Dlaczego pojawiają się problemy?
Czy jednak jest to książka dla programistów?

Książka historyczna

Chcąc zrozumieć współczesność, należy przeanalizować przyczyny. Te mają swoje źródła w historii. Książka Brooksa jest pod tym względem idealna. Jako że jej autor uczestniczył w jednym z najbardziej skomplikowanych projektów w historii informatyki, a następnie spisał swoje przemyślenia, to wnioski z tamtych wydarzeń stały się podstawą do analizy i opracowania metodyk wytwarzania oprogramowania. Metodyki te stały się standardem na kolejnych 30 lat. W dodatku wrosły one w branżę i nadal są ważnym jej elementem. Szczególnie że pokolenie starszych menadżerów uczyło się tworzenia oprogramowania m.in. na tej książce.
Dlatego jest to lektura obowiązkowa dla wszystkich programistów, którzy nie chcą ślepo przeć do przodu, nie zważając na otoczenie. W rozdziałach dodanych w drugim wydaniu Brooks z entuzjazmem odnosi się do programowania obiektowego. Entuzjazm ten podobny jest do tego, jaki towarzyszy obecnie programowaniu funkcyjnemu. Czy za 25 lat nie będziemy mieli podobnego „moralniaka”, mówiąc o FP?

Warto.

Toss a coin to your Sapek

Poprzedni wpis był o finale GoT. Teraz będzie o netflixowej ekranizacji Wiedźmina. Za chwilę Karolina Korwin-Piotrowska zacznie mnie cytować.

Wiedźmina historia krótka i moja

Z opowiadaniami po raz pierwszy zetknąłem się gdzieś w 2000 roku, gdy w Lesku kupiłem „Ostatnie Życzenie”, wydane w ramach cyklu Polityka poleca dobrą literaturę.
Mój brat wybrał sobie wtedy „Miecz przeznaczenia”, a do końca wyjazdu kupiliśmy jeszcze „Krew elfów” i „Czas pogardy”. Do końca wakacji przeczytałem całą sagę, co dość skutecznie wydrenowało mój skromny budżet.
Później był film, Gra Wyobraźni i własny wiedźmiński medalion z edycji kolekcjonerskiej pierwszej części gry (oraz wesele przyjaciela okupione piciem z twórcami + wiedziałem co będzie przed premierą). Do tego jeszcze zaraz wrócimy. Teraz czas na recenzję tego nieszczęsnego Wiedźmina od Netflixa.

O czym jest Wiedźmin

Chodzi sobie gość po świecie i macha mieczykami. Nie jest taki przypakowany jak Conan, ani taki przystojny jak bohaterowie FR, ani tak nieskazitelny jak Aragorn. Tak brzmi przepis na boleśnie standardowe fantasy. Wiedźmin to coś więcej. Andrzej Sapkowski w opowiadaniach bawi się klasycznymi opowieściami, baśniami i legendami. Odziera je z piękna i uroku, ale w zamian wkłada współczesne problemy. O dziwo całość trzyma się kupy, bo te klasyczne opowieści są na tyle uniwersalne, że wystarczy zmienić w nich kilka szczegółów, by wszystko grało. Sapkowski właśnie to zrobił. Uwspółcześnił poprzez dialogi, dodał kilka archetypów z naszego świata, a całość polepił historią romansu. Co z tego wyszło?
Po pierwsze historie o nietolerancji, rasizmie, mizoginii i mizandrii. Mamy więc prześladowane elfy (jebać te wąskodupe, szpiczastouche śmieci), zapędzone do gett krasnoludy (biedne krasnale), ale też uchodźców i przesiedleńców. Mamy wojenki podjazdowe na to, która płeć lepiej czaruje. Niechęć do obcych nawet z własnej rasy, a na zwykłym ludzkim skurwysyństwie kończąc.
Do tego są postacie pragmatyczne, oportuniści, „wujki Janusze z wąsem”, dobre, złe i neutralne. Ci, co chcą żyć i ci, co chcą przeżyć oraz ci, którzy chcą połączyć te dwa cele (dziadek cię zje, ale wcześniej wydupczy). Do wyboru do koloru.

Netflix poległ na polu poprawności

I teraz, mając takie bogactwo bardzo współczesnych problemów, Netflix postanowił wszystko idealnie zjebać. W imię poprawności politycznej spłycono historię, byle by tylko nikogo nie urazić. I w każdym kolejnym odcinku widać, że ktoś, kto pisał scenariusz, myli Brokilon z Brooklynem, a różnorodność kojarzy mu się jedynie z pratytetami zatrudnienia.
W efekcie mamy serial do bólu politycznie poprawny, ale całkowicie pozbawiony przesłania. Nie trzeba było wiele, by pokazać, czym kończy się nietolerancja i rasizm (Koniec świata). Dlaczego tłum idiotów jest niebezpieczny (gdzie szewc Kozojed), gdy zaczyna głosić swoje hasła.

// offtop

Wyobraźmy sobie ekipę z Hołopola, która rusza na smoka z hasłem „Spraw by Caingorn, znowu było wielkie” (Make Caingorn Great Again).

// koniec offtopu

A może Calanthe? Nie, tu jest naprawdę głupio zrobiona historia…

No i ta aktorka

Hmmm – Geralt z Rivii
Kurwa – też Geralt z Rivii.

I nie chodzi wcale o czarne driady, bo IMO, to jest całkiem fajna koncepcja czarno-rude, gibkie babki. To by naprawdę miało sens. Tu chodzi o drętwe aktorstwo.
Jak już wspomniałem wcześniej, Wiedźmin opiera się na uwspółcześnionym dialogu. Na przeniesieniu tego jak sobie wyobrażamy rozmowę, dajmy na to Tuska z Putinem na miejscu katastrofy smoleńskiej, na temerski dwór. Tutaj znowuż Netflix poległ.

// offtopic

Wiedźmin 2002, czyli produkcja Lwa, tego Lwa, pomimo ubogich efektów specjalnych i bardzo „europejskiej” pracy kamery nie był aż tak zły, bo twórcy bardzo dobrze ogarnęli dialogi.

// koniec offtopu

Geralt jest płaski, a jego zasób słownictwa jest na poziomie najbardziej zmenelonego żula. 150 słów góra.
Jaskier nie może się zdecydować, czy mówi normalnie, czy pseudoarchaizować język i składnię by było „po dworskiemu”, czy znowuż wejść w tryb Nikka Sixxa z politycznie poprawnymi żarcikami.
Yennefer z Chupa Chupsem mówi wyraźnie i bez błędnie (fun fuckt – mój brat ma w sumie lekką wadę zgryzu, chuja go rozumieją). Do tego sama kombinacja z młodą Yen jest słaba fabularnie.
Ciri – najlepiej to podsumował Franz. Pomysł nie wypalił, bo należało zatrudnić kogoś w wieku Maisie Williams w pierwszym sezonie GoT, a nie 18-letnią dziewczynę.

Sapkowski ma pecha

Ekranizacja w wykonaniu TVP z 2002 roku była jaka była. Trafiła na moment gdy do kina wchodziły po kolei rok po roku Matrix, Drużyna Pierścienia, Dwie Wieże. Filmy, które przedefiniowały efekty specjalne i to jak należy robić fantastykę. Wiedźmin utrzymałby się w starciu z „Blade: Wieczny Łowca” czy ST:Rebelia, czyli w 1998.
W 2019 pech polega na tym, że GoT zdefiniował gatunek na kolejne 20 lat. Nagle okazało się, że chcą zrobić dobry serial fantasy, trzeba odpalić bitwę pod Sodden, jako starcie stutysięcznych armii. Trzeba nadać bohaterom charakter już od samego początku. To znowuż wymaga dialogów, gry aktorskiej i sensownego podejścia, w którym nikt nie boi się niepolitycznych scen (gdzie balia na cztery osoby?). Bez tego dostajemy produkt serialopodobny.

Podsumowanie

Netflix zrobił średni serial. Jeżeli ktoś nie zna prozy Sapkowskiego, to się nie zawiedzie. Całość wpisuje się we współczesne wymagania i możliwości. Jeżeli jednak poznałeś trochę prozę, albo co gorsza, zrozumiałeś ją, to biada ci. Będziesz się męczył bardziej niż w 2002. W skali od 1 do 10 mocna Agnieszka Dygant (Toruviel sobie porównajcie).

GoT pięknie się kończy

Jeżeli nie oglądałeś i nie chcesz spojlerów, to nie czytaj dalej.

Wprowadzenie

Prawie wszyscy narzekają, że piąty odcinek Gry o Tron nie trzyma się kupy i zniszczono serial. Rzecz w tym, że po ośmiu latach od premiery niektóre wątki już się zatarły. W dodatku część narzekających ludzi nie czytała książek, albo czytała je wiele lat temu i niewiele już pamięta. Zatem po kolei (postaram zachować się kolejność zgonów).

Varys

Takie życie szpiega, że w końcu umiera. Jak Jagoda, Jeżow czy Beria. Varys od początku był postacią służącą koronie, a nie królowi. Nie zauważył, że Daenerys od dawna myśli według zasady „państwo to ja”. W dodatku nie zauważył, że inni ludzie z otoczenia królowej też tak myślą. Są wierni, a nie są politykami, którzy będą grać pod siebie jak Petyr lub swoje rody jak Tywin. Pieczyste z grubasa było oczywistym zakończeniem tego wątku.

Złota kompania

Zapisali się na wojnę, ale zrobili to po złej stronie. Koniec tematu.

Euron

Kolejna oczywista śmierć, choć nie na morzu. Zgubiło go pragnienie władzy i sławy. Taki był od początku historii. Chichotem losu jest, że zabił Jaimiego, ale nikt się o tym nie dowie. Motyw znany i lubiany – bohater, którego czyny zapomniano, bo nie było świadków.

Qyburn

Zabity przez Górę, ale tak naprawdę umarł na głupotę i zadufanie. Motyw z lekka oklepany, bo nekromanta ginie z ręki zombie, ale bez przesady. Nie była to też jakaś bardzo ważna postać, by za nią płakać.

Góra i Pies

Oni się przecież nienawidzili. Od pierwszego epizodu było wiadomo, że ich historia skończy się pojedynkiem, w którym się nawzajem pozabijają.
To, co mi się podobało to charakteryzacja Góry. Świetna robota, bo w końcu mamy jakieś sensownie zrobione zombie. Sam pojedynek fajnie zrobiony i z dobrym zakończeniem. Pies odgrywa się na Górze, za spaloną twarz wrzucając go w ogień.

Jaime i Cersei

Umarli tak jak się urodzili. Razem, wtuleni w siebie. Niby też oklepany wątek, ale moim zdaniem bardzo fajny i w pewien sposób pozytywny. Miłość triumfuje, a Lannisterowie dostają pewnego rodzaju nagrodę. Od początku do końca razem i nie ważne, co działo się dookoła. Scena o tyle genialna, że widać, iż oboje mogą być sobą, tylko jak są we dwoje. W książce ten wątek jest trochę bardziej rozbudowany. Ich wspólne momenty są jedynymi, kiedy nie udają kogoś, kim nie są.

Ci którzy przeżyli

Daenerys jest szalona. Tak jak jej przodkowie i podobnie jak oni rozwiązała problem. Wzięła smoka i spaliła miasto. Dlaczego, skoro za morzem tego nie robiła? Ano dlatego, że tu ma, po pierwsze, wolnych ludzi, którzy jej zdaniem mogą sami podejmować decyzję. Po drugie przez cały czas uważała się za prawowitego władcę, a jej poddani ją zdradzili. Ukarała ich w starym dobrym stylu.
Tyrion, wypuszczając Jaimiego, wiedział co robi. Pozbył się problemu, który pojawiłby się w momencie, gdy trzeba by oddać długi. Poza tym Lannisterowie trzymają się razem.
Arya zabiła, co miała zabić. Jest jednak Starkówną i jest wrażliwa na los ludzi. Jako Nikt też została nauczona, że śmierć bez sensu jest zła. Zatem na liście nadal widnieje Królowa. Jedynie imię trochę inne. Będzie wesoło. IMO zginie.
Jon, który znowu nie ogarnia tej kuwety. Co prawda nasrał tam smok, ale może byłby łaskaw pomyśleć. Wróci do pilnowania muru, a jako nieumarły jest niezłym kandydatem na kolejnego szefa lodziarni.

Podsumowując

Jeżeli ktoś liczył na zakończenie w stylu Tolkiena, to się przeliczy. Niestety wielu krytykujących wychowało się na Harrym Potterze i LoTRze i spodziewają się, że wszytko będzie dobrze.
Ja wychowałem się na Warhammerze. Tam nic nie jest dobrze.

Perkele Trees – czyli kata TDD – część 0

Słówko perkele po fińsku oznacza „diabeł”. Ma też piękne brzmienie. Rzecz w tym, że w Finlandii, gdyby istniała, większość społeczeństwa to luteranie. Zatem słówko to jest traktowane jako taka nasza rodzima kurwa.

Po tym krótkim wstępie przejdźmy do dzisiejszego tematu.

Drzewa Merkle – wstęp

Drzewa Merkle nazywane drzewami hashW są strukturą danych, która ma ułatwiać pracę z dużymi danymi. Historia powstania jest banalna. Ralph Merkle potrzebował w jakiś sposób przechowywać i przesyłać drzewa podpisów jednorazowych Lamporta. Przechowywanie samo w sobie jest już problemem. Ilość danych rośnie wykładniczo (efektywnie) wraz ze wzrostem liczby komunikatów. Nie ma jednak możliwości „naprawienia” tego elementu. Znacznie poważniejszym problemem jest jednak przesyłanie danych (podpisów) w celu weryfikacji. Wyobraźmy sobie, że musimy przesłać ciąg podpisów dla 1000 wiadomości. Będzie on miał około 20MB. Teraz dokładamy kolejny tysiąc wiadomości i nasz blob danych ma teraz… 200MB. Schemat Lamporta ma tę zaletę, że jest bezpieczny nawet w przypadku ataku z wykorzystaniem komputera kwantowego. Jednak trochę to waży.
Pytanie, czy musimy przesyłać wszystkie dane, by móc sprawdzić jakiś ich fragment?
Oczywiście nie! Ralph Merkle zaproponował proste rozwiązanie.

  • Podzielmy nasze dane na N bloków (nie ma znaczenia czy równych);
  • dla każdego bloku policzmy hash np. za pomocą SHA1;
  • pogrupujmy bloki (w pary dla zachowania prostoty);
  • dla każdej takiej grupy policzmy hash z hashy bloków;
  • jeżeli jest więcej niż jedna grupa, to pogrupujmy je tak jak bloki i wyliczmy hash;
  • jeżeli została jedna grupa, to jest ona korzeniem drzewa;

Ten algorytm produkuje nam drzewo. Liśćmi są bloki danych z hashem. Węzłami bloki zawierające skrót wyliczony ze skrótów liści albo skrótów węzłów potomnych.

Proces weryfikacji poprawności (prawdziwości) bloku jest prosty.

  • Na początek potrzebuję pobrać z zaufanego źródła hash korzenia;
  • pobieram blok n (losowy);
  • wyliczam hash bloku;
  • pobieram hash bloku, z którym mam wspólnego rodzica;
  • wyliczam hash rodzica i jeżeli to korzeń to porównuję z pobraną z zaufanego źródła wartością;
  • jeżeli to węzeł, to pobieram hash „rodzeństwa”, wyliczam hash rodzica i tak aż do korzenia.

Jeżeli hash korzenia, pobrany z zaufanego źródła, jest taki sam jak hash, który wyliczyłem, to pobrany blok jest poprawny.
Algorytm ten wykorzystują sieci p2p, mechanizmy weryfikacji spójności w bazach danych oraz algorytmy kompresji.

Kata

Kata (形) to z japońskiego forma – układ. W sztukach walki kata, to rodzaj ćwiczeń, polegających na powtórzeniu pewnej sekwencji ruchów. Pozwala to wyćwiczyć pamięć mięśniową (nawyków), a tym samym powtarzalność. Nauka wykorzystania wyćwiczonych nawyków, ich kompozycji, odbywa się w formie kumite, czyli ćwiczeń dowolnych.
W IT kata to ćwiczenie mające na celu poznanie pewnych technik i narzędzi. Przy czym, równocześnie dotykamy różnych aspektów pracy. W tym przypadku kata i kumite są znacznie bliżej siebie niż w sztukach walki.
Ponadto w IT przyjęło się, że kata jest bardzo mocno związane z TDD. Wynika to z faktu, że największym promotorem tej formy nauki jest Wujek Bob.

O czym będzie ten cykl?

Biorąc pod uwagę dotychczasowe wywody, można założyć, że kilka najbliższych wpisów będzie poświęcone wykonaniu jakiegoś kata TDD.
A tu niespodzianka. Otóż celem jest przeprowadzenie czytelnika, przez proces projektowania kata. Dzięki temu będzie on wiedział jak rozszerzyć swój katalog ćwiczeń i tym samym nie popaść w powtarzalność.