Chemiczny konkurs od DZone – rozwiązanie w Elixirze

Sierpień 12th, 2016

O ile rozwiązanie w Javie możecie sobie podejrzeć w repozytorium, a jego omówienie będzie w przyszłym tygodniu, to dziś pokażę, jak można rozwiązać zadanie w Elixirze.

W praktyce całość sprowadza się do kilku (dokładnie 22) linii kodu:

Listing 1. Rozwiązanie w Elixirze

defmodule Chemicals do
  import Enum, only: [map: 2, uniq: 1, min: 1, member?: 2]
  import String, only: [downcase: 1, split: 3 ]
  
  def main(args) do
      {_, [name|t], _} = OptionParser.parse(args)
      symbols = name |> downcase |> split("", trim: true )|> generateValidSymbols
      IO.puts member?(symbols, t|> List.first |> downcase )
      IO.puts length(symbols)
      IO.puts min(symbols)
  end

  def generateValidSymbols([head|tail]) when length(tail) == 1 do
      tail|> map( &(head  <> &1))
  end

  def generateValidSymbols([head |tail]) do
      symb = tail |> map( &(head  <> &1))
      uniq(symb ++ generateValidSymbols(tail))
  end

end

Kluczem są funkcje generateValidSymbols, które zwracają listę wszystkich poprawnych symboli dla podanej nazwy. To samo napisane w Javie (z zachowaniem tej samej logiki) to zdecydowanie więcej kodu. Co tu się dzieje?

Funkcja przyjmuje jako parametr listę liter z nazwy pierwiastka i od razu dzieli ją na pierwszy element (potocznie głowę – head) i resztę (ogon – tail). Jeżeli ogon ma długość 1, co weryfikuje strażnik, to elementy są łączone. Inaczej dla każdego elementu ogona tworzony jest symbol, a lista tak wygenerowanych symboli jest łączona z listą symboli wygenerowanych z ogona. Z tak powstałej listy usuwane są duplikaty. Następnie za pomocą kilku prostych testów sprawdzamy, czy symbol, podany jako drugi parametr, znajduje się na liście, jak duża jest lista, który symbol jest pierwszy w kolejności alfabetycznej.

A najwięcej czasu zajęło mi sprawdzenie jak to cholerstwo uruchomić, bo nie jest to takie oczywiste 🙂

Mały konkurs na DZone

Sierpień 8th, 2016

Dziś nic szczególnego. Małe ogłoszenie. DZone stworzyło konkurs/zabawę dla programistów. Wygląda zachęcająco. Rozwiązanie można wysyłać do jutra do 5 rano mniej więcej (11:59 EST).

Moje rozwiązanie będzie siedziało w repozytorium. Zatem jak ktoś nie ma zaplanowanego popołudnia, to już ma. Zachęcam do zabawy i dzielenia się swoimi rozwiązaniami.

Słowo o funkcjach anonimowych w Elixirze

Sierpień 5th, 2016

Na wczorajszym spotkaniu wroc-fp mieliśmy newsa w postaci „będzie grupa elixirowa” i tak przy okazji padło pytanie, jak działa operator & w elixirze.

Podstawy

Jak wiadomo, chociażby z tego artykułu, jedną z cech praktycznego programowania funkcyjnego jest możliwość przekazywania funkcji jako parametrów i zwracania ich jako wyników. Czasami potrzebujemy wykonać pewną operację przyjmującą jako argument funkcję, ale nie mamy odpowiedniej funkcji w API. W takim wypadku mamy dwie drogi. Pierwsza to utworzenie funkcji w module wraz ze wszystkimi tego konsekwencjami np. związanymi z metrykami, czy sposobem przekazywana funkcji jako parametru. Druga to przekazanie funkcji anonimowej.

Znak & jest powiązany z tą drugą ścieżką.

Funkcja anonimowa

Funkcja anonimowa w elixirze jest definiowana za pomocą konstrukcji fn -> end i może być przypisana do zmiennej:

Listing 1. Definiowanie funkcji anonimowej

