Inżynieria oprogramowania

Jak kończyć projekty

  • 2008-10-19 01:06
  • 9 komentarzy
  • 3612 odsłon
  • Oceń ten tekst jako pierwszy
Spis treści

          1 Wstęp
               1.1 Projekt
          2 Filozofia programowania
               2.1 Podział programowania
               2.2 Początkujący a zaawansowani
               2.3 Natura hakera
               2.4 Programowanie jako praca twórcza
               2.5 Kończenie projektów
          3 Projektowanie
               3.1 Inżynieria oprogramowania
               3.2 Podejmowanie decyzji
                    3.2.1 Liczenie na palcach
                    3.2.2 Tabelka
                    3.2.3 Tabelka ważona
                    3.2.4 Podsumowanie
               3.3 Formy projektowania
                    3.3.1 Narzędzia
     1
Projekt gry
                    1.3.1 Formy tekstowe
                         1.3.1.1 Listy
                         1.3.1.2 Listy TODO
                    1.3.2 Formy graficzne
                         1.3.2.1 Schematy i diagramy
                         1.3.2.2 Tabele
                         1.3.2.3 Okno główne
                         1.3.2.4 Mapy myśli
               1.4 Etapy projektowania
                    1.4.1 Faza strategiczna
                    1.4.2 Faza określania wymagań
                    1.4.3 Faza analizy
                    1.4.4 Faza projektowania
                         1.4.4.1 Pseudokod
     2 
System dźwiękowy - interfejs
                         2.4.4.1 Diagramy klas
                         2.4.4.2 Diagramy przejść stanów
                    2.4.5 Podsumowanie
               2.5 Jak projektować?
          2.1 Programowanie
               2.1.1 Programowanie a projektowanie
               2.1.2 Co trzeba umieć?
               2.1.3 Jak programować?
               2.1.4 Kod
               2.1.5 Komentarze
               2.1.6 Dokumentacja
               2.1.7 Kopie bezpieczeństwa
          2.2 Testowanie
               2.2.1 Szukanie błędów
                    2.2.1.1 Beep
                    2.2.1.2 Logi
                    2.2.1.3 Przeglądanie kodu
                    2.2.1.4 Praca krokowa
                    2.2.1.5 Komentowanie kodu
                    2.2.1.6 Podsumowanie
               2.2.2 Testy
                    2.2.2.1 Testy Alfa
                    2.2.2.2 Testy Beta
               2.2.3 Optymalizacja
          2.3 Zakończenie
          2.4 Literatura

Wstęp

Artykuł ten przeznaczony jest dla osób zajmujących się programowaniem. Obejmuje praktyczne aspekty inżynierii oprogramowania oraz doświadczenia autora dotyczące procesu programowania w sensie tworzenia programów.

W życiu każdego, kto uczy się programowania przychodzi taka chwila, kiedy czuje, że <em>zamiast tylko programować czas zacząć pisać programy</em>. Jeśli umiesz coraz więcej, ale czujesz, że twoja praca nie przynosi owoców, to znaczy, że <em>musisz najpierw potrząsnąć drzewem</em>. Takim wstrząsem może być systematyczne podejście do programowania oraz poznanie podstawowych technik projektowania.

Skąd ten tytuł? Czy artykuł nie powinien być raczej zatytułowany "Inżynieria oprogramowania w praktyce"? Nie, ponieważ wszystkie przedstawione tu informacje, porady i metody mają wspólny cel - zwiększenie prawdopodobieństwa ukończenia prowadzonych projektów programistycznych, zwiększenie ilości ukończonych programów.

Do kogo adresowany jest ten artykuł? Przede wszystkim do młodych pasjonatów programowania, którzy zajmują się indywidualnym pisaniem niekomercyjnych programów i gier. Pisanie zespołowe wymaga trochę innego podejścia i wzięcia pod uwagę dodatkowych zagadnień (jak komunikacja czy wspólna praca nad kodem), których ten tekst nie obejmuje. Mimo tego niektóre zawarte w nim wskazówki mogą się okazać przydatne także w takim przypadku. Dużo łatwiej powiedzieć, do kogo ten artykuł adresowany z całą pewnością nie jest:

  • Jeśli jesteś początkującym, który dopiero poznaje podstawy programowania, nawet jeśli zrozumiesz ten artykuł (nie powinno ci to sprawić problemu), raczej nie będziesz w stanie zastosować podanych tu rad w praktyce. Aby pisać programy, musisz już umieć programować na tyle, żeby elementarne problemy techniczne nie były dla ciebie przeszkodą.
  • Jeśli jesteś zawodowym programistą po studiach informatycznych i uczyłeś się inżynierii oprogramowania na zajęciach w sposób systematyczny, z pewnością wiesz o tym więcej niż ja. Jeśli ponadto masz za sobą długą praktykę w programowaniu, potrafisz tą wiedzę doskonale zastosować.
  • Jeżeli jesteś programistą pracującym dla firmy i zarabiasz na programowaniu, zupełnie inna jest twoja sytuacja, zupełnie inne są warunki, w jakich pracujesz, a także zupełnie inna jest twoja motywacja. Projektowaniem i programowaniem nierzadko zajmują się w firmach zupełnie różne osoby. Opisane tu problemy po prostu cię nie dotyczą.

Osobom bardziej doświadczonym ode mnie w programowaniu przedstawione w tym artykule informacje mogą się wydać proste, banalne, a z niektórymi z nich można się nie zgodzić. Większość stanowią jednak chyba osoby początkujące i dlatego mam nadzieję, że moje 9-letnie doświadczenia w programowaniu, spisane w tym dokumencie, w jakiś sposób zmienią twój sposób myślenia i pomogą ci zwiększyć szanse na kończenie prowadzonych projektów.

Projekt

Na zakończenie tego wstępu chciałbym wspomnieć, że słowo "projekt" jest moim zdaniem bardzo niefortunne i osobiście nie lubię go. Jego popularność pośród osób zajmujących się programowaniem pochodzi zapewne od angielskiego słowa "project", które według słownika angielsko-angielskiego oznacza przede wszystkim: "task that requires a lot of time and effort", czyli "zadanie wymagające dużo czasu i wysiłku".

Tymczasem słownik języka polskiego nie pozwala na używanie słowa "projekt" w takim znaczeniu. Po polsku "projekt" to wyłącznie plan, zamiar zrobienia czegoś, np. projekt budynku. Angielskie "project" należałoby więc tłumaczyć bardziej jako "przedsięwzięcie" czy "zadanie".

Skoro jednak to określenie jest tak powszechne, że praktycznie nikt nie zwraca uwagi na problemy z jego znaczeniem, ja również będę go używał w tym artykule mimo, że wtedy będzie trzeba mówić o "projektowaniu projektu" i "pisaniu projektu", bo projektowaniem trzeba też nazwać sporządzanie prawdziwego projektu (w takim znaczeniu, jakie obowiązuje w języku polskim). Mamy więc do czynienia z "projektem projektu" :)

Filozofia programowania

Na początek chciałbym trochę pofilozofować i przedstawić swoje przemyślenia na temat istoty zajmowania się programowaniem.

Podział programowania

Wydaje mi się, że uprawiane przez nas programowanie można podzielić na takie trzy rodzaje:

  • Nauka - jest wtedy, kiedy bawisz się w ucznia, czyli poznajesz nowe języki i biblioteki, czytasz kursy i dokumentacje, nabywasz doświadczenie i oswajasz się z nowym środowiskiem, w których chcesz pracować.
  • Eksperymentowanie - jest wtedy, kiedy bawisz się w naukowca, czyli pracujesz nad jakimś zakręconym pomysłem, który ma małe szanse przynieść bardzo dobre efekty (chyba że masz naprawdę dużą wiedzę na dany temat) - np. praca nad własnym formatem plików graficznych, nad sztuczną inteligencją i innymi tematami, które cię fascynują.
  • Pisanie programów jest wtedy, kiedy bawisz się w zawodowego informatyka i korzystając z dobrze już znanej, ugruntowanej wiedzy robisz coś, co chcesz dobrze zaprojektować i napisać, aby mieć na swoim koncie ukończoną produkcję.

Prawdziwemu pasjonatowi programowania powinna sprawiać przyjemność każda z tych form. Robiąc to, na co masz ochotę nie zapominaj jednak utrzymać między nimi odpowiedniej równowagi.

Początkujący a zaawansowani

Ten podrozdział prezentuje moją teorię na temat cech odróżniających osoby początkujące od zaawansowanych. Są to w dalszym ciągu rozważania na temat programowania, które mają nas wprowadzić w klimat :)

Początkujący, to ten, dla którego liczy się głównie efekt i chce go otrzymać jak najszybciej, jak najprościej, <em>po linii najmniejszego oporu</em>. Zaawansowany to ten, któremu przyjemność sprawia samo programowanie a satysfakcję sama świadomość, że napisany kod działa, nawet bez widocznych i namacalnych efektów.

To normalne, że prawie każdy rozpoczyna swoją przygodę z programowaniem licząc na efekty. Z czasem jednak powinno zacząć podobać mu się samo programowanie jako proces twórczy. Jeśli sam ten proces (szczególnie kiedy wymaga poznania czegoś nowego, głębszego zastanowienia się, przeanalizowania problemu) jest dla kogoś męczarnią, a nie sprawia mu przyjemności, to powinien poważnie zastanowić się, czy w ogóle nadaje się na programistę.

Początkujący mówi, że po prostu nie potrafi, nie jest w stanie napisać niektórych rzeczy - bo nie umie czegoś, co jest mu do tego potrzebne. Czasami także wielkość projektu po prostu go przerasta. Zaawansowany natomiast potrafi nauczyć się czegoś nowego, co jest mu akurat potrzebne - algorytmu, biblioteki, jakiś trików, metod, a nawet pewnych zagadnień z matematyki. Duże projekty natomiast to dla niego po prostu kwestia bardzo dobrego zaprojektowania i bardzo dużej ilości czasu potrzebnego na realizację.

Często też początkujący <em>porywają się z motyką na słońce</em> rozpoczynając realizację projektów, których nie są w stanie wykonać. Wynika to oczywiście z nieprzemyślenia sprawy samego pisania, które ich czeka, a jedynie z marzenia o efekcie końcowym.

Początkujący bazują wyłącznie na kursach (ang. "tutorials") znalezionych w Sieci mimo, że w większości z nich opisane są tylko niektóre możliwości, funkcje, zagadnienia z danego tematu. Perspektywa korzystania z prawdziwej, oficjalnej dokumentacji ich przeraża. Zaawansowani potrafią korzystać z dokumentacji, która pochodzi od autorów danej biblioteki czy języka i opisuje w sposób systematyczny wszystkie jego funkcje.

To oczywiste, że nikt nie uczy się i nie zna na pamięć wszystkich potrzebnych mu funkcji. Oczywiste jest też, że oryginalna, systematyczna dokumentacja w postaci spisu funkcji i ich działania nienajlepiej nadaje się do nauki danego zagadnienia od podstaw. Jednak poznawanie każdej nowej dziedziny, biblioteki, języka itp. powinno przebiegać tak:

  1. Lektura kursów, aby wstępnie poznać i zrozumieć dany temat.
  2. Przejrzenie i potem korzystanie z oficjalnej dokumentacji.

Natura hakera

Istotą sprawy, do której zmierzam, jest przedstawienie pojęcia "haker". Nie chodzi tu jednak o to znaczenie tego słowa, które jest najpopularniejsze i które lansują media - o internetowego wandala czy nawet o postępującego według jakiegoś kodeksu etycznego młodzieńca, który tylko dla sportu włamuje się przez Internet.

Prawdziwe, pierwotne znaczenie określenia "haker" (ang. "hacker") powstało w latach pięćdziesiątych w MIT (Massachusetts Institute of Technology). Pochodzi od słowa "hack", którym określano wtedy śmieszny, ale nieszkodliwy żart wymagający sporej wiedzy technicznej. Hakowanie miało też dużo wspólnego z włamywaniem się - ówcześni studenci często wchodzili w miejsca opatrzone napisem "Wstęp wzbroniony", np. żeby dostać się do nieużywanych akurat komputerów.

W tym sensie haker to pewna natura ludzka, pewien sposób myślenia. Niektórzy twierdzą nawet, że hakerem można być w każdej dziedzinie, nie tylko w informatyce, w programowaniu. Trudno powiedzieć, czy hakerem trzeba się urodzić, czy też można nim zostać. Prawda leży pewnie gdzieś pośrodku. Wszyscy rodzimy się z z pewnymi cechami charakteru, a bycie hakerem to po prostu posiadanie pewnego zespołu cech.

W największym skrócie można wymienić niektóre cechy typowego hakera: biegłość w zagadnieniach technicznych, zaradność, znajdowanie upodobania w rozwiązywaniu problemów i przekraczaniu ograniczeń. Hakerzy rozwiązują problemy i tworzą rzeczy wierząc w wolność i wzajemną pomoc. Są kreatywni i bardzo efektywni (szybko się uczą i szybko pracują), jeśli tylko mają motywację. Nuda jest ich wrogiem.

