Wszyscy wiemy, że Notatnik to bardzo dobry edytor kodu. Wystarcza w zupełności, o ile tylko jesteśmy w stanie zorientować się w gąszczu liter, cyfr i symboli bez żadnych wizualnych wskazówek w postaci ich kolorowania. Jakoś dziwnym trafem miażdżąca większość programistów preferuje jednak edytować kod odpowiednio “odmalowany” przez IDE. Widać nie potrafią docenić piękna kodowania na monochromatycznym monitorze ;-)
A nieco poważniej mówiąc: kolorowanie kodu to istotna sprawa, gdyż jest to kwestia komfortu naszych drogocennych narządów wzroku. Ponieważ miałem do czynienia z wieloma edytorami kodu i środowiskami programistycznymi, wydaje mi się, że mogę dość sensownie wypowiedzieć się na temat tego, jak należy ustawić kolory dla poszczególnych składników kodu, by były one komfortowe.
Najważniejszy jest tu prawdopodobnie odpowiedni kontrast, który nie wyraża się wyłącznie różnicą jasności między tłem a tekstem – zwłaszcza w systemie RGB. Powiedziałbym zresztą, że dla każdego jest to kwestia raczej subiektywna. Znam osoby, które świetnie czują się w kolorystyce rodem niemal z Matriksa: czarne tło i biały tekst, który miejscami przechodzi w jaskrawe kolory, wyraźnie odcinające się na ciemnym ekranie (jak zielony lub purpurowy). Pozwolę sobie jednak zaryzykować stwierdzenie, że takich osób nie ma zbyt wiele :) Powszechniejsza zdaje się konfiguracja z jasnym tłem; ma ona też tę zaletę, że po przełączeniu się do innego okna raczej nie doznamy żadnego “szoku świetlnego”, jako że dokumentacje i większość stron internetowych trzymają się raczej jasnych barw.
Zaznaczam jednak, że ‘jasne’ tło nie musi wcale znaczyć ‘białe’. Dopasowując kontrast, dobrze jest poeksperymentować z tłem lekko żółtym, zielonym czy niebieskim. Nie należy aczkolwiek przesadzać i ustawiać mniej niż ~80-procentową intensywność w którymś z kanałów RGB – w efekcie tło będzie najprawdopodobniej zbyt ciemne.
Analogicznie warto też przyjrzeć się domyślnemu kolorowi kodu (zazwyczaj jest to ten stosowany do identyfikatorów w rodzaju zmiennych lokalnych). Nie musi on wcale być absolutnie czarny; zamiast tego można wypróbować coś w rodzaju RGB(30, 30, 30)
.
Oczywiście ów domyślny kolor kodu to zazwyczaj tylko jedna z wielu barw używanych przez IDE. Obecnie środowiska wyróżniają mnóstwo osobnych elementów kodu, pozwalając każdemu z nich mieć przypisany inny kolor. Nie zalecałbym jednak przesady w korzystaniu z tej funkcjonalności, bo przy odrobinie źle pojętej kreatywności możemy otrzymać schemat kolorów świecący niczym choinka, gdzie dodatkowo najdłuższy ciąg o tej samej barwie nie liczy więcej niż dwa wyrazy. Pamiętajmy, że tutaj ważna jest użyteczność. To, jak bardzo dany element jest wyróżniony i wygląda inaczej niż inne, powinno odpowiadać jego ważności. Typowa kolejność, począwszy od najbardziej istotnych tokenów, wygląda mniej więcej tak:
Ta lista może rzecz jasna podlegać dostosowaniu pod własne upodobania, a także pod możliwości używanego IDE (część nie obsługuje niestety kolorowania semantycznego, które rozróżnia np. typy od innych nazw). Każdy język dodałby tu też specyficzne dla siebie elementy, sytuujące się zazwyczaj gdzieś w drugiej połowie stawki. W końcu, wewnątrz każdej z tych grup możliwe są też dodatkowe rozróżnienia – lecz, jak już wspomniałem, należy korzystać z nich ostrożnie.
Pisząc mniej lub bardziej regularnie o PowerShellu, zdołałem zaprezentować całkiem pokaźną kolekcję różnych skryptów. Część z nich do działania wymagała podania dodatkowych danych. Jeśli są one w miarę niezmienne, można je zapisać w samym kodzie jako pseudo-stałe. Jeżeli jednak każde użycie skryptu może potencjalnie potrzebować innych wartości, wtedy najlepiej pobawić się z jego parametrami.
W PowerShellu do specyfikowania argumentów służy słowo kluczowe param
. Można je stosować zarówno do samych skryptów, ale także do zawartych w nich funkcji. Tak jest – może nie wspominałem o tym wcześniej, ale w PSh jak najbardziej można definiować własne funkcje na użytek lokalny skryptu:
W obu przypadkach (skryptów i funkcji) instrukcja param
powinna znaleźć się na samym początku i wystąpić co najwyżej raz.
Składnia deklaracji parametrów jest raczej prosta i wygląda następująco:
Kolejne elementy oddzielamy w niej przecinkami. Dla każdego z nich wartośćDomyślna
nie jest obowiązkowa, lecz niepodanie jej nie czyni wcale argumentu obowiązkowym. Zamiast tego otrzyma on wartość “neutralną” (np. $null
dla obiektów), jeśli nie zostanie przypisany w wywołaniu funkcji/skryptu. Żeby uczynić argument rzeczywiście niezbędnym, należy po prostu… rzucić wyjątek, jeśli nie został on użyty:
Może to się wydawać dziwne, ale nie zapominajmy, że języki powłok takich jak PowerShell są interpretowane Dlatego błąd w postaci braku wymaganego parametru (który normalnie wykryty by został podczas kompilacji) może dać o sobie znać dopiero w czasie wykonania.
Każdemu parametrowi możemy też przypisać typ, którym może być klasa z .NET-owego frameworka lub któryś z typów wbudowanych. Oczywiście nie musimy tego robić. Wtedy jednak będziemy mieli do czynienia z nieokreślonym obiektem (klasy System.Object
), którego rodzaj możemy ewentualnie sprawdzić później (np. metodą GetType
).
Specjalnym typem parametru jest switch
, czyli przełącznik. Tworzy on parametr, któremu nie podajemy wartości, a jedynie uwzględniamy w wywołaniu (albo i nie). Jest to więc flaga podobna do Recurse
czy -Verbose
ze standardowych komend PowerShella lub tysięcy podobnych flag w programach i poleceniach z innych shelli. Semantycznie taki przełącznik jest potem zmienną logiczną typu bool
:
To mniej więcej tyle, jeśli chodzi o definiowanie parametrów dla funkcji i skryptów. Jak teraz wygląda ich przekazywanie w trakcie wywołania?… No cóż, odpowiedź jest prosta: dokładnie tak samo, jak w przypadku wbudowanych komend PowerShella. Przede wszystkim możemy podawać ich nazwę poprzedzoną myślnikiem oraz wartość:
Nie jest to jednak zawsze konieczne. Zamiast tego możemy zdać się na ich kolejność w deklaracji param
, co pozwala ominąć podawanie ich nazw w wywołaniu:
Pamiętajmy tylko, że skrypty (rzadziej funkcje) mogą mieć nierzadko i kilkanaście parametrów. Pomijanie ich nazw na pewno nie wpłynie wtedy pozytywnie na czytelność.
Postawiłbym tezę, że każdy szerzej używany język programowania ma ok. jedną wyróżniającą cechę, definiującą w znacznym stopniu sposób, w jaki się go używa. Innymi słowy, każdy język z czymś się kojarzy. I tak myśląc o C, przypominamy sobie od razu wskaźniki; w Pythonie przychodzi nam na myśl składnia oparta na wcięciach; Pascal kojarzy nam się natychmiast z begin
/end
; w LISP-ie mamy miliardy nawiasów; w C++ przeciążanie operatorów; w PHP kod przeplatany wstawkami HTML – i tak dalej. Chyba tylko C# jest – przynajmniej dla mnie – swego rodzaju wyjątkiem w tej kwestii (co niekoniecznie musi być złe, bo oznacza również brak wyraźnie irytujących feature‘ów).
Dzisiaj jednak chciałem napisać o Javie z tego względu, że przyszło mi ostatnio kodować nieco w tym języku. Wrażenie, jakie w związku z tym odnoszę, jest takie, iż Java to obecnie chyba najbardziej “klasycznie obiektowy” język ze wszystkich, które mają w branży jakieś znaczenie. Tu nie ma żadnych udziwnień, które łamałyby paradygmat OOP-u, na który składają się m.in. obiekty dostępne przez referencje, komunikacja za pomocą interfejsów i metod, wyraźny podział na proste typy wbudowane i złożone klasy, jawne tworzenie kopii obiektów, i tak dalej. Z jakichś powodów “wynalazki” w rodzaju typów generycznych czy wyrażeń lambda przebijają się do Javy bardzo, bardzo powoli.
Do tej listy trzeba też dopisać jakąkolwiek formę wskaźników na funkcje, przydatną w implementacji callbacków, czy też delegatów przeznaczonych do obsługi zdarzeń. Zamiast tego javowe API muszą uciekać się do (znów wybitnie OOP-owego) implementowania ustalonych interfejsów i polimorficznych wywołań metod. Do tego jednak język posiada unikalny feature, który – przynajmniej w założeniu – ma ten proces wydatnie ułatwiać. To właśnie tytułowe niestatyczne klasy wewnętrzne (inner classes).
Idea jest prosta. Jeśli klasę B umieścimy wewnątrz klasy A, to zabieg ten w Javie będzie miał skutek nie tylko dla widoczności tej pierwszej. Klasa B będzie wtedy klasą wewnętrzną A także w tym sensie, że każdy jej obiekt będzie zawsze związany z jakimś obiektem klasy A. Tą związanie odbywa się automatycznie i objawia dostępem do składników klasy “otaczającej”:
To w sumie nie jest aż tak imponujące. Wprawnym okiem można łatwo zauważyć, że to w gruncie rzeczy tylko cukierek składniowy dla przekazywania this
do konstruktora klasy wewnętrznej i późniejszego odwoływania się do niego. Ciekawiej jest wtedy, gdy mała, wewnętrzna, pomocnicza klasa jest na tyle mała i pomocnicza, że nie warto nawet nadawać jej nazwy, bo potrzeba nam tylko jednego jej obiektu:
Wówczas wszystko co o tej klasie wiemy to to, że implementuje ona pewien interfejs i właśnie za jego pośrednictwem możemy się do jej jedynego obiektu odwoływać.
Tak w skrócie wygląda teoria. Z praktycznego punktu widzenia mogę natomiast powiedzieć, że dwojga złego lepiej już te wątpliwej świeżości cukierki składniowe mieć, skoro innego wyjścia nie ma… Mam tu na myśli oczywiście fakt, że wobec braku delegatów/zdarzeń/domknięć/itp. jedynym sposobem na komunikację zwrotną w aplikacjach javowych jest wywoływanie metod z ustalonych interfejsów, implementowanych przez obiekty, które następnie podaje się jako “słuchacze” (listeners). Niestatyczne klasy wewnętrzne zapewniają przynajmniej łatwe połączenie między tymi sztucznie wprowadzonymi obiektami i resztą programu.
Trzeba jednak uważać, by nie zamienić swojego kodu w spaghetti, co może się łatwo zdarzyć, jeśli radośnie wpleciemy definicje klas w treść funkcji. Jest to szczególnie niepożądane w kodzie inicjalizującym np. elementy UI, w którym należy po kolei poustawiać wszystkie listenery dla wszystkich kontrolek. Zdefiniujmy je wszystkie w locie i będziemy mieli całą obsługę zdarzeń w jednej metodzie. Fuj!
Dlatego lepiej już definiować takie pomocnicze obiekty w podobny sposób, jak wyżej – tj. jako pola, którym przypisujemy klasy stworzone ad hoc, zawierające metody z reakcjami na zdarzenia. To oczywiście tylko nędzna imitacja składni prawdziwych procedur zdarzeniowych, ale przynajmniej jest to podróbka akceptowalna.
Wydaje mi się, że przynajmniej pod kilkoma względami programowanie przypomina prowadzenie samochodu. Obu tych umiejętności względnie łatwo się nauczyć i niemal niemożliwe jest zapomnieć. Po nabyciu pewnej wprawy mamy też wystarczającą biegłość, by nie musieć koncentrować całej uwagi na którejś z tych czynności. Wyjątkiem są jedynie te miejsca, w których zalecane jest zachowanie szczególnej ostrożności.
Dla mnie (i pewnie nie tylko dla mnie) takimi miejscami w kodzie są styki programu z otoczeniem, przez które musi przebiegać jakaś komunikacja polegająca na wymianie danych i/lub informacji o zdarzeniach. Fragmenty te są ważne i nierzadko problematyczne, gdyż często pociągają za sobą konieczność dopasowania sposobu wykonywania programu do zewnętrznych wymagań. Jak na skrzyżowaniu, trzeba czasem chwilę poczekać i przynajmniej parę razy obejrzeć się dookoła.
W tym kontekście używa się słowa ‘synchronizacja’, jednak kojarzy mi się ono nieodparcie z programowaniem współbieżnym. To trochę złe skojarzenie, bowiem nie trzeba wcale tworzyć kilku wątków czy procesów, by kwestia zaczynała mieć znaczenie. Wystarczą operacje wejścia-wyjścia (zwłaszcza względem mediów wolniejszych niż lokalny system plików) lub obsługa jakiegoś rodzaju zdarzeń zewnętrznych, albo chociażby RPC (Remote Procedure Call – zdalne wywoływanie procedur) – ale oczywiście ta lista nie wyczerpuje wszystkich możliwości. Ogólnie chodzi o wszelkie odstępstwa od możliwości wykonywania programu krok po kroku – a właściwie kroczek za kroczkiem, gdzie każda sekwencja stałej liczby instrukcji zajmuje umowną, niezauważalną chwilę.
Jeśli by się nad tym zastanowić przez moment, to takich sytuacji jest sporo. Ba, właściwie to wspomniane scenariusze sekwencyjne są raczej wyjątkiem, a nie regułą. Dzisiejsze aplikacje działają w wielozadaniowych środowiskach, na ograniczonych pulach zasobów, wymieniają dane przez różne (niekoniecznie szybkie) kanały informacji, i jeszcze dodatkowo są pod ciągłą presją wymagań co do cechy określanej angielskim słówkiem responsiveness – czyli “komunikatywności z użytkownikiem”. Nic dziwnego, że wspomniane przeze mnie wcześniej ‘punkty szczególnej ostrożności’ stają się na tyle istotne, że zazwyczaj to wokół nich buduje się całą architekturę programu.
W jaki sposób są one realizowane? Zależy to od wielu czynników, w tym od rodzaju aplikacji oraz możliwości i struktury systemowego API, które ona wykorzystuje. Tym niemniej można wyróżnić kilka schematów, aplikowalnych w wielu sytuacjach – chociaż nie zawsze z równie dobrym skutkiem. Są nimi:
i jest przestępstwem ściganym z urzędu w każdym rozsądnym zespole projektowym :) Istnieją aczkolwiek okoliczności łagodzące, zezwalające na jego popełnienie pod ściśle określonymi warunkami. Musimy jedynie być pewni, że zużywanie do 100% czasu procesora przez nasz program jest akceptowalne i że między kolejnymi zapytaniami możemy też zrobić coś produktywnego. Tak się składa, że istnieje typ aplikacji, w którym oba te warunki mogą być spełnione: gry. Ich pętla główna to nic innego jak aktywne czekanie na informacje o zdarzeniach z renderowaniem kolejnych klatek w tak zwanym międzyczasie.
Takie krótkie drzemki zdecydowanie zmniejszają zużycie procesora przez aplikację, ale nie dają jej dużego pola manewru. Ta metoda jest więc stosowalna główne dla usług działających w tle. A raczej byłaby, gdyby nie istniały znacznie lepsze :)
BackgroundWorker
z Windows Forms jest tak prosty w użyciu). Gorzej jest wtedy, gdy na potrzeby asynchronicznego callbacku musimy rozbić na kilka części (i stanów) program, który bez tego działałby niemal sekwencyjnie.Między powyższymi sposobami możliwe są “konwersje”, oczywiście do pewnego stopnia. Wymagać to może uruchomienia dodatkowego wątku, w którym wykonujemy polling operację asynchroniczną lub wręcz blokującą, i którego stan możemy odpytywać lub otrzymać jako sygnał na obiekcie synchronizacyjnym po wejściu w stan przerywalnego czekania.
Nieczęsto jednak taka zabawa ma uzasadnienie. W najlepszym razie otrzymamy rozwiązanie równoważne, a najgorszym stracimy na wydajności operacji dostosowanej pod konkretny typ powiadamiania. Lepiej jest jednak trzymać się tego, co dana platforma i API nam proponuje.
Poznawanie nowych narzędzi programistycznych, bibliotek czy nawet platform to częsta konieczność w pracy kodera. Najczęściej jest to środek do osiągnięcia jakiegoś konkretnego celu projektowego, ale nierzadko możemy też zechcieć zagłębić się w nowe API dla samej przyjemności jego poznania. Jak się do tego zabrać? Są różne sposoby.
Możemy na przykład próbować jak najwięcej się o danej bibliotece dowiedzieć. W dzisiejszych czasach nie jest to trudne, bo standardem jest posiadanie dobrej (albo chociaż jakiejkolwiek ;]) dokumentacji. Ponadto wokół niemal każdej choć śladowo popularnej technologii natychmiast wyrastają internetowe społeczności, których dyskusje na forach (rzadziej już na grupach dyskusyjnych) mogą być również cennym źródłem informacji – zwłaszcza w przypadku wystąpienia problemów.
Postępując w ten sposób możemy zyskać bardzo szeroki i głęboki (jak morze :)) wgląd w funkcjonowanie danej biblioteki i jej strukturę, co może nam pomóc w jej użytkowaniu. Istnieje jednak szansa, że poprzez nadmierne skoncentrowanie się na opisach teoretycznych umkną nam ważne kwestie praktyczne.
Niejako przeciwną metodą jest jak najszybsze zajęcie się ową praktyką. W tym celu wystarcza zwykle przejrzenie różnorakich tutoriali (jeśli takowe są dostępne), a zwłaszcza dokładne przestudiowanie przykładowych fragmentów kodu. Zresztą eksperymentalne poznawanie nowej technologii może być wręcz kopiowaniem sampli, ich uruchamianiem, a następnie modyfikacją w celu zmiany sposobu działania, uelastycznienia lub poszerzenia o nową funkcjonalność.
Gdy w taki sposób zabieramy się do rzeczy, możemy szybko zobaczyć rezultaty. Jeśli jednak będziemy mieli pecha i pojawią się niespodziewane problemy, zaledwie pobieżna znajomość technologii może nam wydatnie utrudnić ich rozwiązanie. Poza tym takie kodowanie “na gorąco” wymaga nieporównywalnie częstszych wypadów na łono dokumentacji :)
Dokumenty zawierające wstawione fragmenty kodu to zazwyczaj artykuły w sieci formatowane przy pomocy znaczników HTML. Rzadziej pojawia się potrzeba wstawienia listingu programu do tekstu edytowanego w pakiecie biurowym, ale jeśli już takowa zajdzie, to warto wiedzieć, jak to zrobić. A właściwie: jak to zrobić (w miarę) dobrze :)
Należy mianowicie pamiętać o tym, że poważne edytory tekstu pozwalają na zgrupowanie ustawień formatowania tekstu w postaci styli. Analogia do klas CSS jest tu dość znaczna, chociaż występują pewne drobne różnice. Jednak i tu, i tu mamy do czynienia z narzędziem, które powinno wykonywać większość pracy związanej z nadawaniem tekstowi wyglądu. Oczywiście miejscowe pogrubienia czy podkreślenia są więcej niż dopuszczalne, ale dla większych odstępstw w formatowaniu – które zwykle mają jeszcze uzasadnienie merytoryczne – należy korzystać z istniejących stylów lub definiować własne. Pod to kryterium podpada na pewno wstawianie do tekstu kawałków kodu.
Niestety, twórcy Worda z pakietu Office uznali najwyraźniej, że niepotrzebny jest predefiniowany styl dla kodu, więc trzeba go sobie stworzyć we własnym zakresie. Należy wówczas pamiętać o:
p
i br
w HTML. Wciśnięcie klawisza Enter dodaje nam jednak nowy akapit, co domyślnie oznacza również wstawienie sporego odstępu pionowego. Nie chcemy takiego odstępu w linijkach kodu. Dlatego powinniśmy uaktywnić opcję Nie dodawaj odstępu między akapitami o tym samym stylu w ustawieniach akapitu.Wczoraj naciąłem się na nieprzyjemny rodzaj błędu w kodzie. Opierając się na powiedzeniu “Najciemniej pod latarnią”, można by go określić jako nieprzenikniona ciemność tuż pod najjaśniejszą latarnią w całym mieście. Normalnie nikomu nie przyjdzie do głowy, by jej tam szukać. Chodzi bowiem o jakąś “oczywistość”, w którą wątpienie jest nie tyle nierozsądnie, co wręcz nie przychodzi nawet na myśl.
A wszystko dlatego, że lubię prostotę – przynajmniej w takich kwestiach, jak wyniki funkcji informujące o ewentualnych błędach. Prawie zawsze są to u mnie zwykłe bool
e, informujące jedynie o tym, czy dana operacja się powiodła lub nie. To wystarcza, bo do bardziej zaawansowanego debugowania są logi, wyjątki, asercje i inne dobrodziejstwo inwentarza. A same wywołania funkcji możemy sobie bez przeszkód umieszczać w if
ach czy warunkach pętli.
Są jednak biblioteki, które “twierdzą”, że taka konwencja jest niedobra i proponują inne. Pół biedy, gdy chodzi tu o coś w rodzaju typu HRESULT
, do którego w pakiecie dostarczane są makra SUCCEEDED
i FAILED
. Zupełnie nie rozumiem jednak, co jest pociągającego w pisaniu np.:
Usprawiedliwiać się mogę jednak tym, że… no cóż, to przecież Linux ;-) Ale kiedy dostaję do ręki porządną bibliotekę z klasami, wsparciem dla STL-a i CamelCase w nazwach funkcji, to przecież mogę się spodziewać czegoś rozsądniejszego, prawda? Skąd może mi przyjść do głowy, że rezultat zero ma oznaczać powodzenie wykonania funkcji?!
Najwyraźniej jednak powinienem taką możliwość dopuszczać. Okazuje się bowiem, że nawet dobrze zaprojektowane, estetyczne API może mieć takie kwiatki. Przekonałem się o tym, próbując pobrać wartość atrybutu elementu XML, używając biblioteki TinyXML i jej metody o nazwie QueryStringAttribute
. W przypadku powodzenia zwraca ona stałą TIXML_SUCCESS
; szkoda tylko, że jej wartością jest… zero ;P
Więc zaprawdę powiadam wam: nie ufajcie żadnej funkcji, której typem zwracanym nie jest bool
!