iex> multipla = fn (a, b) -> a * b end
#Function<12.50752066/2 in :erl_eval.expr/5>
iex> multipla.(2, 2)
4

Wywołanie takiej przypisanej funkcji jest lekko dziwne, to przez kropkę, ale ma sens, jeżeli chcemy zasygnalizować, że multipla nie jest zwykłą nazwaną funkcją, a właśnie anonimową. Jak już wspomniałem, funkcję można przekazać jako parametr i zwrócić jako wynik:

Listing 2. Funkcja anonimowa jako parametr i wynik

iex>  b2 = fn (f2) -> fn (a) -> f2.(a, 2) end end
#Function<6.50752066/1 in :erl_eval.expr/5>
iex> multiplav2 = b2.(multipla)
#Function<6.50752066/1 in :erl_eval.expr/5>
iex> multiplav2.(2)
4

Działa i armaci. Jednak ten zapis ma pewną, drobną wadę. Jest słabo czytelny, gdy zaczynamy robić coś bardziej skomplikowanego.

Znak &

Odpowiedzią na problem czytelności jest użycie skrótowego zapisu wykorzystującego &:

Listing 3. Definiowanie funkcji anonimowej za pomocą &

iex> mlp = &( &1 * &2)
&:erlang.*/2
iex> mlp.(2, 2)
4

Mamy tu do czynienia z dwoma zastosowaniami znaku &. Pierwsze to oczywiście &(), które odpowiada fn -> end, czyli służy do zdefiniowania funkcji. Drugie to &1 i &2, które reprezentuje parametry funkcji. Jako że skrócona definicja funkcji nie zawiera parametrów nazwanych, to jasne jest, że musi istnieć jakiś sposób dostępu do nich. Zapis ze znakiem & jest takim właśnie sposobem.

Ograniczenia

Zapis ten ma pewne ograniczenia. Pierwszym jest brak możliwości stworzenia funkcji, która będzie wykonywać blok kodu:

Listing 4. & nie pozwala na „duże” funkcje

iex> &(
...> l =[&1, &2]
...> Enum.each(l , fn (el)-> IO.puts el end)
...> )
** (CompileError) iex: invalid args for &, block expressions are not allowed, got: (
  l = [&1, &2]
  Enum.each(l, fn el -> IO.puts(el) end)
)

iex> fn (a, b) ->
...> l = [a, b]
...> Enum.each(l, fn (el) -> IO.puts el end)
...> end
#Function<12.50752066/2 in :erl_eval.expr/5>

Jest to dobre ograniczenie. Dzięki niemu unikamy tego, co pojawiło się w Javie wraz z lambdami, czyli długich anonimowych bloków kodu nazwanych dla niepoznaki lambdami.

Drugim ograniczeniem jest brak możliwości zagnieżdżania się funkcji zapisanych za pomocą &:

Listing 5. Funkcje nie mogą się zagnieżdżać

iex> by2 = &( &( &1.(&1, 2)))
** (CompileError) iex: nested captures via & are not allowed: &(&1.(&1, 2))
    (elixir) src/elixir_fn.erl:114: :elixir_fn.do_capture/4

Oczywiście to też jest rozsądne, ponieważ nie mamy możliwości określenia, który parametr przynależy do której funkcji.

Podsumowanie

Podsumowując nasze dzisiejsze rozważania, możemy powiedzieć, że Elixir pozwala na definiowanie funkcji anonimowych. Mogą one być przypisane do zmiennej, mogą być parametrem, jak i wynikiem wywołania innej funkcji. Ze względu na intensywne użycie funkcji anonimowych mamy możliwość zapisania ich w skrócony sposób z wykorzystaniem znaku &. Zapis skrócony ma pewne ograniczenia. Z drugiej strony promuje on pisanie bardzo krótkich i zwięzłych funkcji.

Java 9 nadchodzi – prywatne metody w interfejsach

Sierpień 1st, 2016

