piątek, 3 października 2014

PlayFramework warsztaty - REST - część 2

To jest dalszy ciąg lekcji o Jsonie i webserwisach -- A tutaj jest część 1

Pomimo iż materiału wydawało się być dużo to jednak udało się zrobić całość w 2,5 godziny (Między innymi dlatego, ze już nie chce mi się z pizzą kombinować i nie ma przerwy - ale zawsze ostrzegam - ludzie weźcie kanapki! (Albo odżywki - przyp. red.)).

Prąd i sala się same nie opłaciły to ten tego :
A poniżej wszyscy bez żadnego zewnętrznego przymuszenia promieniują wewnętrznym szczęściem i radością :

A tutaj są linki do poprzednich części :

Wywołanie

Do tego ćwiczenia zostawiamy włączoną aplikacyjkę z poprzedniej części - to będzie apiserwer , zaś to co powstanie tutaj to taki apiclient.

By obydwa serwery się nie pogryzły ten drugi uruchamiamy na innym porcie w ten oto wygodny sposób :

~run 8000

I wołamy :
def webserwis = Action.async {
    import scala.concurrent.ExecutionContext.Implicits.global
    val futureResult=WS.url("http://localhost:9000/list").get
    futureResult.map{response=>
     Ok("otrzymalem : "+response.json)
    }
  }

I to w zasadzie tyle w wariancie podstawowym - powinno śmigać. Trzeba uczestnikom wytłumaczyć tutaj co to jest to "async" i nadmienić, że z wywołania WS dostajemy

Future[Response]
. O samych Future będzie więcej za chwilę.

No i mogą pojawić się pytania o to co to jest i skąd się wziął ExecutionContext. W tym miejscu może się przydać strona z dokumentacji Playa o pulach wątków.

Wysyłanie posta z ciałem

Wygląda tak :
def dodajMeal = Action.async {
    val result = WS.url("http://localhost:9000/add").post(Json.obj("name" -> "kurczak", "calories" -> 1000))
    result.map(r => Ok("dostałem : " + r.body))
  }

Future poligon

Kiedy już mamy ogólne pojęcie o używaniu klienta Webserwisów przyjrzyjmy się bliżej temu co ono zwraca - czyli Future. Tutaj można wspomnieć jak Future jako abstrakcja ratuje nas od masy zagnieżdżonych callbacków.

Jedna ważna rzecz jeśli chodzi o zabawę Future - tutaj nie da się używać worksheet bo plik jest cały czas odświeżany i procek się pali dlatego normalnie w mainie napiszemy kod.

Najpierw zasymulujmy synchroniczne wywołanie serwisu z zamuleniem jednej sekundy

object Poligon {

  def main(args: Array[String]) {
    val result = servis1
    println("start")
    println(result)
    println("end")
  }

  def servis1 = {
    Thread.sleep(1000)
    10
  }
}

I wynik :
start
10
end

Z future


  def main(args: Array[String]) {
    val executorService = Executors.newCachedThreadPool()
    implicit val executionContext = ExecutionContext.fromExecutorService(executorService)

    val result = Future{servis1}
    println("start")
    result.onSuccess{case result=>println(result)}
    println("end")
  }
Wywołanie serwisu nastąpiło asynchronicznie więc rezultat będzie na samym końcu.
start
end
10
No i aby pokazać jakie patenty są możliwe to dajmy na to zmodyfikujemy sobie rezultat
val result = Future { servis1 }.map(_+3)
I mamy teraz :
start
end
13

dwa servisy

O a tutaj proszę jak ładnie komponujemy dwa asynchroniczne wywołania serwisów
object FuturePoligon {
  def servis1() = {
    Thread.sleep(1000)
    10
  }
  
  def servis2(arg:Int) = {
    Thread.sleep(1000)
    arg+7
  }
}

val result = Future { servis1 }.map(result1=>servis2(result1))
    println("start")
    result.onSuccess{case result=>println(result)}
    println("end")
start
end
17

Który future pierwszy

object FuturePoligon {
  def servis1() = {
    Thread.sleep(1000)
    10
  }
  
  def servis2(arg:Int) = {
    Thread.sleep(800)
    arg+7
  }
}

    val future1 = Future { servis1 }
    val future2 = Future { servis2(4) }
    val completedFuture=Future.firstCompletedOf(Seq(future1,future2))
    println("start")
    completedFuture.onSuccess{case result=>println(result)}
    println("end")

start
end
11

Obsluga faili

import scala.util.{Failure,Success}

 val futureFail = Future {
      throw new Exception("się zjebało")
    }

    futureFail.onComplete {
      case Success(value) => println(value)
      case Failure(e)     => println(e)
    }

    futureFail.onFailure {
      case e => println(e)
    }
wypisuje :
java.lang.Exception: się zjebało
java.lang.Exception: się zjebało

Zaginione strony

Ponieważ te ćwiczenia nie powstają za jednym razem ale ciągle wracam i coś poprawiam także czasem zostawiam sobie notatki na przyszłość. W tym miejscu zapisałem sobie "ćwiczenia na typach i zdjęcie z goblinsów" - o co mogło mi chodzić? Pojęcia nie mam. Ale obrazek będzie :

Co dalej?

Na pewno ciekawe będzie poćwiczenie użycia Play i Akka razem ale wcześniej - ponieważ jest zainteresowanie takimi ogólnymi warsztatami o Scali to coś zrobimy też w tym kierunku :

poniedziałek, 15 września 2014

PlayFramework warsztaty - usługi REST - tworzenie i konsumpcja

Warsztaty

Spotkanie odbędzie się 23 września we wtorek - a tutaj są wszystkie szczegóły -

I klika zdjęć z ostatnich zajęć na dowód, że te warsztaty się naprawdę odbywają :) (Bo nigdy nic nie wiadomo - mogę w tej chwili siedzieć gdzieś zakuty w fartuch w zakładzie a to wszystko może dziać się w mojej wyobraźni ale dla potrzeb marketingowych załóżmy, że na 99,999% się jednak odbywają)

Plan na zajęcia

W tej partii materiału skupiamy się na wysyłaniu JSONa z serwera do klienta. Do tego aby nie bawić się w Javascript - klientem będzie inna aplikacja Playowa, która to umożliwi nam naukę używania klienta WebSerwisów(ale o tym w drugiej części).

Cechą charakterystyczną tych zajęć będzie więcej zabawy z bibliotekami Playa samymi w sobie aniżeli z Playem samym w sobie ale oczywiście na Play samego w sobie też się znajdzie czas sam w sobie.

A tutaj są linki do poprzednich części :

Coś na start

Aby dać przedsmak tego jak wygodne jest użycie w playu JSONa - szybki przykład na start. Taki mały kodzik a jakże czytelny i ileż on robi! :

def dajJsona = Action {
    import play.api.libs.json.Json._
    val json = obj(
      "temat" -> "play i json",
      "plan"->arr("krótki przykład","ćwiczenia z biblioteką","dłuższy przykład","webserwisy")
    )
    Ok(obj("message"->json))
  }
I w odpowiedzi dostaniemy
{"message":
 {"temat":"play i json",
   "plan":["krótki przykład","ćwiczenia z biblioteką","dłuższy przykład","webserwisy"]
 }
}
I wiadomo typ "application/json"

Zabawa z JSONem

A zabawa będzie polegała na ćwiczeniach z Jsonem w worksheet. Otwieramy sobie nowy pliczek, kopiujemy to co pod spodem i wio.

//wazny imporcik
import play.api.libs.json._

val mealJson: JsValue = Json.parse("""
{
  "meal": {
    "name" : "hamburger",
    "calories" : 3000,
    "description" : "bardzo tuczące jedzenie",
    "isJunk" : true,
    "ingredient" : {
      "name" : "imitacja mięsa",
      "calories" : 2000
    }
  }
}
""")
Obiekt do ćwiczeń mamy przygotowany więc teraz poćwiczymy jak go parsować i jak po nim nawigować
// wyszukiwanie pojedynczej wartosc
 val name= mealJson \ "meal" \ "name"             //> name  : play.api.libs.json.JsValue = "hamburger"

//wyszukiwanie listy wszystkich wartosci
 val calories= mealJson \\ "calories"             //> calories  : Seq[play.api.libs.json.JsValue] = List(3000, 2000)
Następnie małe ćwiczonka na konwersję do typów Scali:
//zwykła konwersja do scali
 val nameScala= (mealJson \ "meal" \ "name").as[String]
                                                  //> nameScala  : String = hamburger
 //jakby się miało wywalić to prosimy nie o zwykły typ ale o Option
 (mealJson \ "meal" \ "nieMaMnie").asOpt[String]  //> res0: Option[String] = None
 