Ponadto hakerów cechuje często aspołeczność, nocny tryb życia, używanie środków psychoaktywnych, jak kofeina (ale równocześnie niechęć do narkotyków), wolnościowe poglądy, obojętność wobec spraw religijnych, niebywały talent do nauk ścisłych, zainteresowanie fantastyką i grami RPG, a także zainteresowania językowe. Mówi się natomiast, że hakerzy rzadziej, niż inni ludzie dbają o wygląd zewnętrzny, o rozwój emocjonalny czy o kondycję fizyczną. Rzadziej też interesują się sportem mimo tego, że praktycznie cała populacja hakerów jest płci męskiej.

Programowanie jako praca twórcza

Co jest istotą programowania? Chociaż informatyce bliżej do nauk technicznych (czy wręcz matematycznych) niż do dziedzin humanistycznych, to jednak programowanie jest pracą twórczą. Dlatego opiera się nie tylko na wiedzy, ale w bardzo dużym stopniu także na doświadczeniu praktycznym. Wymaga kreatywności i samodzielności, twórczego myślenia i umiejętności rozwiązywania problemów.

Programowanie jest jednocześnie nauką i sztuką. Istnieje nawet takie pojęcie jak poezja kodu. W tych instrukcjach dla maszyny istotnie musi być coś magicznego, skoro programiści postrzegają je tak odmiennie od pozostałych ludzi, dla których programowanie jawi się jako zagadnienie nudne, wręcz jako czarna robota. Co to takiego?

Kod, jak każdy tekst, oddaje pewne intencje autora, jest środkiem wyrazu, odzwierciedla jego niepowtarzalny styl. Każde zadanie różni programiści rozwiążą na różne sposoby. Kod to jednak coś więcej niż tekst. Jego można nie tylko czytać. On się wykonuje - działa - można obserwować efekty jego pracy - i właśnie to jest moim zdaniem tak niezwykłe w programowaniu.

Kończenie projektów

Na zakończenie tych rozważań zadajmy sobie pytanie: Po co w ogóle kończyć projekty? Samo programowanie też sprawia przyjemność i przynosi doświadczenie. Jednak taka potrzeba wydaje się oczywista. Sens i cel kończenia projektów można ująć w kilku punktach:

  • Posiadanie na swoim koncie ukończonego programu czy gry to ogromna satysfakcja.
  • To także coś, co jest w miarę trwałe - pozostaje nawet kiedy ty już o tym zapomnisz i weźmiesz się za następny projekt, może ci przynieść prestiż i popularność.
  • Ukończona produkcja to coś, co reprezentuje ciebie, co możesz zamieścić na swojej stronie WWW.
  • Wreszcie, ukończona i dostępna do użytku aplikacja po prostu może się komuś przydać - program może się okazać użyteczny, a gra może dostarczyć ludziom rozrywki.

Dlaczego nie kończymy projektów i dlaczego zdarza się to nam aż tak często? Co jest przyczyną, że odechciewa się nam pisać dany program? Czy to jest nierozwiązana tajemnica wszechświata? Być może, ale można spróbować sformułować kilka przyczyn:

  1. Zdarza się, że rozpoczynamy projekt zbyt ambitny - tak duży i tak wymagający, że nie jesteśmy w stanie go zrealizować.
  2. Zdarza się, że w pewnym momencie brakuje nam wiedzy, umiejętności i doświadczenia, by zrealizować jakąś rzecz, a w konsekwencji by kontynuować i ukończyć rozpoczęty projekt.
  3. Zdarza się, że pisanie danego programu staje się nudne - okazuje się dużo mniej ciekawe, niż wcześniej było myślenie i marzenie o nim, projektowanie go itp. Czas także działa tu na naszą niekorzyść.
  4. Zdarza się, że wpadamy na nowy, dużo lepszy i ciekawszy pomysł i natychmiast rozpoczynamy jego realizację porzucając poprzedni.
  5. Zdarza się, że rozpoczynamy pisanie podczas gdy sami do końca nie wiemy, co to ma być, jak ma wyglądać i jak ma działać.
  6. Zdarza się, że piszemy niczego wcześniej nie projektując, nie planując i okazuje się, że nie wiemy, jak nasze zadanie ma zostać zrealizowany, jak program ma być zbudowany. Czasami nawet nie potrafimy go zaprojektować, nie mamy na niego żadnej koncepcji.
  7. Zdarza się, że pisząc jakiś moduł chcemy uczynić go zbyt elastycznym, skalowalnym i uniwersalnym, a w konsekwencji piszemy go długo lub w ogóle go nie kończymy.
  8. Zdarza się, że w programowaniu przeszkadzają niesprzyjające czynniki zewnętrzne, jak rodzice, rodzeństwo czy szkoła. Najczęściej po prostu opóźniają pisanie, co może mieć jednak bardzo zły wpływ na powodzenie projektu.

Projektowanie

Realizację projektu podzieliłem na trzy etapy: projektowanie, programowanie i testowanie. Rozpoczynamy teraz omawianie pierwszego z nich. Zanim jednak zaczniemy, musisz poznać w skrócie teoretyczne podstawy dziedziny zwanej inżynierią oprogramowania.

Inżynieria oprogramowania

Inżynieria oprogramowania nie zajmuje się tylko samym programowaniem, ale tworzeniem programów. Traktuje programy jako produkty (tak jak inne rzeczy w sklepach) i tak też podchodzi do ich wytwarzania. Powstawanie programu (albo szerzej: jego cykl życia) według tzw. modelu kaskadowego dzieli na fazy:

Faza strategiczna
Polega na podjęciu decyzji dotyczącej tego, co pisać.
Faza określania wymagań
Polega na określeniu wymagań, jakie ma spełniać program.
Faza analizy (modelowania)
Polega na określeniu, jak program ma działać, żeby spełniał postawione mu wymagania.
Projektowanie
Polega na zaprojektowaniu implementacji programu, czyli zaplanowaniu, jak będzie wyglądał jego kod (podział na moduły, klasy, funkcje itp.).
Implementacja
Polega na napisaniu programu - to jest sedno programowania.
Dokumentacja
Odbywa się równolegle z innymi fazami i polega na sporządzeniu dokumentów opisujących tworzony program.
Testowanie
Polega na znalezieniu i usunięciu błędów.
Instalacja
Polega na przekazaniu systemu końcowemu użytkownikowi.
Konserwacja
Polega na dalszym poprawianiu programu, pisaniu kolejnych wersji itp.

Inżynieria oprogramowania dostosowana jest do projektów komercyjnych, pisanych na zamówienie lub przeznaczonych na rynek, do projektów dużych, pisanych zespołowo. Dlatego nie może być bezpośrednio stosowana do warunków, w jakich pracujesz jako adresat tego artykułu. Ścisłe przestrzeganie jej zaleceń, jak pisemne, formalne definiowanie wszystkiego w każdej z wymienionych wyżej faz nie miałoby z resztą większego sensu.

Książkę poświęconą inżynierii oprogramowania przeczytałem jakiś czas temu, a w niniejszym artykule prezentuję kompilacje metod i technik praktycznych, zebranych przede wszystkim na podstawie mojego własnego doświadczenia. Jakiekolwiek echa inżynierii oprogramowania są więc tutaj mocno zrewidowane i dostosowane do konkretnych potrzeb. Stopień, w jakim obmyślasz, projektujesz, a po napisaniu testujesz swój program musi być zawsze adekwatny do jego wielkości i złożoności.

Podejmowanie decyzji

Rozpocznijmy więc nareszcie omawianie tych praktycznych metod. Pierwszym wyróżnionym przeze mnie etapem realizacji projektu jest projektowanie. Pod tym pojęciem rozumiem wszystko to, co wedle inżynierii oprogramowania zawiera się w fazie strategicznej, fazie określania wymagań, fazie analizy i projektowania.

Mówiąc prościej - chodzi o zaplanowanie programu, zanim rozpoczniemy jego pisanie. To ważne, bo bezmyślne włączenie IDE i rozpoczęcie pisania rzadko owocuje czymkolwiek pozytywnym. Nie można przesadzać w żadną stronę, ale warto zawsze trochę przemyśleć i zaprojektować swój program, zanim rozpocznie się programowanie. Co najwyżej projekt upadnie, zanim jeszcze na dobre się rozpocznie :)

Najpierw chciałbym nauczyć cię podejmowania decyzji. To bardzo cenna i ważna umiejętność, bo podczas programowania bardzo często musisz dokonywać różnych wyborów, a podczas projektowania będziesz to robił jeszcze częściej. W sposób intuicyjny można podejmować tylko najprostsze i najbardziej oczywiste decyzje. Do pozostałych warto poznać pewne systematyczne metody.

Liczenie na palcach

Decyzje do podjęcia bywają różne. Najprostszą sytuacją jest, kiedy zadajesz sobie pytanie czy powinieneś coś zrobić, czy też nie. Najprostszą techniką do podejmowania takich decyzji jest z kolei liczenie na palcach - na prawej ręce zalet, a na lewej ręce wad. Dla przykładu rozpatrzmy taki problem:

Czy swoją grę powinienem pisać w 3D?

Na prawej ręce odliczam zalety:
1. Gra będzie ładniejsza, bardziej atrakcyjna
2. Nauczę się przy okazji 3D

Na lewej ręce odliczam wady:
1. Będę się musiał nauczyć 3D...
2. ...To będzie trudne...
3. ...Pisanie potrwa dłużej...
4. ...Mniejsze są szanse, że to skończę

Decyzja: Więcej jest wad, piszę grę dwuwymiarową!

Przy okazji może się okazać, jak w przykładzie powyżej, że zalety trzeba było specjalnie wynajdywać, a wady można było bez trudu wymieniać jednym ciągiem. Czasami wydaje się nawet, że takim formalnym podejmowaniem decyzji próbuje się tylko potwierdzić, uargumentować swoją znaną wcześniej odpowiedź. To potwierdza moją hipotezę, że każde rozwiązanie, każda odpowiedź jest gdzieś w nas - trzeba tylko umieć po nią sięgnąć.

Tabelka

Kiedy problem jest trochę bardziej skomplikowany (albo kiedy brakuje palców do liczenia :) warto wziąć kartkę, długopis i sporządzić sobie tabelkę. W swojej prostszej odmianie będzie to tabelka nieważona - każdy z argumentów jest tak samo ważny.

Za pomocą tabelki też można podejmować decyzje typu "tak/nie", ale można również dokonywać wyboru pomiędzy różnymi możliwościami. Będziemy znowu zliczali, ile jest argumentów przemawiających za każdą z nich. Najpierw trzeba je wypisać w kolumnach, a potem podliczyć. Oto przykład:



Tabelka ważona

Kiedy czujesz, że taka metoda jest nie w porządku, bo różne argumenty mają różne znaczenie, czas na przypisanie im współczynników wagowych. Tabelka ważona jest trochę trudniejsza, ale często się przydaje i dlatego postaraj się ją dobrze zrozumieć. Najpierw popatrz na przykład, a następnie przeczytaj objaśnienie poniżej.

Najpierw wypisałem w trzech kolumnach trzy opcje, między którymi mam dokonać wyboru - Konsola tekstowa, Windows GDI oraz DirectX. Potem w kolumnie Zagadnienie wypisałem wszystkie 4 ważniejsze kryteria, według których będę oceniał każdą z tych opcji.

Dla każdego zagadnienia każdej opcji przyznaję pewną liczbę punktów. Zagadnienia są sformułowane tak, żeby zawsze więcej punktów przemawiało na korzyść, a nie na niekorzyść danego wyboru. Jako przykład rozpatrzmy zagadnienie Jaki ładny. Każde rozpatrywane API graficzne oceniam w nim pod względem tego, jak atrakcyjną grafikę można moim zdaniem za jego pomocą prezentować. API, w którym grafika wygląda najgorzej (czyli Konsola tekstowa) dostaje 0 punktów, a API, w którym mogę przygotować najbardziej efektowną grafikę - maksimum, czyli 10 punktów. Pozostałe, trzecie API (Windows GDI) plasuje się gdzieś między nimi, a ponieważ w porównaniu z trójwymiarowymi możliwościami DirectX wypada bardzo blado, otrzymuje ostatecznie 2 punkty.

Po wypełnieniu tabelki czas na przypisanie wag poszczególnym zagadnieniom. Posłużyła mi do tego ostatnia kolumna zatytułowana Waga. Najważniejsza jest dla mnie prostota danego API, więc te spawa otrzymuje maksymalną wagę - 10. Pozostałe są bardziej albo mniej ważne i otrzymują różne wagi mniejsze od 10. Żadne zagadnienie nie może otrzymać wagi 0 chyba, że zupełnie nie powinno się liczyć do końcowego wyniku (ale wtedy po co w ogóle je pisać?).

