niedziela, 31 maja 2015

Czas ekspozycji na język - czyli co tak naprawdę jest czytelne i czy dziwne znaczki są dziwne?

W temacie czytelności kodu zdarzyło się ostatnio kilka ciekawych rzeczy. Po pierwsze w dobie prezentacji o tym jakiego koloru powinny być kartki z taskami pojawiło się jedno ciekawe wystąpienie, która podejmuje temat odmiennej perceppcji swiata intro i ekstrawertyków.

A TO JEST DO NIEJ LINK **-~~~~~>>>> http://www.infoq.com/presentations/psychology-agile

Prezentację polecam sobie obejrzeć całą - ale jest jeden ciekawy slajd w temacie czytelności kodu- ciekawy bo opowiada o tym jak "percepcyjnie" nasz móżg "dekoduje ten kod" :

I w skrócie okazuje się, iż z biegiem czasu programista nie czyta kodu znak po znaku ale raczej (powtórzmy to słowo)dekoduje zestaw znanych struktur - dlatego dwa kawałki kodu mogą mieć podobną złożoność wyliczoną jakimiś wskaźnikami a ludzie będą i tak jeden oceniać jako bardziej czytelny niż drugi. (A nawet kiedyś zrobiłem o tym artykuł - http://pawelwlodarski.blogspot.com/2011/10/poznaj-swoje-ograniczenia.html i znowu data 2011 i znowu zaduma nad pędzącym czasem...)

A po drugie mózgi niektórych programistów wychowanych w czeluściach działów utrzymania gdzie zalepiali luki w cegiełkach egipskich systemów ulegną zapewne przepaleniu bo wyszła oto dosyć ciekawa książka :

I to jest o Javie... i już nie można narzekać, że jakiś język X cośtamcośtam Bo-To-Jest-O-Javie ...i o strzałkach i (za jakieś 50 lat - dop. tłumacza) jak korporacje przejdą na Javę8 nie będzie już ucieczki od tych strzałek.

A przykładowy kod z książki wygląda tak :

public static <A, B, C, D> Function<A, Function<B, Function<C, Function<D, String>>>> f() {
    return a->b->c->d->String.format("%s, %s, %s, %s",a,b,c,d);
  }
(zwyklu currying - o ch*** ci chodzi?!)

FP Java

Książka z niebieską okładką jest książką bliźniaczą do wydanej poprzednio przez tych samych autorów czerwonej -pokazanej poniżej, tyle tylko że w dobie napięcia na linii Wschód-Zachód - ma mniej komunistyczną okładkę

Pierwsza ciekawa obserwacja : w obydwu książkach we wstępie jest ten sam przykład z refaktoringiem kodu zakupu kawy od wersji klasycznie-imperatywnej, która ze względu na efekty uboczne w zasadzie się nie komponuje z pozostałymi kawałkami kodu - do formy funkcyjnej, która ładnie składa się w większe kawałki logiki.

I tutaj podobieństwa się kończą bo w przypadku javy jest trochę zabawy z samą składnią gdzie co chwila autorzy piszą "fajnie gdyby się to dało zapisać tak a tak ale się nie da" plus jest trochę uzupełniania brakujących funkcjonalności jak doimplementowywanie klasy Tuple.

Tak czy inaczej fajne jest to, że jak ktoś nie miał okazji popracować z generykami w Javie to teraz będzie ją miał - a do tego kilka java puzzlerów dochodzi już w samym przykładzie próby zaimplementowania rekrencyjnej lambdy gdzie autorzy przedstawiają trzy haki na kompilator (plus taki kawałek - "na potrzeby edukacji udawajmy, że java wspiera tail recursion").

No dobra ale co z tą czytelnością i czy strzałki ją poprawiają?

Czas ekspozycji na technologię

Mamy taki kawałek Javy :
 public static final Function<Integer, Integer> triple = x -> x * 3;

  public static final Function<Integer, Integer> square = x -> x * x;

  public static final Function<Function<Integer, Integer>, Function<Function<Integer, Integer>,
                                        Function<Integer, Integer>>> compose = f1-> f2 -> x -> f1.apply(f2.apply(x));

  public static final Function<Integer, Integer> f = compose.apply(square).apply(triple);
I dokładnie taki sam poniżej w scali - w sensie analogiczny :

val triple:Int=>Int=_*3
val square= (x:Int)=>x*x
val compose : (Int=>Int) => (Int=>Int) => (Int=>Int) =
f1 => f2 => x=> f1(f2(x))

I teraz może nastąpić szereg uwag i obserwacji. Po pierwsze dla osób, które każdy problem rozwiązują skończoną ilością ifów obydwa kawałki kodu będą nieczytelne i stwierdzi, ze to całe FP to bzdura - wiem, bo sam tak kiedyś myślałem.

Po drugie w przypadku osób, które już mogą mieć doświadczenie w Javie8 drugi przykład z kodem _*3 i aliasami typów, które nie występują w Javie Int=>Int będzie zapewne nieczytelny i stwierdzi, ze ta scala to bzdura - wiem, bo sam tak kiedyś myślałem.

Ale zobaczmy teraz coś innego - twarze, których nie ma :

Dlaczego większość ludzi widzi tam twarze? Mózg rozpoznaje wzorce, ewolucja, krytyczne do przetrwania sratatata itd. Ale pytanie jak to ma się do kodu? Generalnie dałem te dwa obrazki aby każdy sam zobaczył, że coś może "po prostu się dziać w naszej głowie" i trudno to opisać w logiczne warunki w stylu "jest logiczne że ciąg znaków xyz jest lepszy abc"

Zakładając, że system percepcyjny może podobnie jak w przypadku twarzy przystosować się do pewnych wzorców w kodzie - a ten link z prezentacją z INFOQ był po to by pokazać, że podobno tak - to możemy sobie powiedzieć że czytelność kodu to nie tyle funkcja jego złożoności ale raczej :

CKp=f(Z,Te)

  • CKp - czytelność kodu według programisty P
  • Z - złoność kodu samego w sobie - cyclomatic complexity itd
  • Te - I to coś nowego! Czas ekspozycji programisty oceniającego na dany język/ sposób zapisu
I tutaj dobry moment na metaforę z siłownią. Generalnie by podnosić większe ciężary przejrzenie magazynu kulturystycznego nie wystarczy, trzeba (mądrze) ćwiczyć, ćwiczyć i jeszcze raz ćwiczyć. Podobnie Java w 24 godziny czy tam Scala dla niecierpliwych czy coś w tym stylu to też może być za mało.

Czy poniższy kod jest czytelny? Może warto zapytać - czy już jest czytelny dla Ciebie?!? (zauważyłem, że sprzedawcy jak mówią do grupy to często używają słowa "Ciebie")
(defn triple [x] (* x 3))
(defn square [x] (* x x))
(defn compose [f1 f2] (fn [x] (-> x f2 f1)))
Dla mnie kod clojure każdego dnia staje się coraz czytelniejszy a nie jestem pewien czy kiedyś i po nim nie jechałem. Nowa forma refaktoringu? - zmniejszenie złożoności kodu poprzez zwiększenie doświadczenia programistów bez zmiany kodu?

Dalsze porównania

Wróćmy an chwilę do narzekania na emotiocon driven development. Jest taki kod scali :

List(1,2,3,4,5).reduce((a,b)=>a+b)
List(1,2,3,4,5).reduce(_+_)

I podobny kod clojure - też są placeholdery %1, %2 - być może po ogarnięciu jednego języka z placeholderami ogólny koncept staje się dla nas akceptowalny?
(reduce (fn [a b] (+ a b)) (list 1 2 3 4 5))
(reduce #(+ %1 %2) (list 1 2 3 4 5))
(reduce + (list 1 2 3 4 5))

I może dzieje się podobnie z innymi językami?

addOneList' lst = map (\x -> x + 1) lst
addOneList'' = map (+1)

Powyższy kod to haskel i tylko wydaje mi się, że wiem co on robi bo przekleiłem go z netu. Istnieje możliwość, że gdzieś tam tworzy monadę 7 poziomu - grupowa infrawizja, +3 do rozpraszania meetingów.

KolonKolon - język zwinny

Ostatnio taka ciekawa rzecz się pojawiła - co się dzieję jak za bardzo przesadzimy z abstrakcjami i faktycznie wszystko jest obiektem?

val a:Any=List(1,2,3)
val res=a match {
    case x : Int => "haaaaaaaaa"
}

I to się kompiluje bo typ jest Any i kompilator nie ma tutaj za dużo do powiedzenia ale już w Runtime

Exception in thread "main" scala.MatchError: List(1, 2, 3) (of class scala.collection.immutable.$colon$colon)
KoląKolą

Otóż okazuje się, że mamy dwa ciekawe kawałki kodu składające się z samych dwukropków - po pierwsze metoda w List :

def ::[B >: A] (x: B): List[B] =
    new scala.collection.immutable.::(x, this)

To dzięki niej możemy sobie pisać :
1::2::Nil
Ponadto w ciele metody :: tworzona jest nowa instancja obiektu ::

final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {

i to dzięki tej klasie działa taki pattern matching :

val list=1::2::Nil

list match {
  case 1::2::Nil => "hahaha"
}

I takich konstrukcji jest więcej (np $hash$colon$colon w Stream czyli #::) - z tego co pamiętam w javie nazwy mogą zaczynać się od literki, dolara lub podkreślnika no i scala obchodzi to ograniczenie mapując :: z kodu w $colon$colon. Czytelne?

I znowu dwie strony medalu - jak ktoś ma clean code na półce (w sumie ja mam) to może się łapać za głowę bo kto to widział nazywać metody "::" ale jak się trochę porobi, następuje czas ekspozycji to taki zapis "1::oldList" naprawdę momentami "staje się" czytelniejszy od List.builder.add(1).add(2) , "oldList.prepend(1)" czy coś w ten deseń.

I druga ważna rzecz, ta cała konstrukcja :: to twór biblioteki a nie języka i w w tym jest właśnie siła Scali- w łatwości dodawania nowych konstrukcji.

CZAS NA METAFORĘ :

  • Jest siekiera - trzeba ściąć drzewo by zrobić papier
  • Generalnie chodzi o produkcję zwykłego papieru - ktoś przynosi pilę mechaniczną i uderzamy nią w drzewo jak siekierą - siekiera działała lepiej
  • Generalnie chodzi o stworzenie jakiegokolwiek papieru - ktoś daje nam maszynę wytwarzającą papier syntetyczny - uderzamy nią w drzewo - wniosek siekiera cały czas działa lepiej
  • Generalnie chodzi o stworzenie medium do wyświetlania znaków - ktoś przynosi tablet - uderzamy nim w drzewo - stara dobra siekiera cały czas działa lepiej.

Czy ta metafora ma sens? Nie wiem ale można sobie poczytać jak użyto Scali w Sparku :

  • https://databricks.com/blog/2015/04/13/deep-dive-into-spark-sqls-catalyst-optimizer.html
  • https://databricks.com/blog/2014/06/13/application-spotlight-typesafe.html
  • http://apache-spark-user-list.1001560.n3.nabble.com/Why-Scala-td6536.html

Podsumowanie

Czy można jasno stwierdzić, które znaczki są dziwne a które nie? Niemowlę wyciągnięte dopiero co ze świata Javy na pewno znajdzie zapewne trudność w odczytaniu takich zapisów _+_ czy #:: ale równie dobrze rasowy programista haskella może przeżyć szok gdy zacznie pisać w Javie i nagle

"Ej.. stary... o co tu chodzi... tutaj musisz stawiać w każdej linii ten znaczek... no to... ; ... to jest maksymalnie dziwne..."

Tak czy inaczej trzeba dać językowi szansę bo z czasem tak jak w tym matrixie będzie można odczytywać co tam spada na tym ekranie

Brak komentarzy:

Prześlij komentarz