Jedną z dużych zmian, jakie przyniosła ze sobą Java 8, było dopuszczenie implementacji metod w interfejsach. Używając słowa kluczowego default, możemy zdefiniować metodę, która będzie mieć implementację:

Listing 1. Przykładowy interfejs z implementacją z Javy 8

interface SomeService{

    default void validate(Client client){
        Preconditions.checkNotNull(client);
    }

    default void someLogic(Client client){
        validate(client);
        TransformedClient transformedClient = transform(client);
        emit(transformedClient);
    }

    default void emit(TransformedClient transformedClient){
        System.out.println(transformedClient);
    }

    TransformedClient transform(Client client);
}

Oczywistym zastosowaniem jest przygotowanie metod szablonowych. Nasz interfejs realizuje pewną większą logikę, która wymaga byśmy w ramach implementacji, dostarczyli jedynie fragmentu logiki reprezentowanego przez metodę someLogic. Tyle tylko, że zarówno validate jak i emit nie powinny być zmieniane (takie założenie). Na poziomie interfejsu jedynym rozwiązaniem, które nam to może zagwarantować, jest zamiana tych metod na statyczne. Nie możemy utworzyć metod finalnych w interfejsach. Jednak nadal pozostaje problem ujawnienia części implementacji. Klienta nie powinno interesować, w jaki sposób przeprowadzana jest walidacja ani jak są emitowane zdarzenia. Z doświadczenia wynika, że te dwie metody prędzej czy później staną się rozwiązaniem zastępczym dla jakiegoś kawałka kodu, gdzieś w odległym miejscu systemu. Skoro dostarczają tych mechanizmów, to czemu by z nich nie korzystać, zamiast zrobić to porządnie, na przykład wydzielając odpowiednią klasę.

A jak w Javie 9?

Java 9 dostarcza nam znacznie lepszy mechanizm, który pozwala na rozwiązanie tego problemu:

Listing 2. Przykładowy interfejs z prywatnymi metodami z Javy 9

interface SomeService{

    private void validate(Client client){
        Preconditions.checkNotNull(client);
    }

    default void someLogic(Client client){
        validate(client);
        TransformedClient transformedClient = transform(client);
        emit(transformedClient);
    }

    private void emit(TransformedClient transformedClient){
        System.out.println(transformedClient);
    }

    TransformedClient transform(Client client);
}

I to wszystko. Otrzymujemy zgrabny kod, który ma ukryte, to co powinno być ukryte i ujawnia tylko, to co trzeba.

Podsumowanie

Dziedziczenie wielobazowe zwane też wielodziedziczeniem w Javie jest realizowane za pomocą interfejsów. Dzięki temu nie mamy problemów związanych z konfliktami na poziomie implementacji. Wprowadzenie metod z domyślną implementacją w Javie 8 wymogło wprowadzenie kilku reguł dotyczących nadpisywania metod i rozwiązywania konfliktów w przypadku takich samych sygnatur. W dużym skrócie opierają się one, na wymuszeniu na programiście jasnej deklaracji co chce zrobić.
Java 9 wprowadza do tego mechanizmu rozwiązania, które pozwalają na uniknięcie części konfliktów, poprzez ukrycie niektórych metod. Ważniejsze jest jednak uzyskanie możliwości ukrywania elementów interfejsu. Dzięki temu zachowana będzie hermetyzacja naszego rozwiązania na odpowiednim poziomie. Zagrożeniem może okazać się wykorzystywanie interfejsów zamiast klas abstrakcyjnych. Szczególnie tam, gdzie klasy abstrakcyjne nie miały właściwości (pól). W ten sposób powstaną potworki, które będą miały wiele niepowiązanych odpowiedzialności.

Odpowiedzi po Confiturze

Lipiec 7th, 2016

W sumie odpowiedź na pytanie o biblioteki, które trzeba mieć w swoim arsenale, jeżeli ruszamy do boju przeciwko Javie 8 🙂