Wreszcie czas na obliczenia. Ponieważ mamy zdecydować się na jedną z trzech rozpatrywanych opcji, musimy podliczyć sumę punktów osobno każdej z nich. Robimy to mnożąc każdą liczbę w kolumnie przez wagę zagadnienia w danym wierszu i dodając te wyniki do siebie. Na przykład Windows GDI ma w wierszu pierwszym (zatytułowanym Jak prosty) wartość 6, a waga tego zagadnienia to 10, więc po pomnożeniu wychodzi 60. W ten sam sposób liczymy pozostałe wiersze dla tej opcji i ostatecznie liczba punktów Windows GDI to:

6*10 + 0*5 + 2*7 + 1*3 = 60 + 0 + 14 + 3 = 77

Analogicznie trzeba policzyć pozostałe kolumny. Wygrywa ten wybór, który otrzyma najwięcej punktów. Jeśli wszystkie pozostałe mają dużo mniej (jak w tym przykładzie), to dobrze - decyzja jest jednoznaczna. Jeśli dwa lub więcej mają równie dużo, trzeba się jeszcze raz zastanowić.

Po takich obliczeniach może się zdarzyć, że będziesz miał uczucie, że nie takiego wyniku oczekiwałeś. Czy to oznacza, że takie formalne podejmowanie decyzji jest bez sensu? Absolutnie nie. To znaczy, że powinieneś jeszcze raz przemyśleć wagi (ważność) poszczególnych zagadnień. Być może też zapomniałeś dodać jakieś ważne kryterium (zagadnienie), które może zadecydować o innym wyniku.

Podsumowanie

Takie tabelki można rysować sobie na komputerze - w Notatniku, w Pancie albo lepiej w arkuszu kalkulacyjnym, jak Microsoft Excel. Osobiście do projektowania zwykle wolę jednak kartkę papieru. Chociaż nie można czegoś na niej przenieść, czegoś skasować i nic się na niej samo nie policzy, to ma jedną wielką zaletę - daje ogromne możliwości formatowania :) Można swobodnie rysować kreski, symbole, krawędzie tabel itp.

Dodatkową techniką podejmowania decyzji jest odrzucanie tych opcji, które w ogóle nie mają sensu. Najczęściej będą to rzeczy zbyt duże, zbyt poważne albo za trudne. Odrzucić trzeba też jakąś opcję wtedy, kiedy jakaś inna opcja jest lepsza od niej pod wszystkimi możliwymi względami. Jeśli w takiej sytuacji nie chcesz takiej opcji odrzucić, to znaczy, że widzisz pewnie jakąś jej zaletę, tylko zapomniałeś jej uwzględnić.

Kiedy odrzucać? Przede wszystkim wtedy, kiedy możliwych opcji jest dużo. Wtedy odrzucanie tych najgorszych to doskonały sposób na uproszczenie problemu. Czasami już na wstępie widać, że jakieś rozwiązanie kompletnie się nie nadaje, nie wchodzi w grę. Można je wtedy odrzucić, albo dla świętego spokoju uwzględnić przy podejmowaniu decyzji - jego niższość powinna wtedy wyraźnie wyjść z obliczeń. Niektóre możliwości można też odrzucić już po wpisaniu do tabelki wartości liczbowych.

Przeprowadzanie formalnego, pisemnego podejmowania decyzji (na kartce albo na komputerze) ma jeszcze jedną wielką zaletę - stanowi dowód podjętej decyzji razem ze spisanymi argumentami, które o niej zdecydowały. Dlatego takie pliki czy kartki należy zawsze zostawiać. Jeśli kiedyś najdą cię wątpliwości co do słuszności takiej decyzji, odnajdziesz wtedy tą kartkę, spojrzysz na nią i upewnisz się, że podjąłeś słuszną decyzję. To ważne, bo do raz podjętych decyzji nie należy wracać. Chyba, że od czasu jej podjęcia zmieniły się argumenty albo ich ważność - wtedy można podjąć decyzję jeszcze raz.

Formy projektowania

Pisemny projekt to jakby przedłużenie umysłu - zapisujesz w nim to, co trudno jest ogarnąć myślami (bo jest za duże i zbyt skomplikowane), żeby rozpisać to w sposób systematyczny, ujrzeć i zachować przed zapomnieniem jakiegoś szczegółu.

W tym podrozdziale chciałbym opisać moje sposoby na organizację projektu i formę, w jakich warto sporządzać projekty. Na początek zajmijmy się doborem odpowiednich narzędzi.

Narzędzia

Wbrew temu, co mogłoby się wydawać, papier wcale nie odchodzi do lamusa. Moim zdaniem do projektowania warto używać kartki papieru, bo daje ona ogromne możliwości formatowania, niedostępne w żadnym programie. Na kartce możesz swobodnie i prosto pisać, podkreślać, przekreślać, rysować strzałki, a nawet całe rysunki, schematy, diagramy i tabele.

Jednak niektóre rzeczy warto robić na komputerze. Najlepiej nadaje się do tego zwykły, systemowy Notatnik. Dokument tekstowy na komputerze ma tą przewagę nad notatką na papierze, że możesz go swobodnie przerabiać, organizować, dopisywać i usuwać jego części w różnych miejscach. Ma też swoje wady - daje mniejszą swobodę formatowania i wymaga włączania komputera. Czasami warto projektować przy wyłączonym komputerze - a raczej czas, kiedy komputer nie jest włączony poświęcać na projektowanie.

Pośród narzędzi bardziej zaawansowanych, niż kartka papieru czy Notatnik, warto wymienić arkusz kalkulacyjny (np. Microsoft Excel). Od czasu do czasu taki program może się przydać, kiedy trzeba przeprowadzić jakieś obliczenia (np. podjąć jakąś trudną decyzję z pomocą tabelki ważonej). Jednak sposób organizacji informacji w arkuszu kalkulacyjnym czyni go raczej nieprzydatnym do jakichkolwiek innych zastosowań w projektowaniu.

Raczej nieprzydatne są też moim zdaniem edytory tekstu sformatowanego (jak Microsoft Word). Wygodniej i lepiej jest pisać w zwykłym Notatniku, a formatowania i wypunktowania robić za pomocą odpowiednich znaczków. Jednak to jest tylko moja opinia, w dodatku dość kontrowersyjna i łatwo można się z nią nie zgodzić. Oto przykładowy fragment takiego pliku tekstowego z projektem:

Projekt gry

________________ Założenia główne - ma być ładna - grywalna - dynamiczna - dużo krwi

Nie są też moim zdaniem potrzebne edytory graficzne czy nawet specjalnie dedykowane do sporządzania schematów i diagramów edytory grafiki wektorowej. Do takich celów wystarczy kartka papieru, a narysowanie takiego schematu na kartce będzie dużo prostsze, niż za pomocą specjalnego programu.

Formy tekstowe

To obojętne, czy chodzi o tekst w jakimś edytorze tekstu na komputerze, czy o notatki na kartce papieru. Chciałbym przedstawić tu najważniejsze ze stosowanych przeze mnie podczas projektowania form, które mają charakter tekstowy (nie graficzny).

Listy

Praktycznie zawsze są to listy - czyli kolejne punkty, a każdy z punktów zawiera trochę tekstu (zwykle nie więcej, niż jedną linijkę). Listy to dużo lepszy, bo bardziej systematyczny i ustrukturalizowany sposób reprezentacji informacji, niż zwykły, ciągły tekst.

Listy nie muszą być płaskie - mogą tworzyć hierarchię. Każdy punkt może zawierać swoje podpunkty, a te kolejne podpunkty itd. - od zagadnień najbardziej ogólnych do najbardziej szczegółowych.

Punkty warto grupować - dzielić na kategorie według jakiś kryteriów. Można to robić pisząc je razem, obok siebie i oddzielając od innych grup linijką odstępu, a można stosować różne rodzaje wypunktowania (do punktowania nadają się znaczki takie jak: - * > ] = o). Listy warto też sortować. Punkty na liście można porządkować według różnych kryteriów, zależnie od tego, jakie informacje się w niej znajdują.

Czego mogą dotyczyć listy? W zasadzie znajdują zastosowanie wszędzie. O różnych etapach projektowania i notatkach, które powstają w ich wyniku przeczytasz w następnym podrozdziale. Oto przykład projektu gry w postaci listy:

- intro
  Jakiś fajny, cząsteczkowy efekt graficzny
- menu
  > Wróć do gry (tylko jeśli gra trwa)
  > Nowa gra
  > Otwórz grę
  > Zapisz grę (tylko jeśli gra trwa)
  > Credits
  > Opcje
  > Wyjście
    Tutaj pytanie: Czy na pewno chcesz wyjść?
- ekran zapisywania i otwierania gry
  > lista zapisanych gier
  > pole do wpisania nazwy
  > przycisk OK
  > przycisk Anuluj
- Opcje
  > wybór rozdzielczości
  > wybór częstotliwości odświeżania

Listy TODO

Najważniejszym rodzajem listy, wymagającym osobnego omówienia jest lista <em>TODO</em>, czyli lista rzeczy do zrobienia. Takie listy pojawiają się i są intensywnie wykorzystywane przez cały czas powstawania i realizacji projektu, dlatego warto dobrze nauczyć się nimi zarządzać.

Na liście TODO zapisuje się rzeczy, które trzeba będzie jeszcze zrobić w ramach projektu - te bliższe i te dalsze, te większe i te mniejsze, a nawet najmniejsze drobiazgi. Chodzi o to, żeby o niczym nie zapomnieć. Kiedy tylko przypomni ci się coś, co trzeba albo można do projektu dodać i nie masz możliwości zająć się tym natychmiast (choćby przemyśleć to), dopisz nowy punkt do listy TODO.

Co można powiedzieć o listach TODO? Przede wszystkim lista taka ulega ciągłym zmianom. Stale pojawiają się nowe punkty, nowe rzeczy do zrobienia, a stare są realizowane i trzeba je jakoś wykreślić. Zależnie od tego, czy daną listę TODO prowadzisz na kartce czy na komputerze oraz czy chcesz, żeby zrealizowane już pozycje pozostały na niej, możesz to robić na różne sposoby:

W pliku tekstowym
Zmienić punktatorek (na v jeśli dana pozycja została zrealizowana lub ma x, jeśli zrezygnowałeś z jej realizacji) albo po prostu wykasować linijkę z danym punktem listy.
Na kartce
Postawić ptaszek lub krzyżyk obok pozycji listy, wykreślić jej punktatorek albo w ogóle przekreślić całą treść punktu.

Dynamika list TODO sprawia, że często musi być ich wiele, w różnych miejscach, dotyczących różnych zagadnień i trzeba poświęcać niemało uwagi na ich organizację. Kiedy już cała kartka zapełni się skreślonymi punktami, wyciągnij nową kartkę, przepisz na nią te punkty, które pozostały jeszcze nie zrealizowane na tej starej, wyrzuć starą i w ten sposób stwórz nową, kolejną listę TODO.

Listy TODO szybko się rozrastają i warto grupować ich punkty według zagadnienia, którego dotyczą. Na przykład możesz oddzielić rzeczy "do zrobienia" związane z pisaniem samego programu, jego dokumentacji, instalatora itd. Poszczególne pozycje listy warto też sortować, a kryteriami tego sortowania mogą być np.:

  • Od rzeczy najprostszych do zrobienia do najtrudniejszych.
  • Od najbardziej niezbędnych do tych dodatkowych, nad których sensem jeszcze trzeba będzie się zastanowić.
  • Od najpilniejszych do tych, którymi można się będzie zająć później.
  • Od najważniejszych do najmniej istotnych.

Do list TODO lepiej nadaje się plik tekstowy na komputerze, niż kartka papieru, bo łatwiej można tam reorganizować całą listę i usuwać poszczególne punkty. Jednak w praktyce różnych list TODO bywa podczas realizacji projektu wiele.

Warto mieć taką listę również na kartce, ponieważ podczas projektowania (albo kiedykolwiek, jeśli tylko twój umysł razem z podświadomością jest naprawdę zaangażowany w projekt) - choćby w środku nocy - może ci przyjść do głowy jakiś pomysł albo jakaś rzecz do zrobienia, o której nie powinieneś zapomnieć i warto mieć przygotowane miejsce, w którym można będzie ją zapisać.

Można też zorganizować sobie taką ogólną listę TODO (niezwiązaną z żadnym konkretnym projektem), na której będziesz zapisywał wszystko, co ci się przypomni - nowe pomysły, drobiazgi i wszystko to, co musi poczekać z przemyśleniem, realizacją albo przynajmniej przepisaniem w docelowe miejsce do chwili włączenia komputera. Tak wygląda w pomniejszeniu moja aktualna lista TODO, która leży na moim biurku:


