Posts from 2010

Zamiast klawisza F7

2010-09-11 12:19

Kompilacja – rozumiana w najszerszym sensie “budowania” wynikowej aplikacji – to najwyraźniej całkiem serious business. Używając wyłącznie jednego IDE (zwłaszcza gdy jest nim Visual Studio), można długo nie zdawać sobie z tego sprawy. W końcu jedną z funkcji środowisk programistycznych jest właśnie maksymalne uproszczenie tego procesu. Idealnie ze strony użytkownika wymagane jest tylko wydanie polecenia zbudowania projektu, a czasem – w przypadku budowania w tle – nawet i to nie jest potrzebne.
Nie zawsze jednak tak było i także obecnie nie zawsze proces kompilacji programu może być czymś, co uważamy za automatycznie załatwione. Wiedzą o tym zwłaszcza aktywni uczestnicy projektów open source, a szczególnie tych pisanych w językach bez jedynie słusznych IDE czy kompilatorów – takich jak C, C++ czy Java. Nic dziwnego, że właśnie z ich otoczenia wywodzą się systemy zautomatyzowanego budowania projektów.

Starym, wysłużonym i trochę już niedzisiejszym, ale wciąż szeroko używanym jest GNU Make. To z niego prawdopodobnie wywodzą się takie podstawowe koncepcje automatycznej kompilacji jak choćby target, czyli rodzaju “punktu wejścia” całego procesu. Dwa polecenia typowo służące zainstalowaniu programu ze źródeł na systemach linuksowych:

  1. make
  2. make install

to właśnie wywołania GNU Make dla dwóch różnych targetów: domyślnego (uruchamiającego kompilację) i install (wykonującego inne czynności instalacyjne). Między targetami występują acykliczne zależności, określające wymaganą kolejność przeprowadzania poszczególnych kroków procesu budowy, a także pozwalające na pominięcie tych, które nie są aktualnie potrzebne. Dla przykładu, domyślny target może zajmować się linkowaniem, więc do działania wymagać skompilowanych modułów. Zależeć wtedy będzie od targetów, które zajmują się właśnie kompilacją. Przy dobrze napisanym pliku makefile wykonają się jedynie te odnoszące się do plików z kodem, w których faktycznie nastąpiły zmiany od czasu ostatniej budowy projektu.

Ponieważ zarówno składnia, jak i możliwości GNU Make (ograniczające się głównie do uruchamiania poleceń powłoki) pozostawiają wiele do życzenia, pojawiły się w końcu inne tego typu narzędzia. Potrzebowała ich głównie wieloplatformowa Java i stąd wziął się Apache Ant (skrót od Another Neat Tool). Nie straszy on już składnią opartą na tabulatorach; zamiast tego straszy XML-em ;-) Mimo większej przenośności międzysystemowej (abstrakcja operacji na plikach i katalogach) nadal brakuje mu jednak uniwersalności językowej, która zresztą nie wydaje się wcale celem istnienia Anta. Pomimo wyraźnego zorientowania na Javę da się go jednak z powodzeniem używać w projektach pisanych również w innych językach.

Te dwa rozwiązania to oczywiście nie jedyne systemy automatycznego budowania, z którymi możemy się spotkać. Z innych ciekawe są chociażby CMake czy Apache Maven. Warto się z nimi zapoznać, by na widok paczki, która oprócz kodu zawiera tylko plik build.xml lub makefile nie wpadać w popłoch :)

Tags: , ,
Author: Xion, posted under Applications, Programming » 5 comments

Androidowa biblioteka do screenów

2010-09-08 10:46

Prezentuję dzisiaj napisaną przez siebie bibliotekę, służącą do wykonywania zrzutów ekranowych (screenshotów) z urządzeń pracujących pod kontrolą platformy Android. Jej zaletą jest to, że może być używana na dowolnych telefonach dzięki pewnemu sprytnemu “hackowi”, ale wymaga uprzedniego podłączenia urządzenia do komputera i użycia narzędzi dołączonych do Android SDK.

Jeśli ktoś zajmuje się pisaniem aplikacji na platformę Android, to zapraszam do testowania. Biblioteka dostępna jest jako projekt w Google Code. Enjoy :)

Tags: , ,
Author: Xion, posted under Programming » 2 comments

Python, wcięcia i bardzo wredny błąd

2010-09-04 17:13