Na podstawie tego tekstu plus moje rozszerzenia:

  • Google Guava – Nie tylko kolekcje, ale też ESB, net, czy grafy (o tym nie pisałem). Tam jest dużo różnych ciekawych rzeczy ponad kolekcjami.
  • Lombok – generatory kodu, obiekty niezmienne itp. Robienie sobie dobrze na poziomie objętości kodu. Przy czym trzeba uważać, bo dookoła śmiga też JPA czy Spring ze swoimi proxy i aspektami.
  • pCollections – kolekcje, które czasami się przydają. Nie zastąpią tych z API, ale czasami potrzebujemy specyficznych funkcjonalności czy też niezmienności. To jest jakiś pomysł.
  • Javaslang – różne elementy funkcyjne, dość ciekawe strumienie i duża naturalność w pisaniu. Przy czym z tyłu głowy pytanie o wydajność… ale to jest Java 😉
  • JOOQ/JOOL – biblioteka, która ma podobne zadanie co pCollection. Generalnie czasami potrzebujemy jakiegoś ekstra API dla strumieni. Tu je zapewne znajdziemy.

Miła pani, która o to zapytała – kto ty?

Pytanie od Tomka Nurkiewicza o użycie parallel streamów. Wersja krótka – wątki potrzebują RAMu, którego nie mogliśmy zapewnić. Wersja trochę bardziej rozbudowana. „Optymalizacje” zrównoleglające mają taki dziwny „defekt”, że JVM próbuje alokować pamięć dla każdego wątku, ale nie swapując pamięci wątków nieaktywnych. Inaczej mówiąc, jeżeli chcemy za alokować np. 500MB pamięci dla każdego z 10 wątków (zakładając, że możemy puścić 10 wątków na raz) to potrzebujemy 5GB i jeżeli damy maszynie 2GB to się wyjebie (albo nie wiem jak to skonfigurować). Dlatego też trzeba delikatnie z mechanizmami wielowątkowymi.

Tyle na dziś.

10 lat Confitury i to już jest koniec…

Lipiec 4th, 2016

Długie dni mają to do siebie, że są długie. Start z mieszkania przed 6 i już o 11 byłem na miejscu, czyli w centrum konferencyjnym na Bobrowieckiej. Tam gdzie była pierwsza Confitura (2011), pod nazwą Confitura właśnie. Tym razem dane mi było obejrzeć tylko dwie prezentacje, ale mogłem poprowadzić trzecią 🙂 Cieszę się, że głosowaliście i zapewniliście mi tę możliwość.

Concurrency in Java – Mateusz Kaczmarek

Myśl przewodnia tej prezentacji i coś, o czym warto pamiętać. Nasz software nie żyje w próżni. JVM, system, krzem to wszystko należy brać pod uwagę, gdy piszemy kod współbieżny. Całkiem fajna prezentacja, rzeczowa i pozostawiająca pewien niedosyt. Warto było posłuchać.

Kotlin, why? – Paweł Byszewski

Paweł na przykładzie z życia pokazał, jakie są przewagi Kotlina nad „czystą” Javą. Całkiem fajna prezentacja, w której prawie nic nie zabrakło. Z drugiej strony mam wrażenie, że to „prawie”, to całkiem duży zakres materiału. Szczególnie że pytania z sali dotyczyły też składni. Z drugiej strony mając mało czasu, trzeba coś ciąć. Na pewno prezentacja ta pokazała mi, o jakich rzeczach warto wspominać, gdy omawiamy Kotlina, bo była prowadzona z punktu widzenia programisty androidowego.

To już jest koniec

Całe słuchanie ciężkich brzmień w moim wypadku zaczęło się od jakiejś kasety ze składanką Black Sabbath. Wcześniej było oczywiście Deep Purple, Pink Floyd czy Queen, ale to nie było jeszcze to. Niedługo później poznałem ból zakupu oryginalnych płyt… Paranoid kosztowało jakieś 69PLN… cóż. Wakacje upłynęły mi pod znakiem Electric Funeral, War Pigs i Fallouta 🙂