Formy graficzne

Kiedy tekst nie wystarcza, trzeba użyć jednej z graficznych form projektowania. Do wszystkich ich wygodniej jest moim zdaniem używać kartki papieru i długopisu, niż programów graficznych na komputerze.

Schematy i diagramy

Czasami zdarza się, że istota konkretnych informacji, które chcesz w sposób systematyczny zapisać, nie pozwala na ich dobre zorganizowanie za pomocą listy z punktami i podpunktami. Wtedy narysuj poszczególne hasła rozmieszczając je przestrzennie na powierzchni kartki, biorąc je w owalne albo prostokątne ramki i połącz strzałkami.

Czasami też lepszym pomysłem na przedstawienie hierarchicznie zorganizowanych informacji jest narysowanie drzewa, w którym każda informacja może mieć swoje podinformacje - może się rozgałęziać.

Pewne rodzaje schematów i diagramów - diagramy klas i diagramy przejść stanów - poznasz w następnym podrozdziale. Tam też znajdują się odpowiednie przykłady. Oto przykład ogólnego schematu organizacji gry - moduły i relacje między nimi:



Tabele

Kiedy zapisywane informacje nie tylko składają się z kolejnych punktów (wierszy), ale każdy z nich ma takie same pola (kolumny), wtedy można pomyśleć o użyciu tabeli.

Do zrobienia tabeli nadaje się kartka w kratkę, ale czasami pomocny może być też arkusz kalkulacyjny (szczególnie jeśli przy okazji trzeba coś policzyć). Przykłady mogłeś zobaczyć w podrozdziale poświęconym podejmowaniu decyzji.

Okno główne

Kiedy masz już w miarę klarowną koncepcję swojego pomysłu i chcesz rozpocząć jego dokładne projektowanie (między fazą określania wymagań, a fazą analizy), na pierwszej stronie swojego stosu kartek z projektami narysuj schematycznie okno główne swojego programu albo ekran główny swojej gry (w czasie normalnego działania). Zaznacz na nim ważniejsze przyciski i inne kontrolki, narysuj jakieś przykładowe informacje wewnątrz okna czy jakąś przykładową sytuację z gry.

Taki rysunek to dobry punkt wyjścia do rozpoczęcia projektowania poszczególnych części programu czy nawet do samego dzielenia go na części i moduły. Pomaga wyobrazić sobie, jak program będzie wyglądał po ukończeniu i przywodzi na myśl marzenia o chwili, kiedy będzie już gotowy.

Kiedy zagłębiasz się w szczegóły projektowania, a potem pisania jakiś elementów swojego programu, rzuć czasami okiem na ten <em>główny rysunek</em>. To pomaga wrócić na chwilę myślami do całościowego spojrzenia na projekt, o którym można czasami łatwo zapomnieć, a to może mieć złe konsekwencje (np. kiedy poświęcasz za dużo uwagi czemuś, co nie jest zbyt istotne albo nawet robisz coś, co potem okazuje się zupełnie niepotrzebne i musisz to skasować).

Mapy myśli

Mapa myśli (ang. "mind map") to sposób organizacji notatek na kartce, który zdaniem wielu najlepiej odzwierciedla pracę ludzkiego mózgu. Mnie on zupełnie nie odpowiada - wolę listy z punktami i podpunktami. Widocznie mój umysł właśnie takich struktur danych używa do wewnętrznej reprezentacji informacji. Tym nie mniej niektórzy twierdzą, że mapy myśli nadają się do wszystkiego - od lepszego rozumienia przeczytanych książek, poprzez naukę do sprawdzianów, aż po zapisywanie efektów burzy mózgów i dlatego czuje się w obowiązku napisać o nich kilka słów.

Mapa myśli operuje słowami-kluczami. Na środku jest jakieś główne hasło lub symbol, a wokół należy pisać poszczególne zagadnienia w postaci odgałęzień. Odgałęzienia mogą się łączyć, w każdej chwili można znaleźć miejsce na dodawanie nowych itd. - panuje tu pełna swoboda. Zaleca się przy tym stosowanie różnych formatowań - różnych rodzajów linii, tekstu, różnych kolorów, stawianie symboli (jak wykrzykniki "!" czy znaki zapytania "?") przy niektórych hasłach itp.

Oto pomniejszona mapa myśli, którą sporządziłem kiedyś w trakcie czytania książki na temat biblioteki standardowej C++. Do robienia takich map należy używać kartek nie mniejszych, niż w formacie A4.



Etapy projektowania

Wracamy jeszcze raz do inżynierii oprogramowania, by omówić niektóre fazy powstawania programu. Sama świadomość, że program przed napisaniem trzeba najpierw zaprojektować i umiejętność tego projektowania nie wystarcza. Jeśli będziesz chciał zaprojektować większy program wystarczająco dobrze, może się okazać, że samo to projektowanie jest zadaniem bardzo trudnym.

Dlatego warto robić je sobie na kolejne etapy. Chociaż nie musisz kończyć każdego z nich spisaniem formalnego dokumentu, powinieneś być świadomym tego podziału, by za każdym razem wyobrazić sobie te etapy i przejść przez nie - poświęcając każdemu z nich przynajmniej chwilę namysłu czy jedno zdanie na kartce.

Faza strategiczna

To jest faza, w której podejmujesz decyzję, co pisać. To najważniejsza decyzja w całym projekcie. Możliwe, że wybór odpowiedniego pomysłu to właśnie ten moment, który ma decydujący wpływ na szanse ukończenia projektu. Tylko jak go dokonać? Jeżeli masz naturę hakera, z pewnością codziennie przychodzi ci do głowy wiele pomysłów. Nigdy nie będziesz w stanie napisać ich wszystkich i dlatego musisz wybrać te najlepsze.

Po pierwsze - nigdy nie zaczynaj pisania pochopnie. Każdym nowym pomysłem jesteśmy tak podekscytowani, że w pierwszej chwili wydaje się być wręcz genialny. Dopiero po pewnym czasie okazuje się, czy jest naprawdę bardzo dobry, przeciętny, czy może całkiem głupi. Dlatego moja rada jest taka: z rozpoczęciem pisania zawsze poczekaj do następnego dnia. Trzeba najpierw <em>przespać się z problemem</em>. To daje dostatecznie dużo czasu, by emocje opadły, a pomysł został przynajmniej wstępnie, racjonalnie przemyślany. Kiedy wieczorem i rano składasz pościel, myjesz się, jesz itp. to być może jest jedna z nielicznych chwil w ciągu dnia, kiedy twój umysł nie jest niczym zajęty i może spokojnie pomyśleć nad tą sprawą, choćby podświadomie.

Być może masz takie pomysły, które chodzą ci po głowie od dawna, często o nich myślisz i co jakiś czas wracasz do nich. Ja takie mam i wiem, że najlepiej jest właśnie je wybierać do realizacji. Na przykład jeśli wczoraj przyszedł ci do głowy pomysł, żeby napisać tetris, przedwczoraj arkanoid, tydzień temu RPG, ale już od dawna myślisz o napisaniu platformówki, napisz platformówkę. Kiedy wybierasz taki pomysł, jest większa szansa, że pracowanie nad nim dłużej ci się nie znudzi.

Moja trzecia rada jest taka, by wybierać te małe pomysły. Każdy mały program jest duży - będziesz do pisał kilka tygodni, a każdy duży program jest ogromny - będziesz go pisał miesiącami. Dlatego pomyśl wcześniej, czy jesteś gotów przez tak długi czas pracować tylko nad tym, czy nie znudzi ci się, czy nie wpadniesz na lepszy, ciekawszy pomysł? To bardzo prawdopodobne i może stanowić przeszkodę nie do pokonania w realizacji większych projektów nawet, jeśli sama wielkość projektu pod względem technicznym nie stanowiłaby już dla ciebie problemu (dzięki umiejętności projektowania i organizacji kodu).

Powinieneś z góry odrzucać pomysły nierealne. To jasne, że fantastyczne byłoby napisać myślącego choatterbota - ale pomyśl, jak to zrobisz, jeśli nie udało się to jeszcze nawet największym naukowcom? Oczywiście, że super byłoby napisać grę przygodową czy RPG z porywającą fabułą - ale pomyśl, kto ułoży wszystkie plansze i napisze wszystkie dialogi? Będzie ci się chciało? Wiem coś o tym, bo już raz napisałem całą grę RPG, skończyłem kod, a projekt upadł kiedy przekonałem się, ile pracy jest z układaniem wirtualnego świata.



Faza określania wymagań

Na tym etapie powinieneś określić, jakie wymagania musi spełniać projektowany program. Czy twoja gra będzie miała trójwymiarową grafikę? Czy będzie miała podkład muzyczny? Czy wymaga skomplikowanego menu? Czy powinna umożliwiać zmianę rozdzielczości? Wyobraź sobie ją tak, jakby była już skończona. Najważniejsze, by w tej fazie nie wdawać się w szczegóły implementacyjne.

Takie myślenie dla programisty może być trudne. Jak nie myśleć o programowaniu? Aby to zrobić, spróbuj poczuć się jak użytkownik czy gracz. Potem wyobraź sobie swój program (już skończony) - jego interfejs użytkownika, główne okno programu czy główny ekran gry. Jakie elementy interfejsu użytkownika powinien zawierać? Jaki powinien być, ogólnie i w szczegółach, żeby spełniał oczekiwania? Jakie są w ogóle twoje oczekiwania wobec niego? To są pytania, na które powinieneś szukać odpowiedzi w tej fazie.

Jedynym wyjątkiem od tej zasady, by nie myśleć o samym kodowaniu, jest kontrolowanie cały czas, czy wymyślane oczekiwania nie są dla ciebie nierealne do zrealizowania. Podczas projektowania, szczególnie na tym etapie, bardzo łatwo jest się zagalopować i nawymyślać ogromną ilość fantastycznych, ale bardzo trudnych albo wręcz niemożliwych do napisania elementów. Stawiaj przede wszystkim na takie rozwiązania, które są jednocześnie proste do napisania, a przynoszą bardzo dobry efekt. Nie zapomnij też rozpatrzyć zawsze, które rzeczy są najważniejsze, a z których można zrezygnować albo je ograniczyć.



Faza analizy

W poprzedniej fazie trzeba się zastanowić, *co* program ma robić, a w tej, *jak* ma to robić - czyli jak zrealizować postawione mu wymagania. Można już zacząć myśleć o szczegółach implementacyjnych i planować, jak napiszesz to wszystko. Często okazuje się, że część planowanych rzeczy trzeba będzie odrzucić.

Pomyśl: Jakich bibliotek użyjesz? Jak ogólnie zorganizujesz swój kod i pracę nad projektem? Jak podzielisz ten proces na etapy, a sam program na moduły? Czy będziesz musiał czegoś nowego się nauczyć - jakiejś biblioteki, jakiejś techniki? Czy jesteś w stanie zrealizować wszystkie założenia? Jak to zrobisz? Tutaj wszędzie pojawiają się decyzje do podjęcia.



Faza projektowania

Według inżynierii oprogramowania, w tej fazie należy dokładnie zaprojektować kod. W praktyce nie zawsze bywa potrzebna - zwykle wystarczy po prostu usiąść i zacząć pisać polegając tylko na swoim doświadczeniu i intuicji. Czasami jednak, kiedy napisanie jakiegoś modułu wydaje się bardzo trudne, warto wprowadzić ten etap pośredni i pomóc sobie w pisaniu formami, które przedstawię poniżej. Mnie już nie raz pomogły one napisać coś, co wydawało się trudne do ogarnięcia umysłem.

Pseudokod

Jeżeli sam kod w języku programowania, którego używasz będzie skomplikowany, czasem warto pomóc sobie projektując go "na sucho" - na kartce albo lepiej w Notatniku, bez kompilacji, w pseudokodzie. Pseudokod to jakiś język programowania, który nie istnieje, a którego używasz, żeby jak najprościej opisać jakieś instrukcje. Może być podobny do C++, do Pascala, do czego tylko chcesz. Nie musisz się martwić szczegółami, obsługą błędów, możesz używać nieprecyzyjnych czy niezdefiniowanych jeszcze funkcji - byle opisać samą ideę.

W niektórych sytuacjach ta metoda jest naprawdę niezastąpiona. Za jej pomocą możesz zaprojektować sam interfejs jakiegoś modułu czy klasy - jakie funkcje będzie udostępniał na zewnątrz. Możesz też opisać w pseudokodzie jakiś skomplikowany algorytm.

To duży komfort psychiczny pisać kod bez kompilacji, nie obawiając się, że gdzieś wkradnie się mniej czy bardziej poważny błąd. Możesz go dopracowywać "na luzie". Kiedy już będzie dostatecznie precyzyjny, przepiszesz go na swój język programowania i jeśli o niczym nie zapomniałeś, doświadczysz czegoś doprawdy wspaniałego - ten skomplikowany kod skompiluje się i zadziała poprawnie od razu, za pierwszym razem!