Niektórzy programiści, rozmawiając z innymi, znajdują przyjemność we wskazywaniu na specyficzne cechy różnych języków programowania, które im nie odpowiadają. Nie uważam tego za specjalnie produktywne i samemu staram się tego unikać. W końcu co za różnica, że w Javie czy C++ mamy nawiasy klamrowe, w Pascalu begin i end, a w Pythonie bloki wyróżniane wcięciami? Dla sprawnego programisty nie powinno to mieć żadnego znaczenia.
I faktycznie jest – dopóki nie okaże się, że pozornie nieistotna cecha języka prowadzi do błędów. Nieprzyjemnych, wrednych i trudnych do wykrycia błędów. Tak, wtedy dyskutowanie o podobnych składniowych błahostkach może być choć częściowo usprawiedliwione…

A skoro sam ten temat podjąłem, to wymaga mi przedstawić moje usprawiedliwienie. Nie jest ono długie. Wygląda bowiem tak:

  1. def escape_chars(text):
  2.     result = []
  3.     for c in text:
  4.         if ord(c) > 255:
  5.             result.append ("&#%s;" % ord(c))
  6.     else:
  7.         result.append (c)
  8.     return ''.join(result)

Ta prosta pythonowa funkcja ma za zadanie zamienić w podanym tekście znaki spoza zakresu ANSI na ich XML-owe odpowiedniki w postaci encji numerycznych (np. Lj). Niezbyt skomplikowane, prawda? A jednak całkiem długo zajęło mi dojście do tego, czemu dla tekstu z samymi znakami ANSI wynikiem jest… łańcuch pusty.

Jest oczywiście bardzo prawdopodobne, że ktoś patrząc teraz na powyższy kod znajdzie przyczynę w mniej niż dziesięć sekund – zwłaszcza, że niemal bezpośrednio zasugerowałem ją na samym początku. To naturalnie nie świadczy o niczym, bo napady specyficznego rodzaju ślepoty na rzeczy oczywiste są nieodłączną częścią zajęcia zwanego programowaniem :) Z tym nie ma sensu polemizować.
Ale jak najbardziej można dyskutować o tym, dlaczego “wrodzona” cecha składni języka uważanego za nieskomplikowany, efektywny i nowoczesny (cokolwiek to znaczy) może być bezpośrednią przyczyną powstawania błędów, o którym użytkownikom C, Javy czy Pascala nawet się nie śniło! Nie widzę innego wytłumaczenia oprócz krótkowzroczności projektantów języka, którzy nie potrafili uświadomić sobie, że jego feature‘y mogą wchodzić ze sobą nie tylko w pożyteczne, ale czasem i niepożądane interakcje.

Dla jasności wytłumaczę jeszcze dokładniej, w czym rzecz. Mianowicie w Pythonie koniec bloku kodu rozpoznawany jest nie obecnością terminatora w rodzaju } czy end, lecz zmianą poziomu wcięcia następnej linijki – czyli czymś, co w innych językach pełni rolę wyłącznie estetyczną. Wiersze mające tę samą liczbę początkowych spacji leżą więc na tym samym poziomie zagłębienia.
Stąd zaś wynika fakt, iż w powyższej funkcji fraza else nie jest wcale dołączona do instrukcji if, lecz do… pętli for. Prawidłowe zagnieżdżenie wygląda bowiem tak:

  1. def escape_chars(text):
  2.     result = []
  3.     for c in text:
  4.         if ord(c) > 255:
  5.             result.append ("&#%s;" % ord(c))
  6.         else:
  7.             result.append (c)
  8.     return ''.join(result)

Ale chwilka – jak pętla for może mieć else‘a?… Ano to już jest rzecz specyficznie pythonowska (a biorąc pod uwagę okoliczności, wręcz monty-pythonowska), którą zresztą kiedyś zdarzyło mi się opisać. Wówczas to określiłem ją tylko jako zawracanie głowy. Dzisiaj porównałbym ją raczej z możliwością wpisywania stałych ósemkowych w kodzie C. Oba feature‘y używane są świadomie średnio raz na trzy lata, przez resztę czasu potencjalne powodując jedynie błędy.

Tags: , ,
Author: Xion, posted under Programming » 9 comments

Odpalanie klasy

2010-08-31 12:09

Niskopoziomowe, wewnętrzne klasy logiki mają tę wadę (jeśli można to tak nazwać…), iż są właśnie niskopoziomowe i wewnętrzne – a przez to trudne do testowania w powiązaniu z całą aplikacją. Między nimi a interfejsem może znajdować się wiele warstw, które utrudniają debugowanie.
To sprawia, że przydatne stają się testy jednostkowe (unit tests). Do ich tworzenia i uruchamiania potrzeba jednak odpowiednich frameworków. Na szczęście te są już dostępne dla niemal każdego sensownego języka programowania i nierzadko są wręcz częścią jego lub jego środowiska.
Nie zawsze jednak tak było. Alternatywą dla ręcznego tworzenia specjalnych aplikacji przeznaczonych wyłącznie do testowania klas były (i właściwie nadal są) interesujące mechanizmy “uruchamiania klas” jako takich, które oferują niektóre języki programowania.