Jak już czytamy to i można od razy walidować
 //można od razu walidować
 val zle=(mealJson \ "meal" \ "nieMaMnie").validate[String]
                                                  //> zle  : play.api.libs.json.JsResult[String] = JsError(List((,List(ValidationE
                                                  //| rror(error.expected.jsstring,WrappedArray())))))
 zle.map(name=>"jem :"+name)                      //> res1: play.api.libs.json.JsResult[String] = JsError(List((,List(ValidationEr
                                                  //| ror(error.expected.jsstring,WrappedArray())))))
 
 val dobre=(mealJson \ "meal" \ "name").validate[String]
                                                  //> dobre  : play.api.libs.json.JsResult[String] = JsSuccess(hamburger,)
 dobre.map(name=>"jem :"+name)                    //> res2: play.api.libs.json.JsResult[String] = JsSuccess(jem :hamburger,)

Sami tworzymy JSONa

Co gdy chcemy sami stworzyć obiekt JSONa z istniejących danych? Nie trzeba oczywiście sklejać stringa gdyż istnieje bardzo wygodny i wyrazisty mechanizm do wypełnienia właśnie tego oto zadania. Także uczestnicy kursu są tutaj poproszeni o zastąpienie stringa poniższą konstrukcją i wszystko hulać powinno.

//poniższy import działa dlatego, że wcześniej mamy import "play.api.libs.json._" - fajny patent
import Json._
val mealJson=obj(
 "meal"->obj(
  "name"->"hamburger",
  "calories"->3000,
  "description"->"bardzo tuczące jedzenie",
  "isJunk" -> true,
  "ingredient"->obj("name"->"imitacja mięsa","calories"->2000)
 )
)

Automatyczne konwersje

Mamy do dyspozycji metodę "Json.toJson(...)", która konwertuje nam dany typ w JSona. Działa z automatu dla podstawowych typów Scali - aby działało dla naszych typów musimy dostarczyć przepis na konwersję. Potem robi się "implicit" dzięki czemu nie trzeba tego cały czas przekazywać do metod.

                     //> json  : play.api.libs.json.JsValue = {"name":"mięso","calories":3000}
                                                  //| } = controllers.writers$$anonfun$main$1$$anon$1@50b98ef4

I teraz różne implementacje "ingredientWrites" można sobie importować z różnych bibliotek (ale nie na raz bo się pogryzą) i w ten sposób można konfigurować, który sposób konwersji ma być użyty. Zerknijmy na bardziej rozbudowany przykład z pełną klasą "Meal"

case class Ingredient(name:String,calories:Int)
  case class Meal(name:String,calories:Int,description:String,isJunk:Boolean,i:Ingredient)
  
  implicit val ingredientWrites=new Writes[Ingredient]{
   def writes(i:Ingredient)=obj(
    "name"->i.name,
    "calories"->i.calories
   )
  }                                               //> ingredientWrites  : play.api.libs.json.Writes[controllers.writers.Ingredient
                                                  //| ]{def writes(i: controllers.writers.Ingredient): play.api.libs.json.JsObject
                                                  //| } = controllers.writers$$anonfun$main$1$$anon$1@24ef2645
  val ingredient=Ingredient("mięso",3000)         //> ingredient  : controllers.writers.Ingredient = Ingredient(mięso,3000)
  
  implicit val mealWrites=new Writes[Meal]{
   def writes(meal:Meal)=obj(
    "name"->meal.name,
    "calories"->meal.calories,
    "description"->meal.description,
    "isJunk"->meal.isJunk,
    //I tutaj łądnie wykorzystujemy wcześniej zadeklarowany "Writer" dla typu Ingredient
    "ingredient"->meal.i
   )
  }                                               //> mealWrites  : play.api.libs.json.Writes[controllers.writers.Meal]{def writes
                                                  //| (meal: controllers.writers.Meal): play.api.libs.json.JsObject} = controllers
                                                  //| .writers$$anonfun$main$1$$anon$2@347db2f9
  val meal=Meal("Hamburger",3000,"złe jedzenie",true,ingredient)
                                                  //> meal  : controllers.writers.Meal = Meal(Hamburger,3000,złe jedzenie,true,In
                                                  //| gredient(mięso,3000))
  
  val json=toJson(meal)                           //> json  : play.api.libs.json.JsValue = {"name":"Hamburger","calories":3000,"de
                                                  //| scription":"złe jedzenie","isJunk":true,"ingredient":{"name":"mięso","calo
                                                  //| ries":3000}}
A żeby było ładniej to można :
prettyPrint(json)                         //> res0: String = {
                                                  //|   "name" : "Hamburger",
                                                  //|   "calories" : 3000,
                                                  //|   "description" : "złe jedzenie",
                                                  //|   "isJunk" : true,
                                                  //|   "ingredient" : {
                                                  //|     "name" : "mięso",
                                                  //|     "calories" : 3000
                                                  //|   }
                                                  //| }

I w drugą stronę

Jak do zamiany obiektu w JSONa jest "Writes" tak do zamiany na odwyrtkę jest "Reads". Tutaj będzie trochę inaczej bo korzystamy z lekko innego api. "Writes" też można tworzyć tym innym sposobem co być może dałoby lepsze wrażenie spójności ale to mapowanie przez strzałki, którego użyliśmy powyżej jest prostsze.

//najpierw taki dziwny import bo będziemy korzystać z innego api
import play.api.libs.functional.syntax._

//i tera
implicit val ingredientReads: Reads[Ingredient] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "calories").read[Int]
 )(Ingredient.apply _)                     //> ingredientReads  : play.api.libs.json.Reads[controllers.writers.Ingredient]
                                                  //|  = play.api.libs.json.Reads$$anon$8@503dbd9a

   //i wyłuskiwać naszą klasę można
  (json \ "ingredient").as[Ingredient]      //> res0: controllers.writers.Ingredient = Ingredient(mięso,3000)
  //i walidować
  (json \ "ingredient").validate[Ingredient]      //> res1: play.api.libs.json.JsResult[controllers.writers.Ingredient] = JsSucce
                                                  //| ss(Ingredient(mięso,3000),)

Tutaj walidacja była poprawna ale gdyby coś się popsuło to można tak zadziałać :
  val zlyJson=Json.obj{"zly"->"json"}             //> zlyJson  : play.api.libs.json.JsObject = {"zly":"json"}

  (zlyJson \ "ingredient").validate[Ingredient] match {
   case s:JsSuccess[Ingredient] => s"dobre ${s.get}"
   case e:JsError => s"złe ${JsError.toFlatJson(e)}"
  }                                               //> res2: String = złe {"obj.name":[{"msg":"error.path.missing","args":[]}],"o
                                                  //| bj.calories":[{"msg":"error.path.missing","args":[]}]}

Jeszcze dokładniejsza walidacja

implicit val ingredientReads: Reads[Ingredient] = (
  (JsPath \ "name").read[String](minLength[String](10)) and
  (JsPath \ "calories").read[Int](min(1000) keepAnd max(8000))
 )(Ingredient.apply _)

//I niestety "hamburger się nie załapał"
(json \ "ingredient").as[Ingredient]      //> play.api.libs.json.JsResultException: JsResultException(errors:List((/name,
                                                  //| List(ValidationError(error.minLength,WrappedArray(10))))))

Makra

I tutaj te wszystkie napisane Writery i Readery można zastąpić takim oto zestawem jednolinijkowców.

 object JsonImplicits{
   case class Ingredient(name:String,calories:Int)
   case class Meal(name:String,calories:Int,description:String,isJunk:Boolean,i:Ingredient)
   implicit val macroIngredientFormat=Json.format[Ingredient]
   implicit val macroMealFormat=Json.format[Meal]
 }
  
  import JsonImplicits._
To automatycznie generuje Writes i Reads makrami na podstawie Definicji Klas. Kiedy to testowałem to aby działało w Worksheet musiałem owe makra zamknąć w zewnętrznym obiekcie, zaimportować i wtedy trybi. Może tak to musi działać aby makra zaskoczyły a być może muszę się po prostu wyspać.

No to jakaś aplikacyjka w Playu

object Application extends Controller {
 def index = Action {
  Ok(views.html.index("Your new application is ready."))
 }

  import play.api.libs.json.Json

  implicit val mealFormat=Json.format[Meal]

  def list = Action {
    Ok(Json.toJson(MealDatabase.data))
  }
  
