niedziela, 18 grudnia 2016

Sprawność ITSilnika

Za wikipedią : https://pl.wikipedia.org/wiki/Sprawno%C5%9B%C4%87

Sprawność – skalarna bezwymiarowa wielkość fizyczna określająca w jakim stopniu urządzenie, organizm lub proces przekształca energię występującą w jednej postaci w energię w innej postaci, stosunek wartości wielkości wydawanej przez układ do wartości tej samej wielkości dostarczanej do tego samego układ.

Mamy na przykład sprawność silnika cieplnego, do tego z technikum pamiętam, że generator prądu też miał sprawność, a no i w zasadzie wszystko co zamienia jeden rodzaj energii na inny ma sprawność. Zamiana energii programisty w implementację ficzerów też ma swoją sprawność. Zerkniemy trochę na tę sprawność. Trochę na serio ale bardziej nie na poważnie. Bo tak na poważnie to się nie da.

mała uwaga

Jeśli pojawia się słowo "sprawność" to najpierw ostrzeżenie. Niestety branża IT przepełniona jest masą folkloru i samozwańczych metodyk wytwarzania końcowego produktu co owocowało tym, że "sprawność pracy" w przeszłości była określana na podstawie chybionych porównań pomiędzy skrajnie różnymi formami pracy. I tak ludzie, którzy nie za bardzo znali się na wytwarzaniu oprogramowania ale regularnie prasowali swoje koszule określili, że miernikiem sprawności pracy jest ilość wytwarzanych linijek kodu bo przecież jak produkujemy śrubki to im więcej normy ktoś wyrobi to chyba lepiej tak?

To co mam nadzieję zobaczymy poniżej powinno nas przekonać, że jest odwrotnie. Im mniej linii kodu tym lepiej. (z założeniem odpowiedniej czytelności co też trudno zdefiniować bo czasem brak czytelności kodu to nie właściwość kodu ale właściwość niesatysfakcjonującego poziomu wiedzy osoby ten kod czytającej ) . A w zasadzie jak ktoś ma ten współczynnik ujemny czyli np -100 oznacza usunięcie 100 lii kodu i uzyska oczekiwany efekt - to ten ktoś powinien dostać kurde bonus. Każda linia kodu to potencjalne bugi a tu linie kodu poznikały a ficzery są! W zasadzie jeśli programista jest w stanie przyjść do pracy i zrobić ficzera zgodnie z założeniami jakości kodu w 30 minut to powinien iść do domu bo każda minuta obecności programisty przy komputerze zwiększa prawdopodobieństwo pojawienia się bugów.

Na koniec obligatoryjne wspomnienie klasyki : https://en.wikipedia.org/wiki/The_Mythical_Man-Month by przypomnieć, że już od 40 lat niektórzy śmieją się z tworzenia programowania przy pomocy "metodyki ludzkiej fali", o i teraz można przejść do rzeczy prostszych czyli języków programowania.

Sprawność programowania

Jest taki dowcip, że programowanie to konwersja kawy do linii kodu (taki śmieszny śmieszny dowcip). Można to uogólnić na wymianę energii psycho-fizycznej programisty w uporządkowany zestaw cybernetycznych operacji na abstrakcyjnych formach informacji, które to można dla uproszczenia nazwać "ficzerami produktu, które przynoszą hajs".

I tak np. dla uproszczenia rozważmy formę zminimalizowaną tych ficzerów - niech "ficzer" na początku będzie reprezentowany przez minimalną logikę inkrementująca inta. Mamy tu przykład Javy 7 i Javy 8 także zostajemy w mainstreamie.

//Java7
Function<Integer,Integer> inkrementuj7=new Function<Integer, Integer>() {
    @Override
    public Integer apply(Integer integer) {
        return integer+1;
    }
};

//Java8
Function<Integer,Integer> inkrementuj8 = i -> i+1;
W pierwszym przypadku potrzeba więcej ruchów palcami, spalenia większej ilości kalorii i większej aktywności pomiędzy neuronami by wyprodukować rezultat dokładnie taki sam jak jedno-linijkowiec poniżej.

Teraz zerknijmy na bardziej rozbudowany przykład - funkcja map, która robi podobne manipulacje jak analogiczna metoda na Stream'ach - zmienia każdy z elementów według podanej funkcji prostej 'f' -> stąd zagnieżdżone wewnętrzne Function w typie.

Function<List<Integer>,Function<Function<Integer,Integer>,List<Integer>>> map = input -> f -> ...; 

Typ jest dosyć zakręcony a to w zasadzie jedna z najprostszych sygnatur funkcji wyższego rzędu jakie można spotkać. Zobaczmy co możemy uzyskać po wprowadzeniu łatwiejszej notacji typów. Tym razem scala :

val mapFull: Function[List[Integer],Function[Function[Int,Int],List[Integer]]] = input => f => ???
    
val map : List[Integer] => (Int=>Int) => List[Integer] = input => f => ???
Chociaż na początku wydaje się, że różnica to tylko kilka centymetrów to jednak z punktu widzenia programisty różnica w poświęconej energii na kodowanie/dekodowanie typu rozszerzonego jest odczuwalna. Przynajmniej ja ja odczułem. Może drogi czytelniku/czytelniczko przeprowadzić swój własny eksperyment i spróbować popisać samemu/samej te typy.

Ciekawą rzeczą jest tutaj także koszt dekompresji rozwiązania do mózgu programisty i subiektywna ocena tego kosztu wynikająca czasem z braku odpowiedniego doświadczenia. Na przykład :

