Spośród wielu nowości wprowadzonych w HTML5, API do rysowania dwuwymiarowej grafiki rastrowej bez użycia wtyczek typu Flash jest z pewnością jednym z najbardziej interesujących. Wielu zresztą twierdzi (wliczając w to prominentne figury świata IT), że zwiastuje to początek końca wspomnianych pluginów. Czy rzeczywiście tak będzie, to oczywiście zobaczymy w niedalekiej przyszłości. Już teraz jednak można zobaczyć liczne przykłady na to, że technologia ta oferuje naprawdę spore możliwości.
W ramach zapoznawania się z poszczególnymi składowymi HTML5, nie mogłem więc nie przyjrzeć się bliżej elementowi <canvas>
– bo to o nim oczywiście mowa. Tradycyjnym testem dla tego rodzaju rozwiązań (tj. graficznych bibliotek 2D), który zawsze staram się zaimplementować na początku, jest… chmara odbijających się piłeczek :) Nie inaczej było i w tym przypadku, czego efekty zamieszczam tutaj wraz z krótkim opisem i wprowadzeniem w podstawy Canvasa.
Zwykłego użytkownika od power usera dobrze odróżnia sposób radzenia sobie z powtarzalnymi zadaniami. Perspektywa zmiany nazwy N plików, skonwertowania N obrazków czy skatalogowania N utworów muzycznych jest odstręczająca już dla niewielkich wartości N, jeśli mielibyśmy wykonywać te czynności ręcznie. Zaawansowany użytkownik w tym celu zakasze jednak rękawy i wysmaży odpowiedni skrypt, który może nie będzie działał od razu, ale za to w końcu poradzi sobie z zadaniem całkowicie automatycznie. Niekoniecznie musi to w sumie zająć mniej czasu niż procedura ręczna, ale na pewno będzie mniej męczące :)
Dlatego też niemal zawsze staram się wybierać programową automatyzację. Wiąże się z tym jednak pewien problem. Otóż języki powłok systemowych (shelli) to nie jest coś, z czym koder ma intensywny kontakt na co dzień. Należą one raczej do obszaru zainteresowań administratorów. W związku z tym wyprodukowanie jakiegoś działającego kawałka skryptu jest często poprzedzone co najmniej krótkim przypominaniem sobie składni i semantyki danego języka. Zasadniczo jest to strata czasu lub – wyrażając się nieco inaczej – czynnik zwiększający minimalną wartość N, od której automatyzacja ma sens.
Ale jeśli nie języki shellowe, to co? Ano to, czego używamy na co dzień, czyli zwykłe języki programowania. Tu niestety nie ma sprawiedliwości: niektóre z nich nadają się do zadania nieporównywalnie lepiej niż inne. Część tych drugich ma swoje interpretowane, skryptowe wersje; przykładem jest choćby javowy BeanShell. Ich odpowiedniość do wersji pełnych nie jest jednak wcale zapewniona. Inne języki zwyczajnie nie mają podobnych narzędzi i zostawiają nas z koniecznością wyprodukowania kompletnego programu.
Tutaj ujawnia się przewaga Perla, Pythona, Ruby’ego i podobnych im języków interpretowanych, które nie wymagają do uruchomienia niczego poza plikiem z “czystym” kodem. Jest to dokładnie taka sama sytuacja, jak w przypadku basha czy innych języków powłoki. Korzyść jest jednak oczywista, jeśli tylko któryś z tych języków jest nam znany: nie ma tu bariery składniowej.
Nie znaczy to oczywiście, że jeśli ktoś potrafi jednym zaklęciem złożonym z ls
, xargs
i grep
a przetworzyć tysiąc plików tekstowych, to powinien porzucić tę sztukę i zwrócić się ku Prawdziwemu Programowaniu™. Zapewne też dobry programista będzie potrafił w końcu wyprodukować ową magiczną formułę, pod warunkiem spędzenia odpowiednio długiego czasu nad stronami mana. Jeśli jednak alternatywą jest napisanie kilkunastu linijek w znanym sobie języku, które zrobią to samo, będą miały spore szanse działać za pierwszym razem i zajmą w sumie co najwyżej kilka minut… to czemu nie? Warto korzystać ze swoich umiejętności nie tylko w ich ściśle ograniczonym obszarze zastosowań.
A jeśli przypadkiem ktoś właśnie zechciał akurat nauczyć się któregoś z wymienionych ze mnie języków, to… tak, polecam Pythona :)
W językach kompilowanych mechanizmy wejścia-wyjścia opiera się niemal zawsze o koncepcję strumienia (stream), czyli abstrakcyjnego obiektu z którego możemy czytać dane i/lub je do niego zapisywać. Ponieważ strumień jest opakowaniem na jakiś zewnętrzny zasób – plik, połączenie sieciowe, itp. – należy generalnie dbać o jego poprawne i szybkie zamknięcie, gdy nie jest już potrzebny. Dotyczy to także zwłaszcza języków z zarządzaną pamięcią, gdzie osierocony obiekt strumienia może nie być posprzątany przez bardzo długi czas, zajmując zewnętrzne, niezarządzane zasoby systemowe.
Poprawny sposób postępowania z obiektem strumienia, który chciałem dzisiaj omówić, dotyczy konkretnie języka Java, gdyż tam cała sprawa jest co najmniej nietrywialna. Dzieje się tak z trzech powodów:
catch
), albo zadeklarowany jako opuszczający funkcję (throws
). Jest to sprawdzane podczas kompilacji.close
, zamykająca strumień, deklaruje potencjalne wyrzucanie wyjątku IOException
.with
(obecnej np. w C# i Pythonie), która automatycznie posprzątałaby po obiekcie strumienia w momencie opuszczenia jej zasięgu.Brak instrukcji with
sprawia, iż do dyspozycji pozostaje nam wyłącznie try-catch
lub try-finally
. Naiwne zastosowanie któregoś z nich nie daje jednak pożądanych efektów:
Takim efektem byłby na przykład fakt kompilowania się kodu :) W tej wersji jest to jednak niemożliwe (o ile funkcja nie deklaruje wyrzucania IOException
), bowiem wyjątek ten może zostać rzucony przez metodę close
… A przynajmniej taka jest teoria, którą kompilator niestety pedantycznie sprawdza.
W rzeczywistości ten kod ma przynajmniej jeszcze jeden błąd, którego nie wyeliminuje otoczenie wywołania close
odpowiednim blokiem try-catch
. Jego znalezienie pozostawiam aczkolwiek jako – ahem – ćwiczenie dla czytelnika ;) W zamian pokażę dla odmiany nieco lepszy sposób na obejście zaprezentowanych problemów.
Polega on na zastosowaniu dwóch zagnieżdżonych bloków try
: jednego z catch
do złapania IOException
i drugiego z finally
do zamknięcia strumienia. W całości prezentuje się to następująco:
Przy zastosowaniu takiej konstrukcji wszystkie miejsca, w których wyjątek I/O może wystąpić, są otoczone blokiem try-catch
, więc kompilator nie będzie miał powodów do narzekań. Nadal też gwarantujemy, że strumień zostanie zawsze zamknięty, co z kolei zapewnia blok try-finally
.
A że wygląda to wszystko cokolwiek nieestetycznie? Cóż… Java :)
Właśnie skończyłem lekturę książki Wprowadzenie do HTML5 autorstwa Bruce’a Lawsona i Remy’ego Sharpa, wydanej w wersji polskiej przez Helion. Zakupiłem ją z nadzieją, że wprowadzi nieco porządku w cały ten buzz odnośnie HTML5, którego trudno nie zauważyć, jeśli tylko robi się cokolwiek chociaż pośrednio związanego z tą tematyką. I od razu mogę stwierdzić, że pod tym względem moje oczekiwania zostały rzeczywiście spełnione.
W książce można bowiem znaleźć przegląd kilku elementów składających się na standard HTML5 oraz paru technologii pobocznych, które formalnie do standardu nie należą, ale często są uwzględniane pod parasolem szerszej definicji całego zjawiska/technologii/ruchu/trendu/czegokolwiek-czym-to-jest. Wśród nich mamy takie podstawy jak nowe elementy strukturalne, nowe i przedefiniowane znaczniki opisu tekstu, poszerzone możliwości formularzy i różne drobne usprawnienia składniowo-semantyczne. Nie są one może tak ekscytujące jak “gwiazdy” technologii HTML5, ale fakt ich porządnego uwzględnienia jest według mnie tym bardziej godny uwagi.
Oczywiście nie zabrakło też wielu “nowych wspaniałych API”, dzięki którym możemy chociażby tworzyć SQL-owe bazy danych po stronie klienta i wykonywać szereg innych niezbędnych a właściwych dla stron WWW czynności ;D I tak możemy na przykład dowiedzieć się, co to właściwie jest ten Canvas, w jaki sposób zapewnić wsparcie dla techniki drag & drop i do czego w zasadzie mogą przydać się te całe webowe gniazdka (czyli Web Sockets). Ponieważ wszystko pokazane jest na prostych przykładach, opanowanie podstaw każdego z tych rozwiązań dla ogarniętego programisty nie stanowi żadnego problemu.
Kluczowym słówkiem są tu jednak owe ‘podstawy’. Chociaż różne tematy są tu opisane z różnym poziomem szczegółowości – czasem nawet zbyt dużym, jak choćby w przypadku
<audio>/<video>
– to jednak w żaden nie jest opisany wyczerpująco. Nie wskazuję tego jako wady, bo przecież mówimy o książce z wyraźnym napisem “wprowadzenie” na okładce. Poza tym w prawie każdym miejscu, gdzie autorzy świadomie coś pomijają, podawane są inne (zwykle internetowe) źródła, z których można czerpać bardziej szczegółowe informacje. Trzeba też powiedzieć szczerze, że właściwie żaden z opisywanych tematów (może poza Canvasem) osobno nie prezentuje się jako specjalnie skomplikowany bądź obszerny. Skondensowane wprowadzenie może więc równie dobrze być całkowicie wystarczające.
Podsumowując więc: lektura okazała się jak najbardziej pożyteczna. Książkę czyta się szybko i bez wielkiego wysiłku, a jednocześnie można się z niej dowiedzieć wielu interesujących rzeczy. Jeśli nawet nie zamierzamy – za przeproszeniem – programować w HTML5, to przeczytanie tej książki da nam ciekawy wgląd w technologie będące podobno przyszłością Internetu czy nawet aplikacji jako takich.
Kilka dni temu zajmowałem się optymalizacją szybkości aplikacji, która działa na platformie Google App Engine. Żeby dobrze podejść do tego zadania, zacząłem oczywiście od poszerzenia swojej wiedzy na temat zagadnienia, co obejmowało chociażby obejrzenie odpowiedniej prezentacji z zeszłorocznego Google I/O. Przedstawiono tam odpowiednie narzędzie służące do profilowania (Appstats), czyli niezbędnego etapu przygotowawczego (o czym mam nadzieję wszyscy pamiętają ;]). Dodatkowo dowiedziałem się też o najważniejszych źródłach opóźnień w tego rodzaju aplikacjach – i to okazało się dość zaskakujące.
Ogólny wniosek, jaki zdołałem stamtąd wyciągnąć, dotyczy właśnie prawidłowego wykrywania wąskich gardeł (zwanych bottlenecks – tytułowe szyjki od butelek). W przypadku wspomnianej aplikacji okazało się na przykład, że nawet stosunkowo skomplikowana logika (przetwarzanie sporych porcji danych w JSON-ie, renderowanie długich szablonów HTML, itd.) napisana całkowicie w wysokiego poziomu języku interpretowanym (Python) relatywnie zajmuje dosłownie chwilę. Nieporównywalnie dłuższy czas związany jest z dowolnym wywołaniem API platformy, bo każde z nich wymaga sieciowej wycieczki do osobnego serwera, który może je obsłużyć. To zaś sprawia, że wszelkie próby optymalizacji samych algorytmów (nawet potworków rzędu czy
) nie mają sensu, jeśli nie wiąże się to ze zmniejszeniem ilości wspomnianych wywołań, czyli np. dostępu do magazynu danych.
Podobne pozornie zaskakujące wnioski można wyciągnąć, gdy przyjrzymy się wewnętrznej strukturze innych platform. Bardzo dobrym przykładem jest chociażby diagram opóźnień I/O w komputerach PC. Łatwo na nim zauważyć, w których miejscach należy jak najbardziej ograniczyć ruch danych, aby uzyskać odpowiedni wzrost wydajności. Przy takim spojrzeniu na aplikacje, podejście znane jako DOD (Data Oriented Design) nabiera też nieco więcej sensu ;-)
Większość programistów zna (albo powinna znać) serwis Stack Overflow. Dla niezorientowanych wyjaśniam, że jest to specyficznego rodzaju forum dyskusyjne dla koderów, nastawione przede wszystkim na zadawanie pytań i udzielanie na nie odpowiedzi. Ze względu na swoje rozmiary mierzone liczbą zgromadzonej wiedzy, stanowi ono też doskonałe źródło szybkiej informacji w tych częstych przypadkach, gdy potrzebujemy prostego rozwiązania jakiegoś niezbyt skomplikowanego problemu.
Oprócz bycia nieocenioną pomocą w pracy programisty, SO jest też ciekawym przypadkiem sprawnie działającej społeczności, na straży której nie stoją całe zastępy moderatorów. Zamiast tego to sami użytkownicy – za pomocą sukcesywnie zdobywanych przywilejów, takich jak edycja czy tagowanie pytań – wcielają w życie zasady dyskusji. Podobno fachowo idea ta nazywa się crowdsourcingiem; możemy dołączyć to słówko do pokaźnej już liczby dziwnych pojęć spod znaku Web 2.0 :)
Centralnym mechanizmem, wokół którego kręci się światek Stack Overflow (oraz niezliczonych innych stron opartych na tym samym silniku) jest reputacja poszczególnych użytkowników. To zwykły numer, w swoim znaczeniu podejrzanie podobny do tzw. karmy, nad której obecnością na forum Warsztatu raz się nawet nieco rozpisałem. Reputacja jest zwiększana między innymi wtedy, gdy nasze pytania lub odpowiedzi zostaną poparte (upvote) przez innych użytkowników. To nam daje punkty, które w większej ilości przekładają się na przywileje quasi-moderatorskie. W sumie jest to więc klasyczny przykład sprzężenia zwrotnego dodatniego (positive feedback loop), występującego w świecie w najprzeróżniejszych formach – od giełdy po FarmVille.
Jak w każdym takim systemie, także i tutaj wszystkie trybiki nie zawsze jednak działają idealnie. Ponieważ reputacja w serwisie SO jest dobrem mającym wartość także poza nim, nie dziwi duża chęć do jej zwiększania, występująca u wielu użytkowników. Nie jest to wbrew pozorom nic trudnego – nawet mimo obecności licznych ekspertów z niemal każdej poddziedziny związanej z programowaniem, którzy weryfikują i oceniają udzielane odpowiedzi.
Jest tak z prostej przyczyny: w serwisie pojawia się całe mnóstwo pytań trywialnych, na które bez problemu odpowie każda średnio zaawansowana w danym temacie osoba. Ze względu na ilość użytkowników (ponad 700 tysięcy) fakt ten generuje dość komiczne sytuacje, gdy w przeciągu mniej niż minuty pojawiają się trzy albo cztery niemal dokładnie identyczne odpowiedzi. Zabawa czasami zamienia się więc raczej w konkurs szybkiego pisania na klawiaturze :)
Mimo tych wad muszę stwierdzić, że uczestnictwo w dyskusjach na SO jest w ogólnym rozrachunku bez wątpienia pożyteczne. Wymierną osobistą korzyścią jest oczywiście sama reputacja, przeliczalna rozmiar ego po bardzo korzystnym kursie ;] Ważniejsza jest aczkolwiek możliwość utrwalania i poszerzania swojej wiedzy w wybranych działkach koderskiej działalności.
Ktokolwiek, kto pisał dłuższy lub bardziej skomplikowany arkusz stylów CSS, wie jakim bólem siedzenia potrafi to być. Nie chodzi tu nawet o same style, których interakcje między sobą oraz z zawierającymi je selektorami mogą czasem być cokolwiek zagadkowe (tak, marginy i paddingi – o was mówię). Rzecz w tym, że sam język CSS jest po prostu biedny – nie tyle w zakresie możliwości (w końcu jest zupełny w sensie Turinga), co wygody użytkowania. Brakuje mu chociażby kilku kluczowych konstrukcji, jakich można by się spodziewać. Dobrym przykładem są stałe (zmienne) i możliwość wykonywania na nich operacji arytmetycznych – choćby w celu policzenia zależnych od siebie marginesów.
Z tą bolączką można sobie oczywiście radzić we własnym zakresie, podłączając na przykład arkusze CSS pod system szablonów normalnie używany do generowania stron HTML na poziomie serwera. Nie wydaje się to jednak najlepszym możliwym sposobem na zużywanie jego zasobów. Istnieją też na pewno bardziej interesujące rzeczy, które mógłby zrobić programista w czasie, gdy będzie takie (z natury raczej specyficzne) rozwiązanie implementował.
I tutaj pojawia się bardzo ciekawy projekt o nazwie LESS, rozszerzający składnię CSS-a o wiele przydatnych elementów, jakich domyślnie w nim nie uświadczymy. Najfajniejsze jest przy tym to, że trik ten odbywa się po stronie klienta przy pomocy odpowiedniego skryptu JavaScript. Możemy więc korzystać z niego niezależnie od backendu naszego serwera czy nawet w ogóle bez serwera, tj. do stron bez dynamicznie generowanej treści. Integracja jest zaś bardzo prosta: sprowadza się do dodania jednego znacznika
<script>
(oczywiście) oraz lekkiej modyfikacji referencji do arkuszy stylów, czyli znaczników <link rel="stylesheet">
.
Co dostajemy w zamian? Oprócz wspomnianych na początku zmiennych i operacji na nich, mamy też w LESS-ie “funkcje” (czy raczej makra) z parametrami. Pozwala to chociażby na łatwiejsze dostarczanie stylów specyficznych dla przeglądarek. Możliwe jest też np. importowanie kodu z innych arkuszy LESS, zagnieżdżanie selektorów oraz… wstawianie komentarzy w stylu C++ :)
Powyższe wyliczenie dodatków nie brzmi może bardzo imponująco, lecz nie jest to wcale dziwne. Tak naprawdę sam CSS już dawno powinien oferować tego rodzaju możliwości. Wiemy jednak, jak jest w rzeczywistości. Sądzę więc, że tym bardziej warto jest używać rozwiązania, które zamienia CSS w coś, czego można w końcu normalnie używać :)