  def oneByName(n:String)=Action{
    MealDatabase.data.find(_.name.toLowerCase==n).map{foundMeal=>
     Ok(Json.toJson(foundMeal))
    }.getOrElse(BadRequest(s"no meal with name ${n}"))
  }
  
  def add=Action(parse.json){request=>
    val meal=request.body.as[Meal]
    MealDatabase.add(meal)
    Created(s"added ${meal.name}")
  }

}

case class Meal(name:String,calories:Int,description:Option[String])

object MealDatabase{
 var data=List(Meal("HotDog",2500,Some("Parówa w bule")),Meal("Pizza",4000,None))
 
 def add(m:Meal)=data=m::data
}
No i Testy
"Application" should {

    "return meals list" in new WithApplication{
      val jsonResult = route(FakeRequest(GET, "/list")).get
      
      contentType(jsonResult) must beSome.which(_ == "application/json")
      contentAsJson(jsonResult) \\ "name" must containAllOf(Seq(JsString("HotDog"),JsString("Pizza")))
    }
    
    "return one meal by name" in new WithApplication{
      val jsonResult = route(FakeRequest(GET, "/meal/pizza")).get
      
      contentType(jsonResult) must beSome.which(_ == "application/json")
      contentAsJson(jsonResult) \ "name" must beEqualTo(JsString("Pizza"))
    }
    
    "add new meal to list" in new WithApplication{
      val inputJson=Json.obj("name"->"pomidorowa","calories"->5000)
      val addingResult = route(FakeRequest(POST, "/add").withJsonBody(inputJson)).get
      
      status(addingResult) must beEqualTo(CREATED)
      contentAsString(addingResult)  must beEqualTo("added pomidorowa")
    }
  }

I teraz w planach jest, iż zostanie trochę czasu aby przełączyć się na naukę wywołania webserwisów... co opiszę w następnym odcinku. W sumie jak ludzie będą chcieli to można jechać JSONa przez całe spotkanie - grunt by było ciekawie bo tylko wtedy nauka ma sens. Z drugiej strony wywołania asynchroniczne i działanie na abstrakcjach, które symbolizują coś co się jeszcze nie wydarzyło i zmaterializuje się w przyszłości jest magiczne samo w sobie... i znowu wielokropek.

*   *   *

środa, 3 września 2014

Neurologia Pair Programming

"Programowanie w parze" jeszcze kilka lat temu szło (a być może wciąż tu i tam idzie) wbrew intuicyjnej wiedzy managerskiej zapożyczonej wprost z fabryki pralek. Bo gdzie jest profit jak dwa risorsy robią pracę jednego risorsa?

Z jednej strony są relacje programistów, którzy mówią iż czasem "co dwie głowy to nie jedna" a z drugiej jest jakiś irracjonalny wzór na efektywność mierzoną ilością wciśniętych klawiszy w jednostce czasu. Kiedyś nawet na JUGu w Łodzi był koleś z prezentacją jakiegoś chorego pluginu do eclipse zliczającego akcje programisty w przeciągu godziny.(Zastanawiałem się czy o tym w ogóle wspominać by jakiś managerski "mlody wilku" nie poczuł inspiracji).

Być może poniższy tekst doda trochę siły naukowej do faktu, iż czasem siedzenie we dwóch przy jednym kompie może być wnieść coś więcej niż tylko "programista razy 2"

Sieć zadaniowa - co się dzieje gdy siedzimy sami nad kompem

Gdy sami siedzimy zagłębieni w problemie aktywowana jest sieć : http://en.wikipedia.org/wiki/Task-positive_network

  • Zalety : skupiamy się na jednej konkretnej rzeczy i rozwiązujemy konkretny problem.
  • Wady : jesteśmy zamknięci na nowe pomysły i sposoby podejścia do danego problemu - a to dlatego, że w danej chwili aktywna jest tylko jedna sieć.
  • Komentarz : oczywiście mogą pojawić się głosy ludzi twierdzące, że to bez znaczenia ponieważ programista ma tylko przepisać diagram narysowany przez kogoś z bardziej egzotyczną nazwą stanowiska. Owe osoby zapytane "skoro to tylko kwestia przepisania diagramu to dlaczego nie mogą tego robić automaty" - odpowiadają pomidor

Sieć społeczna - co się dzieje gdy siedzimy w parze

Teraz powinna działać cześć sieci : http://en.wikipedia.org/wiki/Task-negative A w celu uzupełnienia informacji można też zerknąć na :
  1. http://en.wikipedia.org/wiki/Default_mode_network
  2. http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3380415/ - tu jest dużo i naukowo.
  • Zalety : otwieramy się na nowe pomysły i możliwości rozwiązania danego problemu.
  • Wady : trudniej jest nam się skupić na jednej rzeczy i zanalizować konkretny problem dokładniej i dogłębniej.
  • Komentarz : oczywiście mogą pojawić się głosy ludzi twierdzące, że to bez znaczenia ponieważ programista ma tylko przepisać diagram narysowany przez kogoś z bardziej egzotyczną nazwą stanowiska. Owe osoby zapytane "skoro to tylko kwestia przepisania diagramu to dlaczego nie mogą tego robić automaty" - odpowiadają pomidor
Poniżej default mode network to zdaje się zielone i niebieskie.

Podsumowanie

Rzecz jasna nie było bezpośrednich badań nurkujących wprost do głów programistów w sytuacjach niezwykle fascynującej codzienności życia IT. Powyższy opis jest jedynie interpretacją obecnych faktów naukowych ale być może przyda się jako argument do dyskusji (jeśli w jakichś firmach takie dyskusje wciąż trwają) odnośnie produktywności programowania w parach.

Należy zerwać z błędną wizją "dwóch risorsów" przy jednym komputerze - a zaprosić nowe przekonanie, że programiści czy to pracując sami w słuchawkach czy też żywo dyskutując w N osób - pracują tak naprawdę w różnych trybach przydatnych do rozwiązywania różnych problemów.

oczywiście mogą pojawić się głosy ludzi twierdzące, że to bez znaczenia ponieważ programista ma tylko przepisać diagram narysowany przez kogoś z bardziej egzotyczną nazwą stanowiska. Owe osoby zapytane "skoro to tylko kwestia przepisania diagramu to dlaczego nie mogą tego robić automaty" - odpowiadają pomidor

Informacje do tego artykułu napotkałem w nowym kursie na courserze - Inspiring Leadership through Emotional Intelligence. Na początku myślałem, że to będzie standardowy badziew managerski w stylu "klep ludzi po ramieniu, mów good job, pamiętaj o urodzinach, jak trzeba bądź surowy i stanowczy i kłam, że release jest miesiąc wcześniej niż w rzeczywistości". A tu się okazało, że koleś ciekawie nawija o neurologi co jest o tyle interesujące, że trudno filozofować o neurologii bo to jest tak jak z kompilacją kodu - jak działa to działa a jak nie to nie.

*   *   *

wtorek, 26 sierpnia 2014

Expression problem nie tylko dla orzełów

Zagadnienie, które będzie poniżej plastycznie opisane kwiecistym językiem ma się tak do codziennego programowania jak zawody formuły 1 do jazdy osobówką po mieście. Niby nie widzimy na ulicach formułek a jednak stanowią one ciekawy poligon dla rozwiązań technicznych, które po pewnym czasie lądują w normalnych samochodach

Z drugiej jednak strony fakt, że nie mam pojęcia o zawodach formuły 1 może sprawić, że powyższe porównanie jest nieprawdziwe i bez sensu. Tak czy inaczej najtrudniejszą część - czyli wstęp - mam już za sobą i można jechać dalej...

Więcej niż "polimorfizm"

Przez lata moje rozumienie słowa "polimorfizm" sprowadzało się do "jest interfejs i jego kilka implementacji" i było swoistym uwiecznieniem dobrego projektu obiektowego. Pierwsza ciekawa nowinka(dla mnie) - można to samo uzyskać bez żadnego interfejsu zwykłymi generykami - a dwa - nie ma się czym podniecać bo jak wszystko w życiu ma to swoje wady i zalety.

Na wzmiankę o wadach takiego rozwiązania natrafimy w miejscu, którego się nie do końca spodziewamy - czyli w rozdziale zdaje się szóstym "Clean Kodu" o Obiektach i Strukturach danych. Właśnie tam opisany jest ciekawy przypadek kiedy to użycie niesławnego instanceof może nam bardziej pomóc niż zaszkodzić. Gdy mamy sytuację gdzie dochodzą dochodzą nam nowe typy to rozwiązanie z interfejsem ładnie w to wchodzi ale już przy nowych operacjach, na typach jednak wbrew intuicji i powszechnej poprawności projektowej - instance of sprawdza się lepiej.