map(List(1,2,3))(e=>e+1)
map(List(1,2,3))(_+1)
Ten podkreślnik .W scali możemy zastosować taki "plejsholder" zamiast powtarzać wystąpienie argumentu. Dla mnie osobiście jest to trochę czytelniejsze (to z podkreślnikiem) natomiast pamiętam na początku, że nie zawsze było dlatego potrzebny jest pewien czas ekspozycji na dane rozwiązanie by subiektywnie było zakwalifikowane do indywidualnego worka "(według mnie) obiektywnie czytelne"

Dane

Podobnie jak z operacjami na informacjach tak samo można podejść do kosztów energetycznych tworzenia samych informacji. Jaki wqysiłek mierzony w ruchach palcami (a przypomnijmy, że jakieś tam zapalenie nadgarstków to zawodowa choroba programistów) należy ponieść by zdefiniować dane?

Java
class User{
    private Long id;
    private String name;

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
Kotlin i Scala
//Kotlin zwykła klasa
class User(val id:Int,val name:String)

//Kotlin klasa z equalsami,hashcodami i innymi takimi
data class User(val id:Int,val name:String)

//scala zwykła klasa
class User(val id:Int,val name:String)

//scala - klasa z całym dobrodziejstwem inwetnarza hasz kody,equalsy i pattern matching
case class User(id:Int,name:String)

Oczywiści w 2016 roku nie trzeba każdej literki pisać z osobna bo IDE dużo samo pogeneruje jednakże już w trakcie analizy - nawet mając do czynienia ze znajomymi wzorcami gettery/gettery/gettery - trzeba to gdzieś w pamięci operacyjnej zmagazynować i chyba wtedy zdaje się sód albo jakiś inny pierwiastek jest używany do zmiany potencjału na aksonach (końcówki neuronów) i później jakieś odpadki wyładowań elektrycznych, które po tych wymianach danych pozostały pomiędzy neuronami, muszą być w trakcie snu usunięte.

W javie jest jeszcze ten Lombok. Nie znam produktu jako takiego ale skoro ludzie chcą go używać to strzelam, że ilość mechanicznego kodu, który inaczej musieliby napisać ręcznie jest dla nich bolesna. Coś tam słyszałem, że w Javie 10 może jakas lepsza składnia będzie. Jak będzie to będzie.

Bonus : bezpieczne granice

Ponieważ pojawiła się Scala to być może pojawią sie opinie, że trudny język, że tak dalej i tak dalej... Generalnie o ile przy przejmowaniu kodu z dalekich krain trzeba mieć się na baczności o tyle samemu kontrolując granice można naprawdę traktować scalę jak wygodniejszą jave. I chociaż w środowisku scalowym często sformułowanie "używać scalę jako lepszą javę" często jest używane trochę z negatywnym zabarwieniem, że ktoś niby nie stosuje/rozumie technik niedostępnych w javie (wolne monady 147 poziomu) - o tyle jeśli ktoś zdecyduje się przejść z javy do scali to to jednak od czegoś trzeba zacząć. Można zacząć od prostych rzeczy zgodnie z powiedzeniem "lepszy rydz niż nic".

Pomimo, że scala kojarzy się (przynajmniej mnie) z popularyzacją podejścia funkcyjnego to - być może nie jest wiedza powszechna - ale ale Scala jest bardziej obiektowa niż Java. Bo jak to większość czytanek wspomina - w Javie wszystko jest obiektem.... no może poza klasą która ma same metody statyczne,... no i może poza int... i w sumie też nie long. No i boolean to też nie obiekt.

Weźmy taki standardowy przykład z abstrakcją za interfejsem i fabryką implementacji realizujących tę abstrakcję - możemy to strzelić w scali w kilku linijkach

trait Abstraction{
  def abstractProcess(s:String):String
}

class ConcreteImplementationDupa extends Abstraction{
  override def abstractProcess(s: String): String = s+"dupa"
}

object SurpriseFactoryPattern{
  def create():Abstraction = new ConcreteImplementationDupa
}

I co najważniejsze - jeśli będziemy trzymać się koncepcji, które dla javy 8 nie są czymś obcym to pod spodem dostaniem byte code, który też nie powinien zawierać niespodzianek. I tak trait z abstrakcyjnymi metodami kończy jako zwykły interfejs

public interface jug.lodz.workshops.Abstraction {
  public abstract java.lang.String abstractProcess(java.lang.String);
}

A klasa to zwykła klasa
public class jug.lodz.workshops.ConcreteImplementationDupa implements jug.lodz.workshops.Abstraction {
  public java.lang.String abstractProcess(java.lang.String);
  public jug.lodz.workshops.ConcreteImplementationDupa();
}

Fabryka jako, że stanowi koncept dla javy8 obcy czyli singleton na poziomie języka - jest odwzorowany przy pomocy dwóch klas SurpriseFactoryPattern.class i SurpriseFactoryPattern$.class - czyli jedna jedyna instancja i dostęp do tejże instancji poprzez statyczne metody - w javie jako javie inaczej chyba się nie da

Również jesli chodzi o sygnatury funkcji process kompilacji jest przewidywalny i a na poziomie kodu mamy bardzo czytelne aliasy typów funkcji :

  val map: List[String] => (String=>String) => List[String] = input => f => input.map(f)
Są one przez kompilator zamieniane w znajome dla javy8 SAM type
public static scala.Function1<scala.collection.immutable.List<java.lang.String>,
 scala.Function1<scala.Function1<java.lang.String, java.lang.String>,
 scala.collection.immutable.List<java.lang.String>>> map();

Zakończenie

Na zajęciach z polskiego zawsze była taka teoria, że artykuł ma początek, środek i zakończenie. Gdzie zakończenie to wstęp tylko innymi słowami. Zamiast się tutaj produkować przeczytajcie sobie raz jeszcze wstęp ale co drugie słowo to wyjdzie coś oryginalnego z tym samym przesłaniem i tyle na dzisiaj starczy.