Pierwszym przykładem niech będzie zdefiniowanie interfejsu, czyli funkcji, które pewien moduł (weźmy dla przykładu moduł dźwiękowy projektowanej gry) będzie udostępniał na zewnątrz. Nie ważne, czy to będzie klasa, czy funkcje globalne.

System dźwiękowy - interfejs

- void Create() - inicjalizacja systemu - void Destroy() - finalizacja systemu - void Load(int ID, string FileName) - wczytanie dźwięku WAVE - int Play(int ID, bool Loop) - odgrywa dźwięk, zwraca kanał - void Stop(int Channel) - zatrzymuje dźwięk z podanego kanału - void SetVolume(int Channel, byte Volume) - ustawia głośność dźwięku

Jako przykład projektowania algorytmu przytoczę tym razem autentyczny fragment mojego projektu pewnej gry - algorytm wykrywania kolizji. Jak widać, używany pseudojęzyk może wyglądać dowolnie - tak, jak w danej chwili jest wygodnie. Chodzi o opisanie samego algorytmu.

foreach(obj na liście)
  if obj się porusza
    Circle = obj.Ruch
  CircleBackup = Circle
    Stop = false
    foreach (kolidująca ściana, Circle)
      if obj.Collision(ściana)
        Stop = true
    foreach (kolidujący obiekt, Circle)
      if obj.Collision(obiekt)
        Stop = true
      if obiekt.Collision(obj)
        Stop = true
    if Stop
      Circle.x = obj.x
      if JestKolizja(Circle)
        Circle = CircleBackup
        Circle.y = obj.y
        if JestKolizja(Circle)
          obj.vx = 0
          obj.vy = 0
        else
          obj.pos = Circle
          obj.vx = 0
      else
        obj.pos = Circle
        obj.vy = 0
    else
      obj.pos = Circle

Diagramy klas

Niezależnie czy piszesz obiektowo czy strukturalnie, w budowie swojego programu z pewnością możesz wyróżnić jakieś "obiekty". Jedne przechowują w sobie drugie obiekty albo całe ich kolekcje, odpowiadają za ich tworzenie, usuwanie, odwołują się do nich itd. - są ich właścicielami. Tak powstaje pewna hierarchia. Inną hierarchią są dziedziczone klasy. Czasami po prostu jakiś obiekt potrzebuje dostępu do innego, przechowuje wskaźnik do niego itp. Tak powstają różne relacje. To nie muszą być tylko klasy, to mogą być np. moduły.

Takie wzajemne zależności między elementami muszą być dobrze zaprojektowane. Nie zawsze da się przewidzieć już podczas projektowania, jak to wszystko będzie najlepiej napisać. Dlatego prezentowane tu metody nadają się bardziej do stosowania już podczas implementacji - by pomóc sobie w trudnych momentach.

Kiedy samo pisanie nie wystarcza i sytuacja jest skomplikowana, możesz narysować sobie diagram klas. Są do tego specjalne formalne notacje (jak UML), ale nie musisz się ich uczyć. Ważne tylko, żeby jakoś rozrysować, a przez to pomóc wyobrazić sobie sytuację w kodzie.

Jako przykład możemy rozrysować organizację elementów projektowanej platformówki. Po lewej stronie jest hierarchia klas. Obiekt jest abstrakcyjną klasą bazową i z niej dziedziczą inne klasy wprowadzając pola i metody charakterystyczne dla swojego rodzaju. Po prawej stronie są relacje między działającymi już obiektami - GRA przechowuje kolekcję obiektów oraz mapę, która składa się z kolei ze zbioru pól.



Diagramy przejść stanów

Nie przydają się zbyt często, ale to jeszcze jeden sposób na rozpisanie sobie działania programu, jeśli jego projektowanie czy kodowanie staje się zbyt skomplikowane - po prostu kiedy już <em>nie mieści się w głowie</em>. Taki diagram składa się ze zbioru stanów i połączeń między nimi. W każdej chwili program (czy jakiś jego obiekt, którego diagram dotyczy) jest w jednym z tych stanów. Strzałki pokazują, jak można między tymi stanami przechodzić, a przy każdej można napisać, jaka akcja powoduje przejście do tego stanu i jakie dodatkowe czynności są przy tym przejściu podejmowane.

Poniższy przykład to diagram stanów, w jakich może być gra i przejść między nimi. Zakładamy przy tym, że wyjście do menu nie jest równoznacznie z usunięciem z pamięci samej gry, a więc można wyjść do menu w celu zapisania gry, a potem do niej wrócić.

Inny przykład to diagram stanów dla prostej sztucznej inteligencji potworka w grze. Pokazuje on, że stan nie musi być czymś globalnym dla programu - każdy potworek będzie wykonywał ten algorytm i będzie miał swój własny stan niezależnie od innych.



Podsumowanie

Można powiedzieć, że w pewnym sensie są dwa modele programowania. Pierwszy to taki, w którym przed rozpoczęciem kodowania planuje i projektuje się wszystko dokładnie. Jak dokładnie? - to zależy od wielkości programu. Projektować trzeba wystarczająco szczegółowo, żeby się potem nie pogubić w kodzie. Dużo zależy też od osobistego doświadczenia i umiejętności <em>improwizowania</em>.

Wbrew pozorom ten model oprócz zalet ma też swoje wady. Kiedy programowanie staje się tylko "formalnością", bardzo łatwo przyjąć taką strategię, że realizuje się poszczególne zaplanowane części <em>na "odwal się"</em> jak najszybciej zapominając o nich - bez dobrej optymalizacji, bez dobrego testowania - pozostawia się kod powolny i pełen błędów, w dodatku słabo skomentowany i trudny potem w konserwacji. Trzeba na to uważać.

Drugim podejściem jest pisanie na żywioł - bez żadnego dokładniejsze projektowania. To trochę niemądre, ale ma też swoje zalety. Wiadomo, że trudno wtedy napisać od razu dobrze i trzeba będzie stale coś zmieniać, a niejednokrotnie dużą część kodu napisać zupełnie od nowa. To wymaga więcej czasu i pracy, ale wcale nie jest takie złe - każda nowa wersja jest lepsza, a podczas takiego pisania zdobywa się dużo doświadczenia. Takie pisanie skłania też do dbałości o kod. Kiedy wiesz, że będziesz musiał do niego wracać, bardziej starasz się pisać go czytelnie, z komentarzami itp.

Choć to niezwykłe, taki sposób pomógł mi kiedyś, kiedy nawet solidne projektowanie zawiodło. Chciałem napisać coś, co trudno było ogarnąć i zaplanować. Nie mogłem wymyślić dobrej organizacji tego wszystkiego, więc zacząłem po prostu pisać - i napisałem! Wydaje mi się, że to jednak można racjonalnie wytłumaczyć. Jako programiści pasjonaci jesteśmy tak zżyci z kodem, że łatwiej nam operować na nim niż na abstrakcyjnych schematach.

Jak projektować?

Kiedy już potrafisz dobrze projektować okazuje się, że zaprojektowanie skomplikowanego programu wcale nie jest dużo prostsze od jego napisania. Dlatego na koniec chciałbym udzielić odpowiedzi na pytanie: Jak projektować, żeby to nie było za trudne?

Najtrudniej jest zacząć. Mówią nawet, że <em>kiedy zaczniesz, to zrobiłeś już połowę roboty</em>. Dlatego moja pierwsza rada jest taka: nie spędzaj czasu na "przerażaniu się" - jakie to wielkie i trudne zadanie - tylko wyciągnij kartkę, weź długopis, napisz nazwę swojego programu, dzisiejszą datę itp. - po prostu zacznij. Dopiero potem pomyślisz co dalej a przekonasz się, że dalej już jakoś pójdzie.

Po drugie: podziel swoje zadanie - <em>wzdłuż i wszerz</em>. To oczywiste, że każdy większy projekt jako całość wydaje się nie do ogarnięcia. Dlatego najpierw zaprojektuj go ogólnie, podziel na części (moduły), a dopiero potem przejdź do szczegółów skupiając się zawsze tylko na jednym z nich. Nigdy nie przegap okazji, by jakieś zagadnienie podzielić na części albo na podzagadnienia - wtedy taki problem staje się kilka razy prostszy. Posługuj się listami z punktami i podpunktami. Wypisuj i systematyzuj wszystko, co się da. Po prostu <em>dziel i rządź</em>!

Mówiąc już bardzo abstrakcyjnie możnaby nawet powiedzieć, że organizacja projektowania programu ma strukturę trójwymiarową:

Szerokość
To rozbicie zagadnienia na mniejsze, osobne elementy, które tworzą płaską listę.
Głębokość
To podział zagadnienia na swoje bardziej szczegółowe podzagadnienia, które tworzą hierarchię (drzewo).
Wysokość
To podział procesu projektowania na etapy według inżynierii oprogramowania (faza strategiczna, określania wymagań, analizy, projektowania), które następują kolejno po sobie w czasie.

Czasami mimo wszystko jakiś problem wydaje się nie do rozwiązania. Wtedy warto odczekać pewien czas, <em>przespać się z problemem</em>. To, że dziś nie możesz znaleźć dobrego rozwiązania wcale nie oznacza, że w ogóle nie możesz tego zrobić. Wiadomo, że każdy z nas bywa w różnym nastroju i czasami lepiej się myśli, a czasami gorzej. Po prostu zajmij się czymś innym, nie martw się, poczekaj do następnego dnia, odpocznij, uwierz w siebie, pograj w jakąś grę, a rozwiązanie na pewno się znajdzie.

Programowanie

Po zaprojektowaniu przychodzi czas na programowanie (inaczej kodowanie, pisanie, implementację). Jednak te dwie rzeczy nie są zupełnie odrębne. Czasami można zacząć pisanie bez dokładnego zaprojektowania całości, a czasami trzeba już podczas pisania wrócić do projektu albo jakiś szczegół zaplanować.

Programowanie a projektowanie

Przez ostatnie pół roku miałem zajęcia z probabilistyki i statystyki z pewnym profesorem, którego z pewnością długo nie zapomnę. Jedną z rzeczy, które powiedział i które na dobre zapadły mi w pamięci jest takie oto stwierdzenie:

Otóż powiedział on, że to, co my - studenci - nazywamy rozwiązywaniem zadania, to jest tylko formalność - najmniej ważna część rozwiązania. To nie podstawienie do wzoru i policzenie stanowi sedno sprawy. Tak naprawdę w zadaniu chodzi o to, żeby przeczytać i zrozumieć jego treść, rozpoznać problem, wybrać odpowiednie metody, wzory i zaplanować, w jaki sposób trzeba będzie go rozwiązać.

Myślę, że z programowaniem jest trochę podobnie. Im jesteś bardziej zaawansowany i im większe rzeczy piszesz, tym lepiej i dokładniej musisz projektować, a im lepiej i dokładniej projektujesz, tym bardziej samo kodowanie staje się tylko formalnością.

Ale na szczęście w naszym przypadku nie musi to wyglądać aż tak radykalnie. Programowanie nadal pozostaje czynnością najważniejszą, sednem sprawy i istotną naszej pracy. Jeżeli boisz się, że przez porządne projektowanie programowanie straci swój urok, przypomnij sobie chwile, kiedy wpadasz na jakiś nowy pomysł i obmyślasz go, marzysz o nim. Projektowanie i wszystkie inne etapy tworzenia programu mogą być i są równie przyjemne.

Co trzeba umieć?

Żeby w ogóle mógł myśleć o programowaniu jako o realizacji pewnych projektów, musisz umieć kilka rzeczy w stopniu przynajmniej podstawowym. W przeciwnym razie ten artykuł nie jest dla ciebie, a jedyne, co mogę ci polecić, to dalszą naukę samego programowania i nabywanie doświadczenia. Żeby móc dobrze programować, musisz umieć:

Język programowania
Z językiem programowania jest trochę tak jak z językami obcymi. Dopóki nie znasz go dobrze, jedyne, co możesz robić, to dalej się go uczyć na jakiś zmyślonych przykładach. Dopiero potem, kiedy potrafisz wyrażać w nim swoje myśli możesz powiedzieć, że potrafisz się nim dobrze posługiwać. Aby dobrze programować, nie musisz znać perfekcyjnie wszystkich sztuczek i drobiazgów swojego ulubionego języka programowania, ale musisz znać go na tyle dobrze, żeby swobodnie wyrażać w nim swoje myśli - czyli implementować swoje pomysły.
Biblioteki
Sam język programowania nie wystarczy. Musisz poznać i nauczyć się używać jakiś bibliotek - np. do tworzenia graficznego interfejsu użytkownika (jak Windows API), do grafiki (jak Windows GDI, DirectX czy OpenGL), do dźwięku (jak DirectAudio czy FMOD), do sieci (jak WinSock czy DirectPlay) itp. - takie, które będą ci potrzebne. Czasami będziesz się musiał nauczyć jakiejś biblioteki specjalnie, by móc zrealizować konkretny projekt.
Matematykę
Tego trudno nauczyć się samemu. Twoja wiedza matematyczna zależy do tego, w jakim jesteś wieku i do jakiej szkoły chodzisz. Dlatego nie zaniedbuj nauki matematyki w szkole, bo to jest na prawdę bardzo ważne w programowaniu.
Myśleć
Programowanie to proces twórczy, wymagający inteligencji, logicznego i abstrakcyjnego myślenia, a także niemałej wiedzy, doświadczenia, kreatywności i intuicji.
Wyszukiwać
Internet (głównie strony WWW) to <em>Wszechnica Wiedzy Wszelakiej</em>, ale z niej też trzeba umieć odpowiednio korzystać. Do rozwiązania niektórych problemów potrzebujesz dodatkowych informacji i musisz umieć je odnaleźć, np. za pomocą wyszukiwarki Google (zamiast od razu biegnąć z pytaniem do bardziej doświadczonego kolegi).
Korzystać z dokumentacji
Nawet najlepszy programista nie zna na pamięć wszystkich funkcji używanych bibliotek, ich nazw ani parametrów, jakie przyjmują. Z kolei bazowanie na kursach (tutorialach) to nienajlepszy pomysł - one mają pomagać w zrozumieniu nowego tematu i opisują tylko niektóre funkcje. Musisz potrafić korzystać z oficjalnych dokumentacji do swojego języka i używanych bibliotek, nie bać się ich. To właśnie tam jest systematyczny, dokładny opis wszystkich funkcji.

Jak programować?

Nawet kiedy doskonale znasz język programowania i wszystkie potrzebne biblioteki, możesz mieć problem z napisaniem wymarzonego programu. Przyczyną jest zwykle jego wielkość. Zaprojektowanie bardzo pomaga poradzić sobie z tym problemem, ale to nie koniec. Dalsze krotki trzeba podjąć także podczas kodowania. Dlatego również w tej dziedzinie chciałbym udzielić ci kilku wskazówek.

Zawsze najtrudniej jest zacząć. Dlatego zamiast rozmyślać i zamartwiać się wielkością i złożonością tego, co czeka cię do napisania lepiej natychmiast włącz IDE, stwórz nowy moduł, napisz komentarz z nagłówkiem (data, wersja, twoje nazwisko) i zacznij coś pisać - choćby nagłówki funkcji, szkielet klasy itp. - po prostu zacznij. Dopiero potem pomyślisz co dalej, napiszesz treść wszystkich funkcji itp. a przekonasz się, że dalej już jakoś pójdzie. Dokładnie tak jak podczas projektowania.

Wiele lat temu jakiś programista mądrze powiedział, że <em>wszystko wydaje się trudne, dopóki nie stanie się proste</em>. Tą magiczną receptą na uproszczenie wszystkiego co trudne jest podział każdego zadania na mniejsze podzadania i skupienie się na nim najpierw tylko ogólnie, a potem na szczegółach każdego z tych podzadań osobno.

W przypadku programowania lepiej byłoby chyba powiedzieć, że należy pisać kolejne warstwy - otoczki (ang. "wrappers"). Właściwie to jest istota całego programowania i jej zrozumienie wydaje się być kluczem do stania się bardziej zaawansowanym programistą.

Na przykład początkujący mógłby bać się pisania gry z atrakcyjną grafiką myśląc, że to trudne, bo w czasie jej pisania ciągle będzie musiał się tą grafiką zajmować. Zaawansowany wie, że to totalna bzdura - trzeba się nauczyć jakiejś biblioteki graficznej i raz napisać własny moduł graficzny - otoczkę na nią, która udostępni funkcje potrzebne w tej grze (jak rysowanie bitmap, tekstu i cząsteczek). W pozostałej części gry będzie już potem mógł tylko używać funkcji tego swojego modułu.

Nie będę w tym artykule wdawał się w rozważania na temat organizacji kodu w poszczególnych językach programowania. Chciałbym natomiast zachęcić cię do poznania, zrozumienia i stosowania zasad programowania zorientowanego obiektowo. To nie jest tylko zbiór reguł składniowych języka - to sposób myślenia, który może naprawdę dużo pomóc, chociaż osobiście uważam, że czasami bywa przeceniany i sam mieszam kod obiektowy ze strukturalnym.

Na temat programowania obiektowego są całe grube książki, dlatego tutaj napiszę o tym jedynie kilka słów. Po przejściu od asemblera i programowania liniowego (z wszędobylskimi instrukcjami skoku) na wyższy poziom piewcy programowania strukturalnego (jak Niklaus Wirth) głosili konieczność oddzielenia danych (zmienne) kod kodu (funkcje).

Programowanie obiektowe natomiast łączy dane z kodem, który na nich operuje i zamyka je razem w logiczne jednostki zwane klasami. W ten sposób obiekty należące do klasy Samochód (klasa jest typem, można definiować zmienne tego typu i tworzyć jednocześnie wiele takich obiektów) przechowują swoje dane (jak Pozycja, Prędkość, Kolor) i mają funkcje do operowania na nich (jak Ruszaj(), Zatrzymaj()).

Programowanie obiektowe najlepiej nadaje się tam, gdzie zachodzi potrzeba przechowywania kolekcji wielu niebanalnych obiektów (tzn. każdy z nich jest czymś więcej niż pojedynczą liczbą czy znakiem). Oto przykład w C++ dotyczący omawianej, fikcyjnej platformówki:

// Abstrakcyjna klasa bazowa dla obiektów w grze
class Obiekt {
  // Pozycja na mapie
  int x, y;
public:
  // Klasa jest polimorficzna - wirtualny destruktor
  virtual ~Obiekt();
  // Przesunięcie obiektu (sprawdza kolizje)
  virtual void Move(int NewX, int NewY);
  // Odczytanie pozycji
  int GetX() { return x; }
  int GetY() { return y; }
  // Rysuje obiekt na ekranie
  virtual void Draw() = 0;
};
 
// Jakiś leżący nieruchomo przedmiot, ozdoba itp.
class Rzecz : public Obiekt {
private:
  int Rodzaj;
public:
  // Konstruktor
  Rzecz(int a_Rodzaj);
  // Rysowanie
  virtual void Draw();
};
 
// Postać w grze - jakieś żyjątko, które może zginąć
class Postac : public Obiekt {
private:
  int Zycie; // 0..ZYCIE_MAX
public:
  // Konstruktor
  Postac() : Zycie(ZYCIE_MAX) { }
  // Zwraca życie
  int GetZycie() { return Zycie; }
  // Obrazenia dla tej postaci
  // (zwraca true, jeśli nie żyje)
  bool Damage(int x);
};
 
// Bohater gry - to też rodzaj postaci
class Bohater : public Postac
{
private:
  // Amunicja do broni różnego rodzaju
  int Amunicja[BRONIE_COUNT];
public:
  // Konstruktor
  Bohater();
  // Rysowanie bohatera
  virtual void Draw();
};
 
// Potworek, czyli wróg gracza
class Potworek : public Postac
{
private:
  int Rodzaj;
public:
  // Konstruktor
  Potworek(int a_Rodzaj) : Rodzaj(a_Rodzaj) { }
  // Rysowanie potworka
  virtual void Draw();
};
 
// Klasa menadżera obiektów przechowuje kolekcję obiektów,
// zarządza nimi i zwalnia je z pamięci
class ObiektManager {
private:
  std::list<Obiekt*> Lista;
public:
  // Zwalnia wszystkie obiekty z pamięci
  ~ObiektManager();
  // Dodaje do listy utworzony wcześniej obiekt
  void Add(Obiekt *obj);
  // Rysuje wszystkie obiekty
  void Draw();
  // itd...
};

Na koniec chciałbym dodać, że zależności między klasami i modułami powinny tworzyć hierarchię, czyli drzewo. W praktyce trudno tego tak ściśle przestrzegać i powstaje lepszy albo gorszy graf. Tym nie mniej należy dla każdego modułu jasno określić, jaki interfejs (zbiór klas, funkcji itp.) udostępnia na zewnątrz i z jakich innych modułów korzysta.

Przykładowo: jeśli masz w swoim programie moduł z różnymi ogólnymi funkcjami, jak konwersje różnych typów na łańcuchy, szybki generator liczb pseudolosowych itp. i zakładasz, że wszystkie inne moduły twojego programu mogą się do niego odwoływać, to on sam nie powinien używać żadnego z nich, a jedynie nagłówków zewnętrznych (jak nagłówki używanych bibliotek).

Innym przykładem może być moduł dźwiękowy. Skoro ma stanowić otoczkę na używaną bibliotekę dźwiękową (jak FMOD czy DirectAudio), tylko on powinien tej biblioteki używać. Pozostałe moduły gry powinny implementować dźwięk wyłącznie z użyciem funkcji tego modułu, a nie bezpośrednio. Moduł główny gry, podobnie jak inne moduły może odwoływać się do tego modułu dźwiękowego, ale on nie powinien odwoływać się do nich.

Podobnie jest z klasami i obiektami. Każdy obiekt powinien mieć jasno zdefiniowanego właściciela, który go przechowuje (albo wskaźnik do niego), używa go i jest odpowiedzialny za jego tworzenie i usuwanie z pamięci.

Każdy obiekt, wskaźnik i w ogóle każda zmienna powinna mieć jasno określony i zdefiniowany (przynajmniej w twojej głowie, a najlepiej w komentarzu przy jej deklaracji) czas życia - kiedy istnieje, kto i kiedy ją tworzy a kiedy usuwa. Dotyczy to nie tylko obiektów własnych klas czy zasobów alokowanych dynamicznie. Nawet najzwyklejsza globalna zmienna liczbowa w pewnym czasie (w niektórych stanach) nie jest używana, ma wartość niezdefiniowaną, w pewnych sytuacjach i przez pewne obiekty czy funkcje jest zapisywana, a przez inne odczytywana - im jaśniej to wszystko zdefiniujesz, tym lepiej.

W filmie "Matrix" bardzo mądrze powiedziano, że <em>wszystko co ma początek, ma też i swój koniec</em>. Ta zasada powinna ci towarzyszyć ciągle podczas programowania. Kiedy otwierasz nawias, napisz od razu jego zamknięcie, a dopiero potem wypełniaj go zawartością. To znacznie uprości pisanie instrukcji i wyrażeń. Kiedy piszesz kod alokujący pamięć czy tworzący jakieś inne zasoby, od razu napisz komplementarny kod, który je zwalnia. Niezwalnianie zasobów nie spowoduje błędu i w ogóle nie będzie widoczne, ale powoduje wycieki i dlatego jest najbardziej zdradliwym z błędów.

Kod

Być może zdarzyło ci się, że pisałeś coś i nagle dostrzegłeś, że to jest bez sensu, zupełnie źle zorganizowane, nieprzemyślane i nie da się tego dalej ciągnąć - trzeba skasować i napisać od nowa, zupełnie inaczej. Takie sytuacje są częste i wynikają z niedostatecznie solidnego zaprojektowania. Dlatego pamiętaj: <em>pisz w porządku od początku</em>.

To nie znaczy, że musisz zaprojektować cały program dokładnie, zanim w ogóle rozpoczniesz kodowanie. Najważniejsze to zaprojektować ogólnie, czyli odpowiednio podzielić na moduły. Potem już możesz spokojnie zagłębiać się w szczegóły każdego z tych elementów osobno projektując je i pisząc.

To, co teraz napiszę, jest bardzo ważne dlatego proszę, abyś zwrócił na to szczególną uwagę. Otóż musisz cały czas dbać o swój kod. Mimo usilnych starań zawsze będą się zdarzały sytuacje, kiedy projekt okazuje się niedoskonały i w wyniku tego coś się w kodzie nie zgadza, coś trzeba zmienić. Czasami trzeba nawet zmienić bardzo dużo.

Jeżeli taka sytuacja spotka cię w środku czy nawet już pod koniec pisania jakiegoś modułu, musisz natychmiast to zrobić. Nawet, jeśli trzeba w tym celu dużo skasować, dużo napisać od nowa, żmudnie przerabiać różne elementy składniowe kodu czy porządnie przemyśleć i zaprojektować nową organizację tego wszystkiego. Nie możesz pozwolić sobie na żadne prowizorki ani odkładać na potem żadnych poważnych zmian w kodzie.

Takie sytuacje są normalne, nieuniknione i trzeba się do nich przyzwyczaić. Mają też swoje dobre strony - każda zmiana i każda nowa wersja kodu jest lepsza od poprzedniej, a podczas takich przeróbek zdobywasz wiele doświadczenia. Jeśli zostawisz wszystko tak jak jest i postawisz na rozwiązania tymczasowe, bardzo szybko pogubisz się we własnym kodzie i twój projekt upadnie.