I to jest moment na wzmiankę o ...

Expression problem

Genezę problemu można sobie znaleźć tutaj -> http://en.wikipedia.org/wiki/Expression_problem lub tutaj : Expression Problem. Co więcej możemy legalnie ściągnąć profesjonalną pracę naukową samego Oderskiego -> PDF Oderskiego

Niestety albo stety prace naukowe mają do siebie, że brak tam rysunków i animacji rodem z "head first" albo też multimedialnych książek kucharskich. Aby więc było łatwiej zamiast używać oryginalnego problemu z rozwijaniem języka wyrażeń naukowych skupimy się na przykładzie z "Clean Code" - kółka i kwadraty (bo "trójkąty i kwadraty" to już chyba będzie plagiat).

  • M1 - tworzymy interfejs kształt i klasę kółko
  • M2 - dodajemy nowy typ kwadrat - tu nie ma schodów
  • M3 - dodajemy nową operację na kółku - tu są schody
  • M4 - a tutaj tę nową operację dodajemy też do kwadratu

M1

//1
object ExpressionsBlog extends M1{
  //2
 val circle:Shape=new Circle(5)            //> circle  : com.wlodar.expressions.poligon.ExpressionsBlog.Shape = com.wlodar.
                                                  //| expressions.poligon.nablog.M1$Circle@72067564
}

package nablog{
 //3
 trait M1{
  
  trait Shape{
   def area:Double
  }
  //4
  class Circle(r:Int) extends Shape{
   def area=Math.PI*r*r
  }
  //5
  def sum(s1:Shape,s2:Shape)=s1.area+s2.area
 }
}

  1. Traity w Scali najłatwiej wytłumaczyć porównując je do interfejsów w Javie ale tutaj widać także ich trochę inną naturę - w tym miejscu trait M1 wzbogaca moduł/obiekt ExpressionsBlog w nowe zachowanie i nowe typy(tutaj Shape i przykładowa implementacja Circle)
  2. Ponieważ kompilator Scali sam zgaduje typ zmiennej, więc aby uogólnić ów typ do Shape musimy jawnie zadeklarować kółko jako właśnie Shape
  3. Mamy dwa traity : M1 i Shape - widzimy (mam nadzieję) także dwoistą naturę tego konceptu - "Interfejs" i "Moduł". Możemy sobie wewnątrz deklarować klasy itd itp
  4. Klasa jak klasa - standardowe rozszerzenie Shape z implementacją metody
  5. A tutaj mamy metodę sum, która będzie służyła do ilustracji kilku ciekawych faktów i eksperymentów okołopolimorfizmowych.

M2

//1
trait M2 extends M1{
  class Square(a:Int) extends Shape{
   def area=a*a
  }
 }

//2
trait M2Rectangle extends M2{
  class Rectangle(a:Int,b:Int) extends Shape {
   def area=a*b
  }
 }
  1. Dodanie nowego typu jest naturalne i nie wymaga żadnego "wzorca" - co podkreślam by czytelnik dopuścił do siebie fakt, iż wzorzec pomimo "efektu aureoli" cudnego rozwiązania może być zwykłym nadmiarowym szumem
  2. No i Traity można rozwijać niezależnie
//1
object ExpressionsBlog extends M2 with M2Rectangle{  
 val circle:Shape=new Circle(5)            //> circle  : com.wlodar.expressions.poligon.ExpressionsBlog.Shape = com.wlodar.
                                                  //| expressions.poligon.nablog.M1$Circle@57a220c2
   //2                      
 val square=new Square(3)                  //> square  : com.wlodar.expressions.poligon.ExpressionsBlog.Square = com.wlodar
                                                  //| .expressions.poligon.nablog.M2$Square@23557525
 //2
 val rectangle=new Rectangle(3,4)          //> rectangle  : com.wlodar.expressions.poligon.ExpressionsBlog.Rectangle = com.
                                                  //| wlodar.expressions.poligon.nablog.M2Rectangle$Rectangle@164af41d
 
}
  1. Przykład dodania dwóch traitów do modułu
  2. Dzięki temu mamy dostęp zarówno do kwadratu jak i prostokąta

M3

//1
trait M3 extends M1{
//2
  trait Shape extends super.Shape{
   def perimeter:Double
  }
//3
  class Circle(r:Int) extends super.Circle(r) with Shape{
   def perimeter=2*Math.PI*r
  }
 }
  1. Przy dodawaniu nowej operacji zaczynają się schody - nie da się wykonać tego tak naturalnie jak w przypadku dodawania nowych typów
  2. Aby zrodzić iluzję dodanej metody przesłaniamy trait Shape z M1
  3. To samo robimy z klasą ale tutaj jeszcze dodatkowo implementujemy przesłonięty Shape
//1
object ExpressionsBlog extends M3 with M2Rectangle{
  
 val circle:Shape=new Circle(5)            //> circle  : com.wlodar.expressions.poligon.ExpressionsBlog.Shape = com.wlodar.
                                                  //| expressions.poligon.nablog.M3$Circle@331f9cee
   //2
   circle.perimeter                               //> res0: Double = 31.41592653589793
   circle.area                                    //> res1: Double = 78.53981633974483
 
}
  1. Cały czas implementujemy dwa niezależne rozszerzenia M1 - za chwilę stanie się z tym coś ciekawego.
  2. Kółko dostało obydwie metody area i perimeter

M4

//1
trait M4 extends M2 with M3{
//2
  class Square(a:Int) extends super.Square(a) with Shape{
   def perimeter=4*a
  }
 }
  1. Scalamy oba kierunki w jeden moduł - tego chyba nie da się zrobić w Javie - nawet ósemce z "defaultowymi metodami" bo jest konflikt nazw
  2. Standardowe Przesłoniećie klasy - klasę bazową bierzemy z jednego modułu a nową operację z drugiego
Niby problem się rozwiązał ale teraz zaczną się dopiero ciekaw rzeczy....

Niebezpieczeństwo

W opracowaniu Oderskiego wykorzystany jest dodatkowo typ abstrakcyjny- na początku zupełnie nie rozumiałem po co skoro wszystko działało i bez tego. No i to był problem bo ... działało za bardzo. Zerknijmy na to co jest w M1

 trait M1{
  //....
  def sum(s1:Shape,s2:Shape)=s1.area+s2.area
 }

I teoretycznie mogę tutaj przekazać coś co implementuje Shape z M1 jak i Shape z M3. Ale co jeśli to jest niedobre, bardzo bardzo złe i nie chcę tego? No to teraz czas włąśnie na...

Typ Abstrakcyjny

Aby pokazać pełną abstrakcję typu abstrakcyjnego nazwiemy go pomidor
trait M1 {
    //...
    //1
    type pomidor <: Shape
    //2
    def sum(s1: pomidor, s2: pomidor) = s1.area + s2.area
  }
  1. Najtrudniejszym elementem w tym miejscu jest chyba ten znaczek : "<:" wzięty prosto z gry PACMAN - a oznacza on zwyczajnie, że pomidor będzie podtypem kształtu.
  2. Nasza metoda nie przyjmuje teraz kształtów ale dwa pomidory. Wydaje się, że za wiele zmian nie zobaczymy w porównaniu do zwykłego dziedziczenia ale teraz tak naprawdę mogę dokładnie sterować czym jest pomidor - i jak będę chciał to nie będzie miał nic wspólnego z Shape z M1
rait M3 extends M1 {
    //1
    trait Shape extends super.Shape {
      def perimeter: Double
    }
    //2
    type pomidor = Shape
    //....
  }
  1. Pamiętamy, że Shape z M1 jest przesłonięty przez nowy Shape
  2. I ustalamy, że pomidor to właśnie nasz nowy Shape czyli to co nie implementuje Shape z M3 nie ma wstępu do metody
Czyli to się nie skompiluje :
object Testy extends M4 with M2Rectangle {
  def test = {
    val circle: Shape = new Circle(5)
    val rec = new Rectangle(3, 4)
//rec i circle to dwa różne szejpy!
    sum(circle, rec)
  }
}

Polimorfizm inaczej

Metoda sum zdefiniowana w M1 dodaje pola dwóch figur. Stwórzmy sobie sztuczny problem i załóżmy, że po dodaniu nowej metody w M3 chcielibyśmy mieć kontrolę nad tym jak to sum jest liczone :