Koncert Black Sabbath był świetny. Jednak zawsze po tego typu wydarzeniach pozostaje pewien niedosyt. Poczucie, że mogli zagrać jeszcze jeden numer. Jednak i tak było świetnie. Szkoda, że był to ostatni koncert i nie będzie już okazji.

Chrzan z keczupem – własny interceptor Wasabi w Kotlinie

Czerwiec 29th, 2016

Tydzień odpoczynku starczy. Można wrócić do pisania 🙂 Dziś zaimplementujemy własny interceptor w frameworku Wasabi.

Czym jest Wasabi?

Wasabi to framework HTTP napisany w Kotlinie. Pozwala na tworzenie aplikacji wykorzystujących protokół http jako warstwy komunikacji. Posiada wiele elementów, które pozwalają go kwalifikować jako narzędzie REST, ale nie jest na pewno frameworkiem REST. Można tworzyć rozwiązania zorientowane na zasoby, ale nie to jest głównym zadaniem.

Cały mechanizm działania opiera się o dobrze znane elementy takie jak routing, kanały (channels), metody HTTP. Całość jest postawiona na nettym.

Czym są interceptory?

Jak sama nazwa wskazuje, służą one do przechwytywania żądań, które przychodzą do serwera. Następnie wykonywana jest pewna logika związana z żądaniem i wynik jest zwracany jako odpowiedź serwera, następuje przerwanie przetwarzania, albo przesyłany do dalszej obróbki. Można o nich myśleć jak o filtrach znanych z aplikacji webowych. Choć są trochę bogatsze, jeśli chodzi o możliwości.

Jednym z przypadków użycia interceptora jest obsługa statycznych zasobów takich jak statyczne, z punktu widzenia serwera, strony html, pliki js, css, obrazki. Innym jest obsługa nagłówków, autoryzacji, negocjacja dodatkowych parametrów połączenia itp.

Nasz mały interceptor

Wasabi ma na pokładzie klasę StaticFileInterceptor, która pozwala na obsługę statycznych zasobów. Ma on jednak małą wadę w postaci nie możności obsługi domyślnego pliku dla katalogu. Inaczej mówiąc, jeżeli żądanie wskazuje na zasób, który jest katalogiem to dostajemy 404, o ile gdzieś dalej nie leży mapowanie pasujące do wskazanego zasobu, a nie tak jak w przypadku serwerów www plik index.XXX.

Zgłosiłem odpowiednią poprawkę, która została już wdrożona, ale chciałbym omówić, jak do niej doszedłem.

Najpierw kod oryginalnego interceptora:

Listing 1. StaticFileInterceptor stan wyjściowy

public class StaticFileInterceptor(val folder: String): Interceptor() {
    override fun intercept(request: Request, response: Response): Boolean {
        var executeNext = false
        if (request.method == HttpMethod.GET) {
            val fullPath = "${folder}${request.uri}"
            val file = File(fullPath)
            if (file.exists() && file.isFile()) {
                response.setFileResponseHeaders(fullPath)
            } else {
                executeNext = true
            }
        } else {
            executeNext = true
        }
        return executeNext
    }
}

public fun AppServer.serveStaticFilesFromFolder(folder: String) {
    val staticInterceptor = StaticFileInterceptor(folder)
    intercept(staticInterceptor)
}

Mamy tu do czynienia z dwoma elementami. Pierwszy z nich, to implementacja interfejsu Interceptor. Metoda intercept kieruje dalszym przetwarzaniem żądania w następujący sposób. Jeżeli zwróci true, to żądanie jest przekazywane do mechanizmu routingu. Jeżeli zwróci false, to żądanie jest kończone i odsyłane, tak jak jest, co oznacza, że obiekt Response jest przekazywany do mechanizmu zamieniającego go na odpowiedni obiekt z nettiego, który dalej już robi robotę sieciową. Drugim elementem jest funkcja rozszerzająca, która jest dodana do AppServer i obudowuje mechanizm tworzenie i dodawania interceptora.