Komentarze

Kiedy piszesz kod, przelewasz do pliku swoje myśli, ale ktoś inny albo ty sam za kilka dni, tygodni czy miesięcy nie będziesz już pamiętał, o czym wtedy myślałeś - zostanie tylko kod. Dlatego tak ważne jest pisanie komentarzy. Mike powiedział kiedyś, że <em>komentarze są przydatne głównie dlatego, że są zielone</em>. To prawda, mogą pełnić w kodzie także funkcję estetyczną, np. jako separator:

//==========================================================
// Klasy z obiektami gry

To oczywiste, że nie ma sensu komentować wszędzie i wszystkiego. Szczególnie nie warto opisywać tego, co i tak widać w kodzie, np.:

// ŹLE!!!
// Dla każdego wiersza...
for (y = 0; y < MAP_HEIGHT; y++) {
  // Dla każdej kolumny...
  for (x = 0; x < MAP_WIDTH; x++) {
    // Odrysowanie pola mapy
    DrawMapCell(x, y, Map[y*MAP_WIDTH+x]);
  }
}

Komentarz powinien opisywać to, czego z kodu bezpośrednio nie da się odczytać - rzeczy bardziej abstrakcyjne i ogólne, użyte sztuczki itp.

// Odrysowanie całej mapy
for (y = 0; y < MAP_HEIGHT; y++) {
  for (x = 0; x < MAP_WIDTH; x++) {
    DrawMapCell(x, y, Map[y*MAP_WIDTH+x]);
  }
}

Komentarze mają także wiele innych zastosowań. Jednym z nich jest opisywanie interfejsu. Przykładowo, dobrym pomysłem jest opisywanie deklaracji każdej funkcji w pliku nagłówkowym komentarzem z informacją co ona robi, jakie parametry pobiera, co zwraca i jak reagują na błędy.

// Wczytuje dźwięk z pliku WAVE do pamięci
// Dźwięk zostanie sam zwolniony podczas finalizacji systemu dźwiękowego
// - ID       - identyfikator dla wczytywanego dźwięku
// - FileName - nazwa pliku dźwięku wraz ze ścieżka
// Jeśli się uda, zwraca true
// Jeśli się nie uda, zwraca false i wpisuje komunikat do zmiennej g_ErrMsg
bool Load(int ID, const std::string &FileName);

Za pomocą komentarzy można zrobić swego rodzaju nagłówek na początku każdego pliku źródła:

//----------------------------------------------------------
// Nazwa  : sound
// Wersja : 1.0
// Opis   : Moduł dźwiękowy
// Autor  : Adam Sawicki "Regedit"
// Data   : 2004-09-21
// URL    : mailto:[email protected]
// URL    : http://www.programex.prv.pl/
//----------------------------------------------------------

Można też umieszczać w komentarzach dłuższe, ogólne opisy sposobu działania danego modułu, klasy, funkcji itp., by ułatwić ich zrozumienie i używanie. Można opisać (razem z przykładowym kodem), jak należy prawidłowo używać danego modułu. Można nawet rysować sobie ASCII Art.

/* Rysowanie obramowania przebiega w takich 4 etapach:
    _________________
   |   |____3____|   |
   |   |         |   |
   | 1 |         | 2 |
   |   |_________|   |
   |___|____4____|___|
*/

Dokumentacja

Inżynieria oprogramowania wymienia dokumentowanie jako osobną fazę produkcji programu, która odbywa się równolegle ze wszystkimi pozostałymi. Nie chodzi tu jednak o napisaniu pomocy do swojego programu - to można zrobić na końcu, kiedy program będzie już napisany. Chodzi o sporządzanie opisów z informacjami technicznymi.

Część takich informacji możesz zawrzeć w plikach z kodem, w komentarzach - np. listę udostępnianych przez moduł funkcji wraz z opisem ich działania, parametrów, zwracanej wartości i reakcji na błędy. Czasami jednak warto sporządzić osobny dokument tekstowy opisujący np. używany w programie format pliku czy protokół sieciowy. To powinna być możliwie formalna, systematyczna i w pełni kompletna dokumentacja danego zagadnienia.

Warto pisać dokumentacje, bo kiedyś możesz chcieć wrócić do swojego projektu, a dedukowanie formatu pliku na podstawie kodu funkcji do odczytywania i zapisywania to nic dobrego.

Format pliku z poziomem do platformówki
=======================================
 
Lokalizacja: Platformówka\Maps\*.map
Pliki binarne
 
Budowa
------
- nagłowek
- mapa
- liczba obiektów [DWORD]
- obiekt, obiekt, obiekt...
 
Nagłówek
--------
- łańcuch "Platformówka" (12 B)
 
Mapa
----
- Szerokość [WORD]
- Wysokość [WORD]
- Rodzaj pola, Rodzaj pola, Rodzaj pola, ... [BYTE]
 
Obiekt
------
- Typ [BYTE]
  0 = rzecz
  1 = potworek
- x [WORD]
- y [WORD]
Jeśli rzecz:
- Rodzaj rzeczy [BYTE]
Jeśli potworek:
- Życie [int]
- Rodzaj potworka [BYTE]

Kopie bezpieczeństwa

Jeśli nigdy nie straciłeś bezpowrotnie efektów swojej pracy, to bardzo dobrze, ale to nie znaczy, że nigdy nic złego ci się nie przydarzy. Różne wypadki się zdarzają - od wirusów i trojanów, poprzez awarię dysku czy niefortunne repartycjonowanie aż po działalność młodszej siostry. Dlatego żeby nikt nigdy nie mógł powiedzieć ci <em>mądry Polak po szkodzie</em>, rób kopie bezpieczeństwa.

Kopie można podzielić na dwa rodzaje. Pierwszy pomaga uchronić się przed utratą efektów pracy w razie awarii. W tym celu powinieneś kopiować swoje pliki na jakiś inny nośnik, poza dysk twardy - np. na swoje konto FTP/WWW/shell, na dyskietkę czy pamięć flash. Ja robie taką kopię codziennie wieczorem, jeśli tylko coś napiszę, a raz na miesiąc nagrywam wszystkie swoje dokumenty na CD-RW.

Drugi rodzaj kopii powinieneś robić przed poważnymi zmianami w kodzie czy wykasowaniem jakiegoś fragmentu, bo stara wersja tego kodu może jeszcze kiedyś okazać się potrzebna. W tym celu wystarczy objąć pewien fragment źródła w komentarz zamiast go usuwać. Czasem jednak wygodniej jest skopiować starą wersję pliku do osobnego katalogu.

Warto wspomnieć też o systemach kontroli wersji, które w sposób wygodny i funkcjonalny zapewniają archiwizowanie kolejnych wersji plików. W niektórych zastosowaniach są niezastąpione. Osobiście jednak uważam, że w projektach indywidualnych nie ma sensu <em>wytaczać armaty na wróbla</em> i najczęściej wystarcza katalog z podkatalogami z datami, np. Platformówka\Backup\2004-09-22\.

Testowanie

W zatytułowany w ten sposób rozdziale chciałbym tak naprawdę przedstawić kilka różnych tematów.

Szukanie błędów

Pierwszym z nich jest szukanie błędów. Rzadko zdarza się, żeby wszystko działało od razu poprawnie. Mówią nawet, że <em>jeśli program od razu działa dobrze, to coś jest nie tak</em>. Po napisaniu nieuchronnie czeka cię znajdowanie błędów w kodzie zwane też debugowaniem, a w inżynierii oprogramowania - uruchamianiem.

Chociaż nie należy to do przyjemności, to jednak nie musi to być aż tak znienawidzony etap realizacji projektu. Czasami błędu szuka się całymi godzinami, a nawet dłużej niż dzień, ale radość po znalezieniu i usunięciu takiego błędu jest naprawdę ogromna. Poniżej przedstawiam moje metody na znajdowanie błędów w kodzie.

Wcześniej jednak chciałbym wprowadzić pewien podział błędów. Najprostsze z nich to z pewnością błędy składniowe języka programowania. Każdemu zdarza się czasami zapomnieć średnika czy zrobić jakąś literówkę. W nauce bezbłędnego pisania bardzo pomaga programowanie w językach skryptowych takich, jak PHP, ale to nie jest przyjemna lekcja. W językach tych bowiem nie ma deklarowania zmiennych, a więc popełnienie literówki w nazwie zmiennej nie powoduje błędu kompilacji, tylko odwołanie do nowej zmiennej o pustej wartości, a w efekcie dużo trudniejsze do znalezienia błędy w działaniu.

Kompilatory (szczególnie do C++) mają nieprzyjemny zwyczaj wyrzucania ogromnej ilości komunikatów o błędach, w dodatku wszystkich nie tam i nie na ten temat, gdzie naprawdę jest błąd. Dlatego zawsze warto najpierw przyjrzeć się linijkom kodu wokół miejsca, w którym jest pierwszy błąd (szczególnie linijce poprzedniej), a dopiero potem przeczytać komunikat błędu.

Błędów składniowych nie należy bagatelizować nawet w językach takich, jak C++. Na przykład napisanie operatora przypisania = zamiast porównania == wewnątrz warunku if kompiluje się zwykle bez problemu, a powoduje błędne działanie programu.

Dużo gorsze są jednak tzw. błędy logiczne. Takie błędy to po prostu niedoprojektowany, nie przemyślany albo źle napisany kod, chociaż poprawny składniowo. Czasami powodują one wywłaszczenie znane też jako błąd ochrony albo wykonanie niedozwolonej operacji (niedopilnowany wskaźnik), zawieszenie się programu (nieskończona pętla), przepełnienie stosu (nieskończona rekurencja), a czasami po prostu dziwne i błędne zachowanie programu. W dalszej części tego podrozdziału opiszę metody znajdowania takich błędów.

Szukanie błędów w kodzie jest sztuką i wymaga przede wszystkim dużego doświadczenia. Jedną z najważniejszych metod na szukanie błędów jest kontrola przepływu sterowania, czyli co, kiedy i w jakiej kolejności się wykonuje. Czasami Wystarczy wiedzieć, czy jakiś fragment kodu albo jakaś funkcja w ogóle jest wykonywana, a czasami potrzebne są dokładniejsze informacje.

Beep

Najprostszą, ale zaskakująco skuteczną techniką jest wstawianie do kodu wywołań funkcji Beep(). Ta funkcja z nagłówka <windows.h> wydaje dźwięk z systemowego głośniczka zwanego też PC Speaker. Jeśli więc nie podłączałeś go kiedy składałeś swojego peceta, czym prędzej napraw ten błąd.

Nad możliwościami tej funkcji długo możnaby się rozwodzić. Przyjmuje ona dwa parametry - częstotliwość dźwięku (w hercach) i czas jego trwania (w milisekundach). Wstawienie do kodu takiego wywołania, jak pokazane poniżej, pozwala na stwierdzenie, czy dany fragment w ogóle się wykonuje i kiedy się wykonuje.

Beep(1000, 100);

W różnych miejscach kodu można wstawić takie wywołania z różnymi częstotliwościami (zwykle ok. 500-2000 Hz) i czasami (zwykle ok. 50-2000 ms), co pozwoli je rozróżniać. Parametrami tymi mogą nawet sterować aktualne wartości zmiennych. Dwa następujące po sobie, takie same wywołania Beep() nie brzmią jak jedno, ale wyraźnie słychać przerwę między nimi.

Logi

Innym sposobem są drukowania kontrolne (logi). Możesz zorganizować sobie jakąś możliwość zapisywania tekstu, a do różnych miejsc w kodzie wstawiać instrukcje zapisujące takie drukowania. Dzięki temu dowiesz się, co i w jakiej kolejności było wykonywane. Możesz także zapisywać wartości zmiennych po przetworzeniu ich na łańcuchy.

Takim miejscem do zapisywania logów mogą być:

  • Konsola systemowa - można ją stworzyć nawet w aplikacji okienkowej za pomocą funkcji AllocConsole().
  • Specjalnie stworzone, nowe okno.
  • Okno główne programu czy ekran główny gry - informacje loga trzeba wtedy prezentować razem z normalny obrazem.
  • Plik tekstowy

Przeglądanie kodu

Żeby znaleść błąd, czasami wystarczy przejrzeć swój kod - otworzyć w odpowiednich miejscach te pliki, w których twoim zdaniem ukrył się błąd i poczytać własny kod myśląc, jak będzie się wykonywał - wykonując go w <em>wirtualnej maszynie programisty</em>. Warto też skonfrontować użycie niektórych funkcji bibliotecznych z ich dokumentacją, żeby upewnić się, że podawane są do nich właściwe parametry.