trait M1 {
   //....
    type pomidor <: Shape
//2
    trait Summarizer{
     def add(s1:pomidor,s2:pomidor):Double
    }
    object AreaSummarizer extends Summarizer{
     def add(s1:pomidor,s2:pomidor)=s1.area+s2.area
    }
//1
    def sum(s1: pomidor, s2: pomidor)(s:Summarizer) = s.add(s1,s2)
  }
  1. Definiujemy taką niby strategię - sposób dodawania pomidorów jest zdefiniowany poza metodą sum
  2. Summarizer to zwykły interfejs
I teraz w M3 mogę sobie zdefiniować sumowanie po obwodzie :
 trait M3 extends M1 {
//................................
    type pomidor = Shape   
    object PerimeterSummarizer extends Summarizer{
     def add(s1:pomidor,s2:pomidor)=s1.perimeter+s2.perimeter
    }
  }

sum(circle, sq)(PerimeterSummarizer)
Jest jeden mały problem - API troszeczkę ssie pałkę. Zobaczmy co można z tym zrobić :
//1
def sum(s1: pomidor, s2: pomidor)(implicit s:Summarizer) = s.add(s1,s2)
//2
implicit val summarizer=PerimeterSummarizer
//3
sum(circle, sq)
  1. Dodajemy słówko "implicit" co oznacza, że summarizer będzie wyszukiwany przez kompilator i jak go znajdzie to sam go wstawi jako parametr (a jak nie to będzie błąd). Uwagę czytelnika niech zwróci także druga para nawiasów.
  2. Gdzies tam w module deklarujemy domyślnego summarizera
  3. API jest znowu czyste - jak chcemy to cały czas możemy przekazać kolejny argument, który będzie różny od implicit
I uwaga można z tym iść jeszcze dalej :
trait M1 {
    //....
    type pomidor <: Shape
    
//1
    trait Summarizer[A]{
     def add(s1:A,s2:A):Double
    }
//2
    object AreaSummarizer extends Summarizer[pomidor]{
     def add(s1:pomidor,s2:pomidor)=s1.area+s2.area
    }
//3
    def sum[A](s1: A, s2: A)(implicit s:Summarizer[A]) = s.add(s1,s2)
  }
  1. Summarizer ma generyka A
  2. AreaSummarizer określa generyka A jako pomidor
  3. I sum ma tez generyka A
Ej i teraz obadajcie to :
trait M3 extends M1 {
//....
    type pomidor = Shape
//1
    implicit object PerimeterSummarizer extends Summarizer[pomidor]{
     def add(s1:pomidor,s2:pomidor)={println("pomidor");s1.perimeter+s2.perimeter}
    }
  }


 trait M2Rectangle extends M2 {
//2
  implicit val summarizer=new Summarizer[Rectangle]{
     def add(s1:Rectangle,s2:Rectangle)={println("rectangle");s1.area+s2.area}
    }
  
    class Rectangle(a: Int, b: Int) extends Shape {
      def area = a * b
    }
  }

def test = {
    val circle = new Circle(5)
    val rec = new Rectangle(3, 4)
    val sq = new Square(6)
    
    sum(circle, sq) // pomidor
    sum(rec, rec) // rectangle
  }

Normalnie kompilator sam wstrzykuje to co trzeba na podstawie typu! W "1" mamy obsługę Shape w "2" prostokąta ze starym shape z M1 i wio. Ale jest jeszcze bardziej pojechany mechanizm...

Typy Fantomowe

Najpierw ogólna koncepcja :
//1
package phantoms1{
 sealed trait NotValidated
 sealed trait Validated
 sealed trait Normal
 sealed trait Admin
 
 case class User[A,B]
}
//2
def validate[B](u:User[NotValidated,B])=user.copy[Validated,B]
def secret[B](u:User[Validated,B])=println("sekrety")

//3
val user=new User[NotValidated,Normal]
val validatedUser=validate(user)

//4
secret(validatedUser)                           //> sekrety
//5
secret(user)

  1. Deklarujemy serię traitów, która tak naprawdę nigdzie nie będzie deklarowana - dlatego własnie fantomy
  2. Tutaj jest istota przykładu - mamy taką niby walidację w czasie kompilacji poprzez sprawdzenie czy user ma odpowiednie generyki a może je mieć tylko wtedy jeśli przeszedł metodę "validate"
  3. Na początku user miał generyka NotValidated
  4. Jeśli przeszedł walidację i generyk zmienił się na Validated to wyswietli sekret
  5. A jak nie to wywali sie kompilacja
Jak to wykorzystać w głównym przykładzie?
trait Summarizer[A,B]{
     def add(s1:A,s2:A):Double
    }
    trait ZwyklySummarizer
    implicit object AreaSummarizer extends Summarizer[pomidor,ZwyklySummarizer]{
     def add(s1:pomidor,s2:pomidor)={println("shape z M1");s1.area+s2.area}
    }
    def sum[A](s1: A, s2: A)(implicit s:Summarizer[A,ZwyklySummarizer]) = s.add(s1,s2)

//////////
trait NiezwyklySummarizer
    implicit object PerimeterSummarizer extends Summarizer[pomidor,NiezwyklySummarizer]{
     def add(s1:pomidor,s2:pomidor)={println("pomidor z M3");s1.perimeter+s2.perimeter}
    }
////
implicit val summarizer=new Summarizer[Rectangle,ZwyklySummarizer]{
     def add(s1:Rectangle,s2:Rectangle)={println("rectangle");s1.area+s2.area}
    }

//
   sum(circle, sq) //shape z M1
    sum(rec, rec) //rectangle
No i "NiezwykłySummarizer" z M3 został olany i w jego miejsce wszedł "Zwykly" z M1. Przykład trochę na siłę ale stworzony w celach czysto edukacyjnych.

Podsumowanie

  • W programowaniu czeka na nas wiele ciekawych rzeczy
  • Warto czasem czytać referaty i opracowania naukowe i zwalczyć naturalną niechęć do tego typu prac wykształcony w nas przez system szkolnictwa polskiego.
  • a tu jest cały kod
*   *   *

niedziela, 10 sierpnia 2014

Funkcje częściowe i uczulenie na instanceof

To będzie wpis kontynuujący zachętę do stosowania do szukania nowych rozwiązań "Game of life" na coderetreat. Będzie także o wyciąganiu pochopnych wniosków.

Kompozycja funkcji częściowych

By nie tracić czasu i się nie potwrzać --->Tutaj<--- jest link do opisu co to jest coderetreat.

A co do dzisiejszego mięska. Funkcje częściowe w Scali i (chyba nie tylko) zwracają jakiś sensowny rezultat tylko dla określonych argumentów ale zamiast samemu pisać ify sprawdzające parametry wywołania - jest to naturą samej funkcji częściowej iż taki "if" istnieje niejako w niej samej.

I tak poniżej mamy dwie funkcje częściowe, jedna dla żywej komórki a druga zaś dla martwej. Magiczny patent dzieje się w linii 69 gdzie obydwie funkcje łączymy w jedną w taki sposób, że jak jedna nie może obsłużyć argumentu to spróbuje zrobić to druga. Jak ktoś lubi wzorce to to jest taki "chain of responsibility" (w wersji dla dresa : "złoty łańcuch zobowiązań")

//tutaj mamy taki mocno naciągany closure ale na potrzeby przykładu styka
    val liveNeighbours=4   
 
 def liveCellEvolution:PartialFunction[Cell,Cell]={
  case cell:LiveCell if (liveNeighbours==2 || liveNeighbours==3) =>LiveCell()
  case cell:LiveCell => DeadCell()
 }                                         

 def deadCellEvolution:PartialFunction[Cell,Cell]={
  case cell:DeadCell if (liveNeighbours==3) => LiveCell()
  case cell:DeadCell => DeadCell()
 }    

//linia numer 69
    val cellEvolution=liveCellEvolution orElse deadCellEvolution

Jeśli będziemy chcieli coś zmienić w logice ewolucji komórek wystarczy zrobić ooo tak :


val newCellEvolution=newLiveCellEvolution orElse cellEvolution
//
Czy coś tam w ten deseń.

I gdzieś tam hen hen jest ogólna niezmienna logika ewolucji :

def evolve(evolution:Cell=>Cell){
    cells.map(evolution)
 } 
Znowu jak ktoś bardzo chce to może powiedzieć, że jest zastosowany wzorzec strategia (I mamy kolejny wzorzec przy wykorzystaniu tylko jednego mechanizmu funkcji)

Płachta na byka