Wadę tego rozwiązania już opisałem wyżej. Przejdźmy więc do naszej implementacji, która jest trochę inna niż ta, która poszła do repozytorium, ponieważ znacznie intensywniej wykorzystuje wyrażenie when, które nie wszyscy lubią.

Listing 2. Nasz interceptor

class CustomStaticFileInterceptor(val folder: String, val useDefaultFile: Boolean = false, val defaultFile: String = "index.html") : Interceptor() {

    private fun existingDir(path: String): Boolean {
        val file = File(path)
        return file.exists() && file.isDirectory
    }

    private fun existingFile(path: String): Boolean {
        val file = File(path)
        return file.exists() && file.isFile
    }

    override fun intercept(request: Request, response: Response): Boolean {
        return when (request.method) {
            HttpMethod.GET -> {
                val fullPath = "${folder}${request.uri}"
                when {
                    existingFile(fullPath) -> {
                        response.setFileResponseHeaders(fullPath); false
                    }
                    existingDir(fullPath) && useDefaultFile -> {
                        response.setFileResponseHeaders("${fullPath}/${defaultFile}"); false
                    }
                    else -> true
                }
            }
            else -> true
        }
    }
}

fun AppServer.serveStaticFiles(folder: String, useDefaultFile: Boolean = false, defaultFile: String = "index.html") {
    val staticInterceptor = CustomStaticFileInterceptor(folder, useDefaultFile, defaultFile)
    this.intercept(staticInterceptor)
}

W tej wersji mamy kilka drobnych zmian. Po pierwsze dodałem dwa pola useDefaultFile oraz defaultFile, które mają domyślne wartości. Pozwoli nam to zachować wsteczną kompatybilność API. Następnie dopasowujemy warunki działania interceptora. Jeżeli mamy do czynienia z żądaniem innym niż GET, to zwracamy true, co pozwala na kontynuowanie przetwarzania (ostatni else). Jeżeli jednak mamy do czynienia z żądaniem GET, to mamy trzy możliwości. Pierwsza żądanie dotyczy istniejącego pliku, wtedy budujemy odpowiedź i zwracamy false. Druga to sytuacja, gdy żądanie dotyczy katalogu i mamy włączoną obsługę plików domyślnych. Wtedy budujemy odpowiedź z domyślnym plikiem, bez sprawdzania, czy plik istnieje – jego brak to błąd konfiguracji i nas nie obchodzi, i zwracamy false. Trzecia sytuacja reprezentowana przez else, to żądanie katalogu przy wyłączonej obsłudze plików. Zwracamy true i tyle.

W celu zachowania spójności musimy też dodać funkcję do AppServer. W zgłoszonej poprawce zmieniamy istniejącą klasę, a zatem, zamiast dodawać funkcję, zmieniamy ją. Ostatnim krokiem jest likwidacja powtórzeń na poziomie wartości domyślnych. Wystarczy je wyekstrahować do prywatnych stałych w pliku:

Listing 3. Ekstrakcja stałych i nagłówki

private val DEFAULT_USE_DEFAULT_FILE = false
private val DEFAULT_DEFAULT_FILE = "index.html"

class CustomStaticFileInterceptor(val folder: String, val useDefaultFile: Boolean = DEFAULT_USE_DEFAULT_FILE, val defaultFile: String = DEFAULT_DEFAULT_FILE) : Interceptor()
fun AppServer.serveStaticFiles(folder: String, useDefaultFile: Boolean = DEFAULT_USE_DEFAULT_FILE, defaultFile: String = DEFAULT_DEFAULT_FILE)

Podsumowanie

Wasabi framework jest bardzo młodym rozwiązaniem, ale świetnie nadaje się jako miejsce gdzie połączyć naukę języka z rozwojem rzeczywistego oprogramowania. Dzisiejszy przykład pokazuje jak łatwo w kotlinie jest stworzyć rozszerzenie do istniejącego rozwiązania, ponieważ język wymusza na twórcach elastyczność w tworzeniu kodu.

Szkolenie z Kotlina runda druga