Bardzo ciekawa jest też metoda pluszowego misia - próbujesz wytłumaczyć misiowi, jak działa twój kod, a <em>miś znajdzie błąd</em>. Z pewnością domyślasz się, jak to możliwe - próbując precyzyjnie i dokładnie opisać zasadę działania swojego kodu masz okazję jeszcze raz go przemyśleć i uchwycić błędy, sprzeczności i niedorzeczności, które mogły się do niego wkraść w czasie jego pisania.

Praca krokowa

Każde poważne zintegrowane środowisko programistyczne (IDE) posiada wbudowany debugger, a w nim możliwość pracy krokowej. Warto umieć używać tego potężnego narzędzia, bo dzięki niemu dużo łatwiej jest znajdować błędy. Stawia się w kodzie pułapki (ang. "breakpoint"), uruchamia się program, a w chwili wykonywania kodu z tych wybranych miejsc wykonania zostają wstrzymane. Można wtedy śledzić wartości zmiennych i robić różne inne rzeczy, ale przede wszystkim można posuwać się w wykonywaniu kodu po jednej linijce przeskakując je (polecenie "Step Over") lub wchodząc do wnętrza wywoływanych funkcji (polecenie "Step Into").

Dużą pomocą jest też podglądanie stosu wywołań (ang. "Call Stack"). Widać na nim aktualny stan zagnieżdżonych wywołań funkcji - jedna funkcja wywołała drugą, ta następną itd. aż do miejsca aktualnie wykonywanego kodu.

Komentowanie kodu

Kiedy wszystko inne zawodzi, trzeba odwołać się do najbardziej żmudnych i nieprzyjemnych metod. Prezentowana tu technika to ostateczność, a jej używanie jest konieczne właściwie tylko w jednym przypadku - kiedy debugowanie (praca krokowa) jest niemożliwe. Np. dzieje się tak, kiedy program pisany w Visual C++ działa poprawnie w wersji "Debug", a wysypuje się w wersji "Release" (w której debugowanie jest niemożliwe).

Wspomniana technika to obejmowanie w komentarz pewnych fragmentów kodu czy wywołań funkcji, a tym samym ograniczanie funkcjonalności programu. Możesz wykomentować te wywołania, które twoim zdaniem mogą powodować błąd, skompilować program i sprawdzić, czy bez nich faktycznie błąd nie występuje. Z drugiej strony konieczne może się okazać wykomentowanie prawie całego kodu, a następnie stopniowe wprowadzanie do niego kolejnych części jego pełnej funkcjonalności. Tak postępując można zawężać zakres poszukiwań aż do znalezienia miejsca w kodzie, które powodowało problemy.

Podsumowanie

Przy okazji warto wspomnieć o jeszcze jednej rzeczy, która odróżnia początkujących od zaawansowanych programistów. Początkujący napotkawszy jakiś większy błąd często <em>stają w kropce</em> i rezygnują z dalszego pisania. Dla zaawansowanych praktycznie nie ma błędów nie do znalezienia - wszystko jest kwestią odpowiedniego czasu i pracy oraz użycia odpowiednich metod.

Jednak są błędy jeszcze gorsze, niż wszystkie opisane powyżej. To błędy, które dają o sobie znać tylko czasami i są praktycznie nieuchwytne - objawiają się w różnych chwilach i w różnych miejscach, zupełnie ze sobą niezwiązanych. Najgorsze jest to, że czasami nie wiadomo, jak można je spowodować, a tym samym nie sposób ustalić ich przyczyny i miejsca w kodzie.

Co robić z takim błędem? Można pozostawić (może u innych go nie będzie :), można dalej go szukać (tylko ile czasu i pracy to jeszcze zajmie?), a można napisać dany moduł zupełnie od nowa. Najważniejsze jednak, by takich okropnych błędów po prostu nie robić. Ich przyczyną najczęściej są niedopilnowane wskaźniki.

Dlatego zawsze jasno definiuj, w jakich chwilach obiekty alokowane dynamicznie istnieją, kto jest odpowiedzialny za ich tworzenie i usuwanie z pamięci. Na przykład jeśli w każdej chwili obiekt może nie istnieć (wskaźnik do niego jest równy 0 - NULL) albo może istnieć jeden (wtedy wskaźnik pokazuje na niego), to podczas usuwania tego obiektu nie zapomnij wyzerować wskaźnika.

Testy

Drugim zagadnieniem, które chciałbym poruszyć w tym rozdziale jest testowanie napisanego już (przynajmniej do pewnego stopnia) i działającego poprawnie (przynajmniej na pozór) programu. Każdy program i każda gra po napisaniu przechodzi etap testów, w którym różni ludzie (nie tylko programiści) używają go znajdując przy tym błędy i zgłaszając swoje uwagi.

Testy Alfa

Testowanie każdego napisanego fragmentu programu, który już działa i nadaje się do uruchomienia to czynność, którą my - programiści - przeprowadzamy instynktownie. Nie ma sensu robić z tego żadnej wielkiej i systematycznej nauki, chociaż taką się robi.

Jako autor programu, który zna jego kod, możesz odruchowo dobierać testy, które najlepiej sprawdzą jego poprawność - wprowadzając wartości graniczne albo nawet spoza zakresu, wpisując litery w miejsce na cyfry itp. - to są tylko najprostsze przykłady. Z pewnością wiesz, o czym mówię.

Takie testowanie programu przez autora i inne osoby zaangażowane w projekt to alfa-testy. Wersja Alfa to wersja programu przeznaczona do testów wewnętrznych.

Testy Beta

Wersja Beta to wersja testowa programu przeznaczona do testów zewnętrznych - dla wszystkich lub tylko wybranych i upoważnionych osób spoza kręgu jego twórców. To bardzo ważne, by pokazać swoją produkcję innym osobom, które nie brały udziały w jego pisaniu. Warto, żeby niektóre z nich w ogóle nie były programistami.

Osoby niezaangażowane mogą po prostu spojrzeć na projekt z boku, obiektywnie, całościowo i zgłosić uwagi według swoich indywidualnych cech. Dlatego warto zebrać opinie od wielu osób. Można udostępnić wersję Beta swojego programu i poprosić, aby zainteresowane osoby pobrały go, przetestowały i wysłały do ciebie raport z testu. Należy ich zachęcić, żeby pisali w nim:

  • Informacje o znalezionych błędach
  • Swoje uwagi, propozycje, pomysły i sugestie

Mówi się, że w praktyce takie testy mają na celu wychwycenie tylko ogólnych błędów wynikających z niedostatecznego lub nieprawidłowego zaprojektowania, jak złe rozmieszczenie udostępnianych przez program funkcji, jakaś funkcja niepotrzebna, jakaś dodatkowa by się przydała itp. Wystarczy do tego kilka osób - testerów nie musi być dużo.

Bardzo niepokojące jest, że takie z pozoru drobne błędy projektowe wychwytywane przez niezależnych testerów na tym etapie testowania są często niezwykle trudne do poprawienia. Tak jest ze wszystkimi konsekwencjami złego zaprojektowania. Im wcześniej popełniony błąd (etap projektowania, analizy czy już określania wymagań), tym gorsze są jego konsekwencje i tym trudniej go naprawić. Jeden punkt w notatkach projektu może przekładać się na tysiące linii kodu w programie. Błędna decyzja na pierwszym etapie - w fazie strategicznej - dyskredytuje cały projekt.

Dlatego najlepiej, abyś projekty programu jeszcze przed rozpoczęciem pisania pokazał i przedyskutował z jakimś kolegą-programistą. Dzięki temu unikniesz wielu dalszych kłopotów. Pamiętaj, że zagłębiając się w szczegóły i myśląc dużo o danym programie tracisz możliwość obiektywnego spojrzenia na niego "z boku".

Optymalizacja

Kiedy już program działa i to działa poprawnie, można pomyśleć o zwiększeniu jego wydajności. W dziedzinie pisania gier ma to szczególne znaczenie i warto walczyć o każdą dodatkową klatkę na sekundę :)

Mówi się, że <em>przez 90% czasu wykonuje się tylko 10% kodu</em>. Oznacza to, że nie warto przerabiać wszystkiego, co skomplikowane, ważne i co mogłoby się wydawać przeznaczone do optymalizacji. Trzeba znaleźć te miejsca w kodzie, które z racji częstego uruchamiania czy wykonywania się bardzo wiele razy (w pętli) mają decydujący wpływ na wydajność i właśnie im przyjrzeć się bliżej patrząc, co można zrobić w celu ich przyspieszenia.

Do znajdowania tych tzw. <em>wąskich gardeł</em> są specjalne programy zwane profilerami. W praktyce często wystarcza jednak wstawienie do kodu odpowiednich wywołań, które pomierzą, ile czasu zajmuje wykonanie instrukcji zawartych między nimi.

MojProfiler.Begin();
// ...
MojProfiler.End();

Wystarczy odczytać czas na początku, czas na końcu i odjąć je od siebie. Możesz napisać własny moduł profilujący z takimi funkcjami i wyposażyć go w możliwości zapisywania wyników pomiarów do jakiegoś loga, np. do pliku tekstowego.

Zakończenie

Z zamiarem napisania tego artykułu nosiłem się już od dłuższego czasu, ale trudno mi było zebrać się na odwagę. W końcu to jest jeden z takich tekstów, który zamiast skupiać się na suchych zagadnieniach technicznych, opisuje sprawy bardziej ogólne, a także zawiera osobiste doświadczenia i opinie autora.

Zdążyłem już dwa razy rozpocząć i porzucić pisanie tego tekstu. Wreszcie jednak udało mi się go skończyć. Mówiąc szczerze, umotywowała mnie zachęta i oczekiwanie na ten tekst ze strony ludzi z Warsztatu oraz przyjacielska atmosfera, jaka mimo licznych sporów panuje na kanale #warsztat i na Game Design Forum.

Tak więc teraz, w przededniu I Ogólnopolskiej Konferencji Twórców Gier Komputerowych - pierwszego w historii zlotu Warsztatu - oddaje w wasze ręce ten tekst i mam nadzieję, że opisane w nim moje doświadczenia pomogą wam wypracować własne metody na solidne, systematyczne podejście do programowania, a tym samym zwiększyć ilość ukończonych produkcji, którymi możecie się pochwalić.

Na koniec pragnę podziękować wszystkim tym, którzy na ten artykuł czekali i pokładali w nim pewne nadzieje - mam nadzieję, że się nie zawiedliście. Szczególnie podziękowania należą się temu, który zawsze niestrudzenie recenzuje moje teksty zgłaszając cenne uwagi - a jest nim Xion.

Literatura


9 komentarzy

Koziołek 2008-10-24 09:13

Coldpeer mam jedną uwagę. Kompletnie pominąłeś problematykę programowania sterowanego testami, testy jednostkowe i automatykę testów. Obecnie coraz więcej softu powstaje w ten sposób i warto przypominać ciągle i bez przerwy, że pisanie testów i ich uruchamianie uchroni nas przed wieloma przykrymi konsekwencjami. Grrr...

kaban_os 2007-04-17 09:34

Kiedy pierwszy raz zobaczyłem ten art (a dokładnie chodzi mi o jego objętość, bo nie lubię dużo czytać) przestraszyłem się, ale gdy przeczytałem początek to tak mnie wciągneło, że nie mogłem się oderwać:) Atr jest super. Gratulacje!

lmmilewski 2006-08-09 13:10

Fakt - zauwazylem to dzisiaj ok 1030 ;-)

// btw dlaczego wszyscy pisza _I_mmilewski? Czy to l (el = L) wyglada faktycznie jak i?
// I != l ;-)

Coldpeer 2006-08-09 12:20

Immilewski: można, weź "edytuj" i tam wtedy będziesz miał "skasuj komentarz"
@up: tak, wygląda jak "i" :P

Patyk: autor artykułu w mailu sam napisał mi, że mam go w taki właśnie sposób podpisać. Po za tym ten artykuł znajduje się też na jego stronie domowej, do której dałem linka.

Patyk 2006-08-09 00:25

Artykuł pochodzi stąd: http://programex.risp.pl/?strona=j_k_projekty
Myślę, że wypadałoby podać link źródłowy oprócz autora tekstu.

@Coldpeer: Skoro tak to w porządku ;)

lmmilewski 2006-08-09 00:23

Szkoda, ze nie mozna usuwac wlasnych komentarzy - zauwazylem wlasnie podpis autora ;-D Sry

lmmilewski 2006-08-09 00:18

Nie chcialbym nikogo oskarzac o plagiat, ale juz czytalem ten artykul ;-) na Warsztacie, tylko jakos nie moge go teraz odszukac ;-/

TeWuX 2006-08-08 22:25

przeczytalem kilka farmentów, bardzo ciekawie i rzeczowo :)
jutro przeczytam całość.

jrCoder 2006-08-08 20:30

art na medal, sam jestem początkujący ale znam już język na tyle by "zakodować swoje pomysły" i ta praca napewno pomoże mi w dalszym rozoju moich umiejętności, thx

"Założenia główne (..) - dużo krwi" :)