Działa powyższy mechanizm nawet fajnie i ciekawie ale jest i druga strona medalu. Powyżej zastosowałem taki skrócony zapis funkcji częściowej - pełna wersja wyglądałaby tak :


val liveCellEvolution=new PartialFunction[Cell,Cell]{
  def apply(c:Cell)=if(liveNeighbours==2 || liveNeighbours==3) LiveCell() else DeadCell()
  def isDefinedAt(c:Cell)=c.isInstanceOf[LiveCell]
 }  

Ahaaaaa i oto mamy profanację jego dostojności OOP poprzez bluźnierstwo w postaci InstanceOf. w sensie, że moim zdaniem akurat tutaj nie ma w tym nic złego ale jak rok temu jeszcze w Javie pokazałem podobny przykład to poleciały pomidory i na widły mnie chcieli nabić·

Skąd się bierze taka wrogość do "instanceOf". Wydaje mi się, że łatwo to wytłumaczyć bo sam to miałem.

Rekonstrukcja zdarzeń

Wszystko zaczyna się na uczelni gdzie skutkiem kombinacji i przypadków można przejść do 3 czy 4 roku (a nawet skończyć studia) pisząc cały czas programy w main. Istnienie takiej konstrukcji jak Interfejs pozostaje zagadką gdyż studentowi bez odpowiedniego doświadczenia trudno zrozumieć po co jest "klasa", która nie ma metod i w zasadzie nic nie robi?

Potem może być etap 2 kiedy to odkrywa się "polimorfizm w Javie" i odchodzi od metod z setkami ifów w kierunku ukrywania implementacji za interfejsem - o na przykład tak(po staremu) :

for (Invoice invoice : invoices) {
   invoice.process(cosTam)
}
I pewnego dnia wzrok nasz może zaatakować taka konstrukcja :
if(invoice instanceof TakiInvoice){
   //...
  }else if(invoice instanceof SrakiInvoice){
  //.... 
  }
No i wiadomo, ze w pierwszym przypadku jest dobrze bo nie trzeba zmieniać kodu jak dodamy nowe typy a w drugim już jest w tym temacie gorzej bo kod modyfikować by trzeba.

Tutaj można wyciągnąć pochopny wniosek, że istnienie "instanceof" oznacza brak polimorfizmu = a to tak nie do końca bo być może...

Szukamy nie tu gdzie trzeba

Zazwyczaj na coderetreat w rozwiązaniu z nutką obiektowości dochodzi się do rozwiązania gdzie mamy dwie komórki : LiveCell i DeadCell rozszerzajace jakiś wspólny interfejs czy klasę abstrakcyjną. Czyli coś takiego :

W przedstawionym na początku przykładzie nie ma czegoś takiego ale ale.... polimorfizm jest w innym miejscu. Przejawia się on niejako pod postacią operacji na typach a nie na typach samych w sobie.

No i ponownie nie ma się co spinać tylko zaobserwować różnice i wybrać co lepsze w danej sytuacji. A jak ktoś jest uczulony na samo słówko "instanceOf" to może sobie wybrać formę zapisu, która to ukrywa i po kłopocie :)

A no i jest jeszcze efekt krugera dunninga - jak już ktoś przeczyta "head first design patterns" i odkryje polimorfizm to może mu się wydawać, że jest na końcu drogi - kiedy tak naprawdę znajduje się na początku...

Mobilization call for papers

Mobilization 2014 call for papers --> Jest tuaj

niedziela, 27 lipca 2014

Rozszerzenia do "Coderetreat" i "Game of Life"

Global Day of Coderetreat stanowi niesamowitą pomoc w sianiu eksplozji wiedzy w kierunku jednostek programistycznych wciąż uwięzionych w rozwiązywaniu problemów programistycznych przy pomocy tandemu for-if (powtórz n razy). Aby było łatwiej zachęcić ludzi do udziału wydarzenie odbywa się w porze roku kiedy najłatwiej odpalić samochód a wszyscy są opaleni i wypoczęci - czyli na przełomie listopada i grudnia.

Aby nie robić kolejnego CRUDa pracujemy z ciekawszym problemem czyli Grą w życie (nie mylić z "grą życie", którą uprawiamy na codzień). I chociaż ludzie czasem narzakają, że ciągle jest to samo ćwiczenie i można by wrzucić coś nowego to jednak co roku scenariusz przebiegu zdarzeń mówi co innego.

Najpierw większość ludzi jedzie proste rozwiązanie z tablicą dwu wymiarową i wali w komórki albo booleany albo inty. Później mamy trudną do przetestowania gigantyczną metodę main lub kilka mniejszych metod z (niestety) wszędzie rozsianymi iteracjami po tablicach. To czego przez ostatnie dwa lata prowadzenia coderetreat nie udało mi się wyjaśnić to to to to, że rozwiązanie samo w sobie nie jest istotne - właśnie dlatego je wywalamy na końcu (oj jak tego ludzie nie lubią!).

Ważna jest obserwacja, ważny jest proces nauki, ważne jest wyciągnięcie wniosku i zrozumienie jak dana decyzja wpływa na całokształt rozwiązania i dalsze decyzje. Np. fajne byłoby olśnienie, że jeśli każde miejsce kodu jest świadome tego, że operujemy na tablicy dwuwymiarowej to jakakolwiek próba zmiany tego konkretnego aspektu będzie boleć.

Poza Object Calisthenics

Object Calisthenics(link) to zbiór ciekawych ograniczeń, których nazwy nich*ja nie mogę zapamiętać. Ograniczeń - bo formuła ćwiczeń jest dosyć ciekawa - zamiast narzucać (no dobra trochę się narzuca), że dane rozwiązanie jest lepsze - staramy się zaprogramować rozwiązanie z ograniczeniem np."metody maks 5 linii" i pomóc uczestnikom ćwiczeń samemu wyciągnąć wnioski.

Do niedawna Object Calisthenics to była taka obiektowa nirwana ale ale jak już i w javie pojawiły się "strzałki" to warto zerknąć na inne nirwany i zapoznać się z mechanizmami, których w javie do tej pory nie było albo jeszcze nie ma.

Wyrzucić długopisy i ołówki

... i przesiąść się do painta (nie nie to tylko żart prowadzącego).