Czerwiec 24th, 2016

Robimy powtórkę, bo zainteresowanie 🙂

REJESTREACJA

Podsumowanie I edycji

Mam nadzieję, że uczestnicy są zadowoleni. Co prawda trzy godziny to trochę za mało czasu. Forma ćwiczeń po przetestowaniu jej w boju też nie spełniła wszystkich moich oczekiwań, ale było nieźle. Zatem druga grupa będzie miała ten sam materiał, ale w trochę innej formie. Nie będą to duże różnice. Poprawimy kilka drobnostek, które wyszły w praniu.

Zwiększyliśmy też limit miejsc do 30 (jak to piszę to może jeszcze nie być tej informacji na stronie). A teraz szczegóły.

Gdzie?

Wrocław, pl. Konstytucji 3 Maja 3, budynek Silver Tower 13 piętro (zajebisty widok na Wrocław):

Wejście od ulicy Dąbrowskiego, drzwi obok krasnoludka.

Kiedy?

30 czerwca startujemy o 17:30 od czegoś na ciepło. cały czas będą dostępne przekąski na zimno i kawa na zimno z G Coffee Company.

Co i jak?

Będzie to wprowadzenie do Kotlina. Dlatego potrzebujecie własne laptopy z zainstalowanym:

  • Ulubionym IDE
  • Klientem git
  • Gradle
  • Javą 1.8

Całość potrwa około 3 godzin. Zakończenie planowane jest na 21. Później możemy pójść na piwo 🙂

REJESTREACJA

Już lato…

Czerwiec 21st, 2016

Szybko minęło. W sumie 96 wpisów w ramach blogowej wiosny… Gdy zabierałem się za ten cykl, to nie zauważyłem, że będzie to około 10% wszystkich wpisów na blogu. To było dużo pracy. Były święta i długie weekendy. Jeździliśmy po Polsce i zwiedzaliśmy. Czasami trzeba było „wyprodukować” kilka tekstów w przeciągu jednego dnia. Jednak udało się.

Co dalej?

Wiele razy słyszałem, że szata graficzna bloga jest zła. Mam tego świadomość i plany, by coś z tym zrobić. Może przez wakacje uda się jakoś rozwiązać ten problem. Łatwo nie będzie, bo treść jest specyficzna, co czyni układ graficzny trudnym do zaprojektowania.

Inna sprawa to korekta starszych wpisów. Szczególnie pod kątem poprawności listingów, ich formatowania i organizacji. Tu też czeka mnie dużo pracy.

Nadal otwartym tematem jest napisanie kilku pluginów do obsługi listingów. Szczególnie jeśli chodzi o wstawianie kodu do treści, bo to kuleje najbardziej.

Co teraz?

Zapewne kilka dni odpoczynku od pisania. Muszę przygotować kurs Kotlina, którego wersja skrócona już pojutrze.

uf… dało się 🙂

Szkolenie z Kotlina

Czerwiec 20th, 2016

Małe, 3 godzinne szkolenie z Kotlina już w ten czwartek 🙂

REJESTREACJA

Gdzie?

Wrocław, pl. Konstytucji 3 Maja 3, budynek Silver Tower 13 piętro 13 piętro (zajebisty widok na Wrocław):

Wejście od ulicy Dąbrowskiego, drzwi obok krasnoludka.

Kiedy?

23 czerwca (i później zapewne będzie powtórka) startujemy o 17:30 od lunchu, zatem jak chcesz coś wszamać, to trzeba być punktualnie.

Co i jak?

Będzie to wprowadzenie do Kotlina. Dlatego potrzebujecie własne laptopy z zainstalowanym:

  • Ulubionym IDE
  • Klientem git
  • Gradle
  • Javą 1.8

Całość potrwa około 3 godzin. Zakończenie planowane jest na 21. Później możemy pójść na piwo 🙂

Ogólne założenie jest takie, że zaimplementujemy rozwiązania kilku problemów, tak by poznać język.

REJESTREACJA


Translate »