Dotyczy to przede wszystkim – jeśli nie wyłącznie – tych spośród nich, w których koncepcja programu czy aplikacji nie jest wyraźnie zakreślona. Coś jakiego jak entry point (“punkt wejścia”), od którego zaczyna się wykonywanie kodu, musi jednak istnieć i trzeba mieć jakiś sposób na jego określenie. Jeżeli zamiast aplikacji mamy tylko mniej lub bardziej luźny zbiór klas, to siłą rzeczy musi się on znajdować w którejś z nich.
A jeśli znajduje się w jednej, to czemu nie dodać go też do innych – także tych, które z założenia nie mają pojęcia o interakcji z użytkownikiem? Tworzymy w ten sposób pewnego rodzaju back-end, a owe dodatkowe punkty wejścia mogą posłużyć do testów. Oto prosty przykład w języku Java:
public class Sumator
{
private int sum = 0;
public void add(int x) { sum += x; }
public int getSum() { return sum; }

// testowy punkt wejścia
public static void main(String[] args) {
int n = args.length > 1 ? Integer.parseInt(args[1]) : 100;

Sumator s = new Sumator();
for (int i = 1; i <= n; ++i) s.add(i); System.out.println ("Spodziewana suma: " + (n * (n + 1) / 2)); System.out.println ("Otrzymana suma: " + s.getSum()); } }[/java] Statyczna metoda main to w Javie sposób na określenie punktu wejścia. Typowym miejscem dla niego jest główna klasa aplikacji, względnie całkiem osobna klasa przeznaczona do zarządzania samym uruchamianiem programu. Tutaj umieszczamy metodę main w klasie logiki (bardzo zaawansowanej zresztą ;-]), przez co możemy “odpalić” samą tę klasę i wykonać jakiś kod testujący jej funkcjonalność.

Z popularnych języków mających taki feature można jeszcze wspomnieć Pythona. W nim obiektowość nie jest obowiązkowa, więc to skrypt (plik .py) jest podstawową jednostką kodu, którą można uruchamiać:

  1. import sys
  2.  
  3. class Sumator:
  4.     def __init__(self): self.sum = 0
  5.     def add(self, x): self.sum += x
  6.  
  7. if __name__ == "__main__":
  8.     n = int(sys.argv[1]) if len(sys.argv) > 1 else 100
  9.     s = Sumator()
  10.     for i in range(1, n+1): s.add(i)
  11.     print "Spodziewana suma: ", (n * (n + 1) / 2)
  12.     print "Otrzymana suma: ", s.sum

Daje się w nim też umieścić “swobodny” kod, co na pierwszy rzut oka wydaje się dobrym miejscem na instrukcje testowe. Trzeba tylko otoczyć je pokazanym wyżej ifem, aby były one uruchamiane wyłącznie przy wywoływaniu skryptu z wiersza poleceń, nie zaś przy imporcie zawartego w nim kodu (instrukcją import).

Tags: , , , ,
Author: Xion, posted under Programming » Comments Off on Odpalanie klasy

ta’i ma la lojban. bangu fi lo na’e satci

2010-08-28 21:58

Jak w lojbanie można wyrażać się nieprecyzyjnie?

Każdy, kto zetknie się z lojbanem, najczęściej szybko dowiaduje się, skąd pochodzi jego nazwa. Źródłem jest określenie logji bangu, czyli język logiczny. Logika sugeruje zaś dokładność, jednoznaczność i precyzję. Sugeruje to, że w lojbanie nie można wyrażać się niejasno, mgliście czy metaforycznie. Gdyby tak było rzeczywiście, to trudno byłoby to uznać za jego siłę, skoro aspiruje on do miana języka, którym mogą się posługiwać (także) ludzie.
Można więc podejrzewać, że to nieprawda – i tak jest istotnie. Dzisiaj chciałbym pokazać kilka przykładów na to, że mimo swojej “logiczności” lojban jak najbardziej dopuszcza przenośnie, niedopowiedzenia, skróty myślowe i inne tego rodzaju niuanse.

Więcej: nie tylko je dopuszcza, ale wręcz do nich zachęca – między innymi za pomocą pojęcia kontekstu, które samo w sobie nie jest precyzyjnie określone. Nietrudno jednak zrozumieć je intuicyjnie. Kontekst to po prostu… no cóż, kontekst :) Przyjmuje się, że gdy coś z niego wynika, to jest to mniej lub bardziej oczywiste zarówno dla nadawcy, jak i odbiorcy wypowiedzi.
A z kontekstu może w lojbanie wynikać bardzo wiele. Za każdym razem, gdy opuścimy jakieś miejsce w zdaniu, to właśnie kontekst je uzupełnia – lub pozostawia nieokreślone:

mi jinvi lodu’u ba carvi ca le cabdei
Sądzę, że dzisiaj będzie padać deszcz.

Tutaj nie musimy na przykład troszczyć się o wypełnienie drugiego miejsca carvi (powierzchni, na którą pada) lub trzeciego (źródła deszczu), bo kontekst mówienia o pogodzie aż nadto wystarcza, żeby wywnioskować, co mamy na myśli. Na tej samej podstawie możemy często pomijać zaimki osobowe, nawet mimo tego, że lojban nie ma odmiany czasowników (że o samych czasownikach nie wspomnę ;P):

ju’i do’u klama le zarci .i xu kansa
Hej, idziemy do sklepu. Idziesz z nami?

Pomijanie czasów (przeszły/teraźniejszy/itd.) jest z kolei tak powszechne, że wyjątkiem są raczej te sytuacje, gdy potrzebne jest ich użycie. Wówczas też chodzi bardziej o określenie punktu odniesienia (ca le cabdei – dzisiaj) niż o wymagania samej gramatyki.

Powyższe przykłady pokazują, jak pomijanie niektórych części wypowiedzi nie musi wpływać negatywnie na jej zrozumiałość. Innym mechanizmem wprowadzania “niejasności” jest tanru, czyli metafora. Polega on na połączeniu dwóch predykatów tak, że pierwszy modyfikuje znaczenie drugiego. Szczegóły tej modyfikacji nie są dokładnie określone i jest tak by design. Użycie tanru wprowadza więc pewną nieprecyzyjność jako cenę za skrócenie wypowiedzi:

mi xabju lo barda zdani — Mieszkam w dużym domu.
mutce xamgu fa lonu cilre fi la lojban. — Uczenie się lojbanu jest bardzo pożyteczne. (;P)
ko sutra bajra klama fa’a mi — Biegnij szybko w moją stronę.

Jak widać odpowiednikami tanru w innych językach są m.in. połączenia rzeczowników z przymiotnikami, a także stopniowanie tych drugich. Da się więc tutaj zauważyć pewne schematy użycia, ale w gruncie rzeczy możliwości tworzenia tanru są nieograniczone – podobnie jak możliwości ich interpretacji.

Takie stwierdzenie może nieco dziwić wobec często podkreślanej jednoznaczności lojbanu. Trzeba jednak pamiętać, że dotyczy ona wyłącznie gramatyki i objawia się tym, że poprawny tekst w lojbanie jest parsowalny zawsze w dokładnie jeden sposób. To zupełnie inaczej niż w typowych językach naturalnych, czego często przytaczanym przykładem jest “angielskie tanru“:

pretty little girls school

W zależności od “nawiasowania” może ono znaczyć: szkołę dla całkiem małych dziewczynek, całkiem małą szkołę dla dziewczynek, małą ładną szkołę dla dziewczynek – i jeszcze kilkanaście innych kombinacji. Lojbański odpowiednik (milxe cmalu nixla ckule) jest z kolei zawsze odczytywany od lewej do prawej i jednoznacznie wyraża pierwszą interpretację.

Tags: ,
Author: Xion, posted under Culture » 4 comments

Porady odnośnie ubarwiania kodu

2010-08-24 20:01

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.


Kolory idealne dla Perla ;>

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).


Coraz bliżej święta ;)

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:

  1. słowa kluczowe
  2. nazwy własnych typów
  3. wartości stałe (liczbowe, znakowe i tekstowe)
  4. nazwy funkcji i metod
  5. pozostałe identyfikatory
  6. komentarze

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.

Tags:
Author: Xion, posted under Applications, Programming » 6 comments

Triki z PowerShellem #16 – Parametry

2010-08-22 14:44

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:

  1. function Fib
  2. {
  3.     param ([int]$x)
  4.     if ($x -eq 0 -or $x -eq 1)  { return 1 }
  5.     return (Fib ($x - 1)) + (Fib ($x - 2))
  6. }

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:

  1. param (..., [typ]$nazwa = wartośćDomyślna, ...)

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:

  1. param ([string]$name = $(throw "Name required"))

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:

  1. param ([switch]$Debug)
  2. # ...
  3. if ($Debug) { "Debug mode enabled." | Out-Host }

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ść:

  1. Fib -x 12 | Out-Host

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:

  1. Fib 12

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ść.

Tags: ,
Author: Xion, posted under Applications, Programming » 2 comments
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.