Generalnie wiele zespołów na początku dużo dyskutuje o rozwiązaniach i nic a nic nie koduje :( i chyba dlatego wymyśloną sesję "nic nie gadamy tylko piszemy". A można zacząć inaczej. Zastanówmy się co jest potrzebne na samiutkim początku? Mamy komórki i jest też jakaś plansza. I tyle wystarczy by zacząć

trait GameOfLifeBlueprint{
 type Board
 type Cell
}

Słówko "type" należy czytać "jest sobie jakiś typ" (w sensie abstrakcyjnym a nie w sensie "patrz na niego ale typ"). I tyle - decyzje co to będzie za typ odkładamy na później...

I tak dalej

No dobra komórki są gdzieś położone na planszy. Przydałoby się je jakoś namierzyć

trait GameOfLifeWithCoordinates extends GameOfLifeBlueprint{
 type Coordinates
 def findCell(c:Coordinates)(implicit b:Board):Cell
}

I już mamy planszę, komórki i współrzędne a cały czas nie wiadomo co tam siedzi - i to jest poniekąd dobre bo mamy wciąż swobodę podejmowania decyzji. No i podejmujemy decyzje. Zamiast bawić się z tablicami przestrzeń za symuluje nam mapa. Kto chce 2D ma 2D a kto DDD ten ma 3D.

trait MapBasedGame extends GameOfLifeWithCoordinates{
 type Board=Map[Coordinates,Cell]
 val board:Board=Map[Coordinates,Cell]()
}

trait GameOfLife2D extends MapBasedGame{
 type Coordinates=(Int,Int)
}

trait GameOfLife3D extends MapBasedGame{
 type Coordinates=(Int,Int,Int)
}

To co jest jeszcze nie określone to Cell - no to wio

trait GameOfLifeWithStandardCells extends GameOfLifeWithCoordinates{
 trait GameCell
 case class LiveCell() extends GameCell
 case class DeadCell() extends GameCell
 type Cell=GameCell
 implicit val board:Board

 def lifeNeighbours(c:Coordinates)(implicit b:Board):Int
 
 def evolve(c:Coordinates):Cell=(findCell(c),lifeNeighbours(c)) match {
  case (DeadCell(),3)=>LiveCell()
  case (LiveCell(),2)=>LiveCell()
  case (LiveCell(),3)=>LiveCell()
  case _ => DeadCell()
 }
}

Przydałoby się ogarnąć jakieś zalecenia co i w jakiej kolejności zaleca się deklarować. W każdym razie powstało coś na wzór Metody Szablonowej także jak ktoś chce jakies wzorce projektowe to je ma.

No to gramy...

...ale jeszcze nie teraz bo nie chce psuć zabawy uczestnikom (a tak naprawdę nie chce mi się tego implementować bo jeszcze muszę dzisiaj spodnie sobie wyprasować). W każdym razie będzie to mniej więcej wyglądało tak :

object NormalGame extends GameOfLife2D with GameOfLifeWithStandardCells{
 def lifeNeighbours(c:Coordinates)(implicit b:Board):Int={...}
 def findCell(c:Coordinates)(implicit b:Board):Cell={...}
}

A Jak chcemy mieć więcej komórek to też łatwo idzie :

trait GameOfLifeWithZombieCells extends GameOfLifeWithStandardCells{
 case class ZombieCell() extends GameCell
 def zombieNeighbours(c:Coordinates)(implicit b:Board):Int
 
 override def evolve(c:Coordinates):Cell=(findCell(c),lifeNeighbours(c),zombieNeighbours(c)) match {
  case (DeadCell(),3,3)=>ZombieCell()
  case (LiveCell(),2,2)=>ZombieCell()
  case (LiveCell(),3,_)=>LiveCell()
  case _ => DeadCell()
 }
}

object ZombieGame3D extends GameOfLife3D with GameOfLifeWithZombieCells{
 def lifeNeighbours(c:Coordinates)(implicit b:Board):Int={...}
 def findCell(c:Coordinates)(implicit b:Board):Cell={...}
 def zombieNeighbours(c:Coordinates)(implicit b:Board):Int={...}
}

Jakie ograniczenie?

No i cała magia coderetreat polega na tym aby za bardzo nie wchodzić w teorię ale by dać ograniczenie i pozwolić każdemu na własne skórze odczuć uwagi i wnioski. Tutaj można dać "wszystkie typy abstrakcyjne" albo "jeden typ abstrakcyjny na trait" ale to na razie jest jedna wielka improwizacja z mojej strony także zobaczymy co przyszłość przyniesie.

Z kronik "Maintenance City"

Przypomniało mi się zabawne zdarzenie z 2011 kiedy jeszcze nie prowadziłem a tylko uczestniczyłem w CR. Siedzimy sobie i robimy pair programming no i nagle koleś się zawiesza. Pytam "co się dzieje" a ten prawie ze łzami w oczach "o kurde od roku chyba nie robiłem nowej klasy w Eclipse". No to pozdrowienia dla wszystkich we wspólnej walce na awans z low cost centre utopia na to co tam jest następne.

środa, 16 lipca 2014

Tragedia wspólnego kodu

Scenka1

Facet idzie sobie przez las i napotyka gościa wycinającego siekierą choinki
- Panie co pan robisz! Dlaczego dewastujesz las?
- Nie dewastuję lasu tylko zaciągam dług leśny. Będą święta, na choince powieszę bombki to rodzina będzie zadowolona. No a później posadzę nowe choinki w to miejsce.
- A kiedy to zrobisz?
- no za trzy sprinty...

Scenka2

Facet idzie sobie przez osiedle i spotyka gościa, który wywala gruz do piaskownicy
- Dlaczego dewastujesz plac zabaw?
- Nie dewastuję placu zabaw tylko zaciągam dług osiedlowy. Kładę teraz nową boazerię na ścianę i musiałem skuć trochę. Ale jak już skończę to obiecuję, że ogarnę cały plac zabaw.
- a kiedy to zrobisz?
- no za trzy sprinty...

Scenka3

Facet idzie sobie po piętrze w firmie i patrzy a tu gościu piszę metodę na 500 linijek i 20 ifów
- Panie co pan robisz! Dlaczego dewastujesz kod?
- Nie dewastuję kodu tylko zaciągam dług techniczny. Jest koniec sprinta i trzeba domknąć velocity bo inaczej nam targety polecą. Ale jak już to ogarniemy to potem poprawię.
- a kiedy to zrobisz?
- no za trzy sprinty...

Obecna metafora

Obecnie panująca metafora długu technicznego jest na pewno lepsza od braku jakiekolwiek metafory jako, że przecież dzięki niej w końcu ludzie nie do końca techniczni obdarowani ogromną mocą decyzyjną mogą w pewnych ramach mentalnych zrozumieć, że czasami jak działa to nie znaczy, że jest dobrze (a być może jest dobrze ale dobrze nie będzie).

Jednak pojawia się jednocześnie korpo-zagwostka : bo jak tu pokazać na zebraniu rady wydziałowej, że zaciągnęło się dług pragmatycznie, zainwestowało go pragmatycznie , spłaciło pragmatycznie i liczy zyski również pragmatycznie?

Oczywiście istnieje klasyczny algorytm rozwiązywania tego typu problemów : zmierz cokolwiek co generuje jakiś wykres i jedziemy z targetami - może na początek 120% pokrycia kodu?

Najlepszy tekst jaki do tej pory słyszałem to target : "zmniejszyć dług techniczny o 50%". No i oczywiście programiści nie mogą się z tego głośno śmiać bo mają do spłacenia kredyty, dziecku trzeba zeszyty kupić i psa zaszczepić. No i leci kabarecik. Kilka lat temu sam pisałem testy jednostkowe do 500 linijkowych metod z 30 ifami, które to metody napisali "ludzie, którzy już tu nie pracują" - target spełniony, pokrycie kodu zwiększone do 60% - nieustające pasmo sukcesów trwało.

Gdyby to nie wynikało jasno z powyższych rozważań to model "długu technicznego" pomimo swoich nieocenionych walorów edukacyjnych wydaje się na pod pewnymi względami a może i na dłuższą metę zwyczajnie ku*wa lewy. Jednocześnie istnieje być może coś ciekawszego, coś z zupełnie innej beczki...

Jak to wyszło na wyspach wielkanocnych

Cała historia jest opisana szczegółowo tutaj : Zjebka na wyspach wielkanocnych - a tak w skrócie to:

  1. Pomiędzy 400AD a 900AD na wyspy wielkanocne przybywają kontraktorzy z Polinezji i zaczynają nowy greenfield project
  2. Z czasem dzielą się na 12 teamów i zaczynają projekty budowy zajebistych takich dużych pomników głów
  3. Każdy z 12 managerów chce jebnąć najfajniejszą głowę z kamienia - bo generalnie im większa głowa wyrzeźbiona tym więcej respektu na wyspie,lepsze bonusy świąteczne i miejsce na parkingu bliżej wejścia.
  4. Aby przytachać kamienie na plaże gdzie znajdują się stanowiska pracy - używano ściętych drzew jako rolek (taki framework).
  5. Projekty idą zgodnie z planem - kolejne głowo-kamienie milowe są budowane
  6. Mija kilka wieków i na wyspie nie ma już żadnych drzew a co za tym idzie cały ekosystem poooszedł w pizdu.
  7. Na początku szesnastego wieku na wyspę przypływają Europejczycy i zastają garstkę ledwo żyjących mieszkańców (zdaje się, że około 3-10% populacji przetrwało)

A był to wstęp do szerszego zjawiska, które nazywa się..

Tragedia wspólnego pastwiska

Tutaj trzeba poruszać się ostrożnie bo powyższe zjawisko może być zbyt łatwo nad interpretowane "trzeba ludzi kontrolować bo się pozabijają" - co jak zobaczymy nie do końca o to tu chodzi

W 19 wieku angielski ekonomista zauważył, że jak chłopom dać wolną rękę to każdy maksymalizując swój zysk wprowadzi owce czy tam krowy na łąkę - a że każdy chce jak najbardziej nakarmić swoją zwierzynę to szybko wyeksploatują całe pole i będzie kicha dla wszystkich. Niby koleś miał trochę racji ale ważny jest tutaj kontekst sytuacji - otóż ci chłopi wcale nie byli właścicielami ziemi, z której korzystali. Lądem władał tzw. "Suweren" lub była zwyczajnie niczyja.

Własność prywatna - ale czyja?

Pod koniec 20 wieku kilku ekonomistów przypatrzyło się całej teorii i znaleźli pewną istotną zjebkę - otóż w orginale nie rozróżniano dwóch diametralnie różnych sytuacji : "korzystanie z ogólno dostępnego dobra" oraz "korzystanie z dobra będącego własnością wspólnoty". Okazuje się, że w tym drugim przypadku wspomniane wspólnoty ludzi jakoś same rozkminiały zasady korzystania z lasu,jeziora czy łąki. Rozwiązywali oni sami także tzw. "Efekt gapowicza": (Syndrom wesołego Cześka).

Jedna taka pani (Elinor Ostrom) przeprowadziła nawet dogłębne badania i obserwacje, w trakcie których zauważyła następujące niepokojące zjawisko :

  • Jest sobie las a obok lasu wieś
  • Mieszkańcy wsi korzystają z dóbr leśnych a jednocześnie jak ktoś przekombinuje to dostanie parę sęków gdyż mieszkańcy wsi tak po trochu uważają las za część wspólnoty
  • W państwie brakuje kasy
  • Las staje się tzw. "własnością publiczną"
  • Ludziom narzuca się reguły i mają w zasadzie po trochu już w dupie czy ktoś je łamie - od tego momentu to już bardziej sprawa indywidualnej etyki aniżeli społecznego współużytkowania wspólnego dobra

I kilka cytacików :

Ostrom demonstrated that, within communities, rules and institutions of non-market and not resulting from public planning can emerge from the bottom up to ensure a sustainable, shared management of resources, as well as one that is efficient from an economical point of view. Besides the village of Törbel, Ostrom shows examples of common lands in the Japanese villages of Hirano and Nagaike, the huerta irrigation mechanism between Valencia, Murcia and Alicante in Spain, and the zanjera irrigation community in the Philippines.

The first condition for the institutional basis of the success of these mechanisms is the clarity of the law (Who can do what? What can one not do? Who punishes whom? And how?). In addition to being clear, the rules must be shared by the community. This is why another essential element of self-government is the establishment of methods of collective and democratic decision-making, able to involve all users of the resource.

Furthermore, the mechanisms of conflict resolution must be local and public, so as to be accessible to all individuals of a community. Besides mechanisms of graduated sanctions, a mutual control of the resource among the users themselves must be established.

Więcej tutaj : http://www.aei.org/article/economics/elinor-ostrom-and-the-solution-to-the-tragedy-of-the-commons/

Jak to się ma do jakości kodu

Kilka obserwacji z życia wziętych :
  • Czasem - zwłaszcza na początku projektu - programista może skończyć prace nad taskiem szybciej implementując totalnie zjebany kod bez testów
  • Niska jakość kodu jebnie w cały zespół w przyszłości
  • Jeśli zespól jest poddany pseudonaukowym korpo-praktyką w stylu ocena performensu "risorsa" przez menadzera to pojawia się dodatnie sprzężenie zwrotne pomiędzy szybszym skończeniem taska a nagrodą
  • Jeśli zespół (a czasami trudno mówić o zespole bo ludzie są oceniani indywidualnie) ma narzucone reguły kodu to tak mentalnie z założenia widzi właściciela tych reguł poza zespołem
  • Jeśli właściciel kodu jest poza zespołem to można go traktować jako "dobro ogólno dostępne" z narzuconymi restrykcjami
  • Jest jeden wyjątek gdzie takie narzucone restrykcje są dobre ale jednak w praktyce widzę, że tworzy się często "korporacyjne reguły kodu" i wali po całości unifikacją. To tak jakby każdemu w korpo kazać nosić ten sam rozmiar buta.

Zastanówmy się nad taką metaforą - kod projektu jako wspólne dobro - ale wspólne dobro czego konkretnie? W lesie są drzewa, w morzu są ryby a na łące trawa (dla krów) - co jest w kodzie? W kodzie są szybkie postępy pracy, sukcesy projektowe, potencjał awansu i abstrakcyjny banknot pieniężny. Teoretycznie ktoś waląc haka na kodzie zagarnia te wszystkie rzeczy dla siebie utrudniając jednocześnie osiągnięcie tych samych rzeczy innym. - i jeszcze raz - Syndrom wesołego Cześka

A teraz zastanówmy się co by się stało gdyby tak zespół stał się właścicielem kodu i żaden szczebel menadżerski się w to nie wpierdalał? Tutaj nawiążemy do tego tekstu pisanego italikiem kilka akapitów wyżej :

  • Ostrom demonstrated that, within communities, rules and institutions of non-market and not resulting from public planning can emerge from the bottom up to ensure a sustainable, shared management of resources - czy zespół może "oddolnie" stworzyć reguły użytkowania kodu? Ano może
  • The first condition for the institutional basis of the success of these mechanisms is the clarity of the law (Who can do what? What can one not do? Who punishes whom? And how?). In addition to being clear, the rules must be shared by the community. - wspólne reguły kodu na które każdy w zespole się godzi? Tutaj będzie zaraz jedno ALE
  • Besides mechanisms of graduated sanctions, a mutual control of the resource among the users themselves must be established. - wypisz wymaluj code rewiu.

Ten model znacznie bardziej podoba mi się od porównywania pisania kodu do zaciągania "długu". Niby jest fajnie bo można do góry wysłać wiadomość, że teraz trzeba trochę posprzątać bo kilka placków gówna powstało z okazji dedlajnu. Z drugiej jednak zjawisko pracy z "długiem" raz, że sprawia, że niektórzy myślą, iż rozumieją z czym pracują a tak jest niekoniecznie. No a dwa to pomija ważne aspekty pracy grupowej jak choćby problem radzenia sobie z pasażerem an gapę - i po raz trzeci - Syndrom wesołego Cześka

Jedno małe duże ALE

Jest jedna taka sytuacja w życiu kiedy nawet najbardziej zagorzały przeciwnik wszelkich ograniczeń da się poprowadzić za rękę - jest to tzw "tutorial level" - kiedy nasza wiedza jest zerowa i zwyczajnie czujemy każdą komórką ciała, że czerpiemy wartość z wykonywania poleceń.

Tak jest oczywiście w teorii bo w praktyce działa też inny ciekawy efekt. Otóż np. taka Łódź do niedawna była głównie "maintenace city" i wiele osób siedziało po kilka lat wklejając tu i tam linijkę kodu aby zadziałało (R&D w Łodzi - Refactor and Deployment). Jak ktoś robi takie rzeczy kilka lat i widzi w ogłoszeniach o pracę "senior java developer wymagane 3 lata doświadczenia" to ma prawo pomyśleć , że on jest właśnie takim senior developerem i cały proces odbywa się raczej nieświadomie w ramach tzw. "warunkowania społecznego".

I teraz jak np. ktoś przychodzi do tego ludzika i próbuje go przekonać, że np trzy zagnieżdżone fory z piętnastoma ifami w środku nie są najlepszym pomysłem, to następuje wewnętrzny konflikt pojęciowy pomiędzy "uważam się za doświadczonego developera" a "okazało się, że moim kodem można straszyć dzieci jak nie będą chciały jeść zupki". Prowadzi to do tego, że wiedza o tym jak konkretne decyzja w kodzie wpływają na konkretne metryki i co tak naprawdę zyskujemy/tracimy poprzez stworzenie abstrakcji lub zagnieżdzenie ifa - ta wiedza niekiedy musi być wtłoczona w zespół lekko na siłę. Zakładając, że problem Cześka został wyeliminowany a reszta ludzi wykazuje "oświecony egoizm" -> http://en.wikipedia.org/wiki/Enlightened_self-interest - wtedy wszystko się ułoży. Oczywiście jestem świadom tego, że na mnie działają te same procesy - od czasu "funkcyjnego przebudzenia" staram się znajdować krytykę do każdego konceptu który badam.

To podobnie jak kiedyś miałem taką historię jak się dowiedziałem, że nie należy dokarmiać dzikich kaczek chlebem bo im puchnie to w brzuchy i implodują czy coś takiego. Czyli ludzie mogą chcieć dobrze ale z powodu braku wiedzy robią źle.

Także na dzień dzisiejszy : CZYSTY KOD=MODEL KODU JAKO DOBRA WSPÓLNOTY+ZESPÓŁ WŁAŚCICIELEM KODU + ODPOWIEDNIA ILOŚĆ NAPOMPOWANEJ WIEDZY O JAKOŚCI KODU

Epilog

Na pewno warto model kodu jako ograniczonego dobra wspólnego otwiera nowe ścieżki nazwijmy to "badań" aniżeli metafora "długu" i warto by się przyjrzeć schoćby temu : http://en.wikipedia.org/wiki/Socio-ecological_system - i przestać udawać, że problem jakości kodu sprowadza się do kilku metryk w sonarze i, że się poprawi po trzecim sprincie. A no i akurat na courserze zaczyna się kurs "social psychology" - https://class.coursera.org/socialpsychology-002

Trochę linków :