JustPaste.it

Poradnik Początkującego Programisty

Do autora bardzo często zgłaszają się do mnie osoby które programują stosunkowo niedługo, lub właśnie rozpoczynające swoją przygodę z programowaniem i pytają o to jaką książkę bym polecił do języka XYZ.

Do autora bardzo często zgłaszają się do mnie osoby które programują stosunkowo niedługo, lub właśnie rozpoczynające swoją przygodę z programowaniem i pytają o to jaką książkę bym polecił do języka XYZ.

 

Bardzo często zgłaszają się do mnie osoby które programują stosunkowo niedługo, lub właśnie rozpoczynające swoją przygodę z programowaniem, i pytają o to jaką książkę bym polecił do języka XYZ, czy znam jakiś dobry tutorial, ewentualnie jak rozwiązać dany problem (np. jak poprawić błąd kompilacji lub błąd logiczny), czy jaką funkcję użyć aby uzyskać określony efekt. Przez ostatnie 10 lat które "spędziłem" w Internecie takich rozmów odbyłem dziesiątki, jeśli nie setki. Tak więc wychodząc na przeciw przyszłym programistom, postanowiłem stworzyć Poradnik początkującego programisty (słowo "poradnik" jest tutaj kluczowe). Zapraszam do lektury!

1. Jaki język wybrać?

Jednym z najczęściej pojawiających się pytań jest "jaki język powinienem wybrać?", lub "jaki język będzie dla mnie najlepszy?". Jak można się domyślić, nie ma prostej odpowiedzi na to pytanie, ba, powiem więcej, przez ostatnie 20 lat odpowiedź staje się coraz trudniejsza.

W czasie gdy rozpoczynałem swoją przygodę z programowaniem (niecałe 20 lat temu) sprawa wyglądała bardzo prosto - kupowało się mały komputer marki Atari (np. Atari 800 XL), Commodore (np. C64) lub podobny 8-bitowy sprzęt, uruchamiało go, i pokazywał się interpreter języka BASIC. Tak więc do wyboru był BASIC, ewentualnie Logo które dostawaliśmy na kasecie/dyskietce, i które raczej przypominało zabawkę niż język programowania, oraz ewentualnie assembler jeżeli ktoś był na tyle wytrwały by się dowiedzieć jak dysponując jedynie interpreterem BASIC'a pisać w assemblerze. Oczywiście, czym bardziej zaawansowany był komputer i użytkownik, tym więcej języków miał do wyboru - np. Action!, Pascal, lub Turbo BASIC, ewentualnie C, Fortran, lub kilka innych dostępnych głównie na silniejszych platformach.

Teraz, 20 lat później, nie dość iż początkujący programista ma dużo większy wybór, to jeszcze oprócz wyboru języka musi wybrać środowisko programistyczne które go zadowoli i które będzie działać na jego systemie komputerowym.

Co w takim razie początkujący programista powinien wybrać? Zacznę od tego, że pytanie jest nie do końca dobrze postawione - moim zdaniem powinno brzmieć "jaki język powinienem wybrać na początek?". Różnica, mimo iż minimalna, jest bardzo ważna, a wynika z faktu iż programista nie powinien ograniczać się do jednego języka. Powinien raczej popróbować różnych języków, tak aby poznać różne punkty widzenia, dowiedzieć się jakie mechanizmy istnieją w różnych językach, pomyśleć jak symulować pewne rozwiązania natywne dla pewnych języków w innych językach, nie mających natywnego wsparcia dla danej funkcjonalności, etc. Proszę zauważyć iż nie twierdze, że programista powinien w nieskończoność skakać między językami, wręcz przeciwnie! Każdy programista prędzej czy później znajdzie jakiś swój jeden, jedyny, ukochany język, w którym będzie się specjalizował, i który po pewnym czasie będzie znał na wspak i po rumuńsku. Twierdzę natomiast iż programista który pozna rożne języki, będzie lepiej pisał w swoim ulubionym języku, niż programista który ograniczył się tylko do tego jednego języka, nie poznając innych rozwiązań. Np. zdradzę że moim ulubionym językiem jest C (w dialekcie C99), natomiast przez ostatnie 20 lat miałem przyjemność programować w około 40 różnych języków/dialektów.

Kolejna ważna uwaga: nie ma języków które są "zawsze złe", tak samo jak nie ma języków które są "zawsze dobre". Język to tylko i wyłącznie narzędzie w rękach programisty, narzędzie które do pewnych zadań nadaje się lepiej, a w innych przypadkach sprawdza się gorzej. Oczywiście pewne języki każdy z nas polubi bardziej, a inne wręcz znienawidzi, niemniej jednak pamiętajmy o potrzebie obiektywności, oraz o tym że nawet "znienawidzonego wroga" należy poznać. Zresztą, a nuż polubimy ów język po bliższym poznaniu ;>. Przykładowo, kiedyś nie przepadałem za Javą, uważałem że język jest strasznie wolny i do tego niewygodny. Nie przeszkadzało mi to natomiast przez pewien czas pracować jako programista Java, a później przez pewien okres interesować się wewnętrzną budową VM Java'y i byte codem samego języka, przez co Javę poznałem i nawet polubiłem ;>

Czas w końcu odpowiedzieć na pytanie z tematu - jaki język wybrać na początek, tak aby się nie zniechęcić? Moje osobiste propozycje wyglądają następująco:

- Python - Bardzo prosty do opanowania język, z ogromną liczbą bibliotek, i przy okazji uczący ładnego formatowania kodu, oraz posiadający niezłą dokumentację.
- Java - Java ma w sobie pewną prostotę która może przypaść do gustu sporej części osób. Język, podobnie jak Python, jest obecnie open source (więc w razie czego można rzucić okiem do źródeł - patrz punkt o źródłach wiedzy o programowaniu niżej), jest nieźle udokumentowany (chociaż dokumentacja Javy, moim zdaniem, wymaga przyzwyczajenia się), i posiada masę najróżniejszych bibliotek.
- C# - Trochę się wahałem czy ten język tu umieścić, z uwagi na jego głęboki związek z pewną firmą. C# jest językiem obiektowym, odrobinę trudniejszym niż Python, jednak łatwiejszym na początek niż C czy C++. Dokumentacja dostępna jest na MSDN, z którym trzeba się jednak na początku zapoznać, gdyż nie zawsze jest intuicyjny, ale po zapoznaniu się z nim, staje się on niezastąpiony.

Który z powyższych języków wybrać na początek? Jest to w zasadzie obojętne, gdyż po popisaniu trochę w jednym, powinno się zapoznać się z drugim. Oczywiście jeżeli ktoś uważa, że inny język byłby dla niego jednak lepszy na początek, to może spróbować - proponuję jednak zasięgnąć rady doświadczonego kolegi w tym wypadku, gdyż znam niejedną historię o tym jak to np. C czy C++ okazały się za trudne, i zniechęciły potencjalnego programistę do dalszego rozwoju w tej dziedzinie. Oczywiście należy zwrócić uwagę na to czy kolega mówi o języku, czy może mówi czy go lubi czy nie ;>

Co znaczy "trochę popisać" w powyższym stwierdzeniu? To zależy od człowieka. Najlepiej na początku popisać z pół roku, a może i rok, w jednym języku. Dlaczego tak długo? Problemem na początku drogi jest to iż trzeba uczyć się dwóch rzeczy na raz:

- po pierwsze: języka programowania - składni, funkcji, bibliotek, IDE, dodatków (np. preprocesora)
- po drugie: programowania - czyli umiejętności przelewania myśli/pomysłu na kod, czyli takiego korzystania z języka (składni/funkcji/bibliotek/IDE) aby napisany przez nas program robił dokładnie to co chcemy w możliwie najbardziej optymalny (nie koniecznie chodzi tu o optymalną szybkość) sposób

Umiejętność programowania jest niejako niezależna od znajomości języka. Znane są przypadki (szczególnie na różnego rodzaju konkursach programistycznych, czy compo), gdy osoby słabo znające dany język potrafiły stworzyć dużo ciekawszą aplikację niż osoby które dany język znały perfekcyjnie. Pamiętajmy więc, że język to nie wszystko. Należy jeszcze wiedzieć jak go używać - i to właśnie na celu ma nauka programowania.

Popisaliśmy więc trochę w Pythonie i C#, co dalej? Osobiście proponowałbym zapoznanie się z C/C++ i assemblerem (najlepiej równolegle), PHP, Perlem lub Rubym, Javą, oraz z innymi językami które wpadną nam w rękę. Jak pisałem wcześniej, i co będę powtarzał do znudzenia: czym więcej języków poznamy, tym lepiej. Oczywiście w każdy kolejny język wejdziemy łatwiej, gdyż na pewnym poziomie są one bardzo podobne.

Warto również rzucić okiem na inne rodzaje języków programowania - np. na języki funkcyjne jak Lisp, Haskell, języki logiczne jak Prolog, a nawet zabawkowe języki ezoteryczne jak Brainfuck czy LOLCODE.

Ważne jest również aby poznać narzędzie którego się używa - tj. edytor, IDE czy kompilator. Posiadają one zazwyczaj masę pomocnych opcji, które ułatwią tworzenie lub debugowanie programowania. Przykładem opcji które warto znaleźć w swoim IDE/edytorze jest np. skok do deklaracji, skok do definicji, skok do użycia danej funkcji/zmiennej czy szybkie wyszukanie funkcji w podręcznej pomocy. Warto sprawdzić również czy kompilator umie wypisywać dodatkowe informacje na temat błędów lub ostrzeżeń w kodzie (o tym jeszcze będę pisać), lub czy np. można obejrzeć wynik preprocessingu, czy kompilacji do assemblera (nie mylić z kodem maszynowym). Know your tools!

2. Jaką książkę wybrać?

Do drugiej grupy pytań które bardzo często słyszę można zaliczyć "z jakiej książki uczyć się języka XYZ?", "jaki tutorial będzie najlepszy dla języka XYZ?", lub "czy znasz jakieś strony z tutorialami do języka XYZ po polsku?". Bardzo często w takim przypadku osoba początkująca zakłada iż wystarczy przeczytać jakąś książkę lub jakiś tutorial by nauczyć się programować. Nic bardziej mylnego! Oczywiście w żadnym wypadku nie twierdzę, że książki/tutoriale są niepotrzebne, czy że nie warto ich kupować/ściągać. Problemem jest to w jaki sposób się z nich korzysta.

Moim zdaniem, wynikającym z osobistych doświadczeń, książki/tutoriala nie powinno się czytać. Zamiast czytania polecam natomiast wyszukiwanie listingów, i ich przepisywanie (tak, przepisywanie! nie kopiowanie, nie używanie kodu z dołączonej do książki płyty CD, tylko przepisywanie, literka po literce). Przepisując kod programu mamy pewność że nic ważnego z jego treści nam nie umknie, dodatkowo zapamiętujemy składnie, nazwy funkcji i poleceń oraz różne mechanizmy stosowane w języku.

Jednocześnie przepisując kod powinniśmy starać się zrozumieć co się w nim dzieje. Wiem że może to brzmieć jak duże wyzwanie - skąd osoba która nigdy wcześniej nie programowała miała by wiedzieć jak dany program działa? Jednak jest to możliwe, wystarczy zastosować pewną prostą sztuczkę:

W kodzie programu jest pełno wyrazów, które albo są angielskimi słowami, albo zlepkami lub skrótami od angielskich słów. Tak więc słownik w rękę (a raczej w przeglądarkę internetową) i tłumaczymy. Rozważmy np. poniższy kod (pochodzący z losowej strony znalezionej na google):

private void KeyDown(object sender, KeyboardEventArgs e)
      {
          switch (e.Key)
          {
              case Key.Escape:
                  // Will stop the app loop
                  Events.QuitApplication();
                  break;
       [...]


Jak wyraźnie widać powyżej, mamy w kodzie następujące słowa:

- private (ang. prywatny) - czyli coś jest prywatne, (i tu wchodzi wiedza ogólna) czyli nie jest publiczne, nie jest ogólnodostępne
- void (ang. pusty, nieważny, próżnia) - coś jest nieistotne, nie ważne (kolejne pytanie: co ?)
- KeyDown - zlepek słów: Key (ang. klucz, klawisz, przycisk) i Down (ang. pod, w dół, na dole) - czyli "klawisz w dół" - nietrudno się domyślić że chodzi o naciśnięcie klawisza
- object - (ang. obiekt, przedmiot) - czyli mamy do czynienia z jakimś obiektem
- sender - (ang. nadawca) - obiekt nadawca?
- KeyboardEventArgs - zlepek słów Keyboard (ang. klawiatura), Event (ang. zdarzenie, wydarzenie) i Args (i tutaj pojawia się problem! nie ma w słowniku słowa "args" ani "arg", chociaż wybitnie kojarzy się z jakimś piratem rodem z Karaibów ("Argh matee!"); w takim wypadku możemy to wrzucić w google, i zobaczyć podpowiedzi, ewentualnie jakie słowa znajduje; no i proszę, pod koniec pierwszej strony wyników znajdujemy podświetlone słowo "arguments", czyli argument (mój słownik podpowiada nawet że chodzi o"argument funkcji")) - Klawiatura Zdarzenie Argumenty - Argumenty zdarzenia które dotyczy klawiatury! Oczywiście słowo "argumenty" też może być na początku nieznajome - w takim wypadku możemy poszperać po wikipedii czy słowniku wyrazów bliskoznacznych - a nuż się coś nowego dowiemy

Podsumowując, pierwsza linia kodu to: prywatne nieistotne KlawiszNaciśnięty(obiekt nadawca, ArgumentyZdarzeniaKlawiatury e). Nawet jeżeli nie jest nadal do końca jasne co to jest, to wiemy już dużo więcej niż gdybyśmy po prostu rzucili okiem na tajemnicze private void KeyDown(object sender, KeyboardEventArgs e) i od razu polecieli dalej.

Warto zauważyć iż wiedza ogólna i matematyczna również się przydaje - w programowaniu widzimy sporo symboli znanych z matematyki, takich jak choćby + - * =, etc. Często sami możemy wywnioskować o co chodzi (mimo iż zapis programistyczny jest różny od matematycznego). Analogicznie jest z niektórymi nazwami funkcji, takimi jak sin czy log.

Tak więc przepisaliśmy pewien listing, potłumaczyliśmy słówka, ba, udało nam się nawet skompilować i uruchomić (poprawiając wcześniej literówki) program. Co dalej? Teraz modyfikujemy przepisany kod, czyli na chwilę zamieniamy się w naukowców, i zaczynamy przeprowadzać eksperymenty zmieniając pewne rzeczy w kodzie, patrząc uważnie co to zmienia w wykonaniu programu. Np. zamieniamy jakiś + na - i patrzymy co to zmieniło w wykonaniu programu. Usuwamy (lub komentujemy) jakąś linię, i patrzymy co to zmieniło. Zmieniamy jakieś literki tu i tam, i patrzymy czy się nadal kompiluje, ewentualnie jakiego rodzaju błąd wyskakuje (to nam się przyda później). I oczywiście próbujemy zrozumieć jak każda część programu działa.

A teraz, jeżeli nadal widzimy potrzebę, możemy przeczytać tekst w książce/tutorialu towarzyszący listingowi który przepisaliśmy, przeanalizowaliśmy i przerobiliśmy, jednak jest to raczej "bonus" niż konieczność.

Natomiast to nie jedyny pożytek z książki / tutoriala. Książka może służyć jeszcze jako ściąga z mechanizmów używanych w danym języku, oraz jako zestaw przykładów użycia różnych funkcji czy bibliotek. Również jeżeli brakuje nam jakiegoś środka ekspresji, tj. jeżeli zdajemy sobie sprawę że pewna konstrukcja w tym języku prawdopodobnie istnieje i wiemy że by nam się ona przydała - warto przekartkować książkę, i rzucić okiem na różne listingi, tytuły rozdziałów, etc. A nuż trafimy na to czego szukamy ;>

JacuS słusznie również zauważył, że dla niektórych zestaw zadań i pytań znajdujących się pod koniec rozdziału w niektórych książkach również może być bardzo pomocny i pożyteczny.

Podsumowując, początkujący programista nie powinien czytać książki / tutoriala od deski do deski. Przede wszystkim powinien przepisać/przeanalizować listingi, a po za tym powinien próbować z książki korzystać jak z leksykonu lub zbioru podpowiedzi.

Proszę również zauważyć, iż korzystając z tego sposobu uniezależniamy się od języka w jakim jest tekst. Książka może być po chińsku, japońsku, fińsku czy w jakimś innym egzotycznym języku, ale kod i tak będzie w języku programowania którego się uczymy, z nazwami pochodzącymi z języka angielskiego. Tak więc listing i tak bez problemu przepiszemy i przeanalizujemy, nie tracąc czasu na naukę języka chińskiego ;>

Drugą rzeczą godną zauważenia jest fakt, iż tak na prawdę przestaje mieć znaczenie z jakiej książki / tutoriala się uczymy, czy też jak tekst jest napisany (przystępnie? a może dla robotów?). Z każdej książki czy tutoriala możemy przepisać kod.

3. Z czego korzystać przy nauce języka/programowania?

To jest pytanie którego niestety nie słyszę zbyt często, a jest bardzo istotne. Książki i tutoriale nie są oczywiście jedynym źródłem informacji jakim programista dysponuje. Posunę się nawet dalej, i powiem, że moim zdaniem książki i tutoriale są jednym z mniej ważnych materiałów.

Z czego powinien więc korzystać programista podczas nauki? Krótka lista pozostałych możliwości (w kolejności losowej):

- Oficjalna dokumentacja języka oraz bibliotek - Nikt nie zna tak dobrze zakamarków języka programowania jak jego twórca! Dodatkowo, ów tajemniczy twórca bardzo często udostępnia dokumentacje za równo samego języka, jak i standardowych bibliotek dołączonych do języka. Oczywiście oficjalna dokumentacja jest prawie zawsze w języku angielskim, i niestety od tego nie uciekniemy. Owszem, istnieją translatory, i co bardziej uparte osoby będą z nich korzystać, jednak bardzo szybko jasne się stanie, że jeżeli ktoś chce być dobrym programistą, to język angielski musi znać przynajmniej na poziomie umożliwiającym czytanie dokumentacji.

- Kod źródłowy innych aplikacji - Przeglądając listingi innych programistów również się dużo uczymy, za równo o języku programowania, jego funkcjach, jak i o samym programowaniu. Obecnie, w dobie Internetu, mamy pod dostatkiem stron na których możemy ściągnąć źródła jakiejś aplikacji, i zobaczyć co ciekawego w niej siedzi. A nuż znajdziemy jakąś ciekawą konstrukcje, czy może w końcu dowiemy się jak poprawnie używać jakiejś funkcji. Warto również zapoznać się z wyszukiwarkami kodu źródłowego, takimi jak Krugle czy Google Code Search.

- Artykuły w (e)czasopismach - Gdy zaczynałem uczyć się programować wychodziło pewne czasopismo o nazwie Bajtek. Kosztowało grosze, a całe składało się prawie wyłącznie z listingów kodu (głównie w BASICu lub w Assemblerze). Szczerze przyznaje że wiele nauczyłem się z artykułów zawartych w tym czasopiśmie. Dzisiaj, niecałe 20 lat później, rynek (e)czasopism się trochę rozwinął, chociaż z bólem serca przyznaje iż nic równie taniego czy przystępnego jak Bajtek dawno nie widziałem. Niemniej jednak warto zainteresować się czy czasem na rynku / w Internecie nie ma czasopisma traktującego o języku którego się właśnie uczymy. Warto rzucić okiem również na serwisy związane z demosceną czy gamedev sceną, czy czasem nie ma informacji o jakimś nowym wydaniu e-zina.

- Wnętrze języka i bibliotek - Dokumentacja może być nieprecyzyjna, jednak kod źródłowy zawsze jest precyzyjny (mimo iż nie zawsze poprawny czy czytelny ;>). Tak więc jeżeli mamy dostęp do źródeł danego języka programowania (kompilatora, interpretera, VM), lub/i do źródeł standardowych bibliotek, rzućmy na nie okiem. Źródła te mogą na początku wydawać się dla nas zbyt skomplikowane, jednak czym szybciej nauczymy się z nich korzystać, tym dla nas lepiej. Często również pliki nagłówkowe różnych bibliotek są bardzo pomocne (szczególnie pisząc w C/C++). Oczywiście osoby zaawansowane mogą dodać reverse engineering do tego punktu.

- Fora, listy dyskusyjne - Każdy problem który my mamy teraz, z dużym prawdopodobieństwem ktoś kiedyś już miał, i prawdopodobnie zapytał o rozwiązanie na jakimś forum lub na jakiejś liście dyskusyjnej. Odpalmy więc naszą ulubioną wyszukiwarkę, i poszukajmy. Przejrzyjmy również tematy na różnych forach związanych z programowaniem, skorzystajmy z wyszukiwarki wewnątrz forum, etc. Natomiast starajmy się nie pytać nikogo o rozwiązanie. W momencie kiedy zapytamy kogoś jak rozwiązać dany problem, sami składamy broń, poddajemy się i przestajemy o problemie myśleć. Może to wielu z was zaskoczyć, ale tak jest na prawdę - czym mniej ktoś prosi innych o pomoc, tym lepszym programistą się staje, a czym więcej ktoś prosi o pomoc, tym mniej samodzielny i bardziej uzależniony od innych będzie. Poproszenie kogoś o rozwiązanie za nas problemu jest straconą okazją do nauczenia się czegoś nowego, i straconą okazją do nauczenia się jak takie problemy rozwiązywać. Zapewniam was że samodzielność w rozwiązywania problemów w programowaniu jest bardzo ważna. Po za tym pamiętajcie, że kto pyta, ten błądzi z innymi ;>

- Książki i tutoriale - Mimo iż celowo umniejszam rangę tychże mediów, to w żadnym wypadku ich nie skreślam. Należy pamiętać o ich istnieniu, i korzystać z nich w miarę potrzeby. Tak na prawdę czym dalej w las, tym bardziej książki zaczynają się przydawać, szczególnie książki wysoko specjalistyczne.

- Kursy, szkolenia, videoarty, konferencje - Jeżeli mamy możliwość in real life bądź przez Internet uczestniczyć w jakimś kursie lub szkoleniu, to skorzystajmy z okazji. Być może pewna kwestia stanie się dla nas jaśniejsza, lub dowiemy się nowych rzeczy. Oczywiście koniecznością jest późniejsze przećwiczenie samemu tego co się nauczyliśmy. Celowo videoarty wrzuciłem do tej, a nie poprzedniej kategorii.

- Konsultacje z bardziej doświadczonymi kolegami - Wcześniej pisałem że nie powinniśmy nikogo prosić o pomoc, więc czemu nagle "wyskakuje" z konsultacjami? Ponieważ czymś innym jest proszenie o rozwiązanie własnego problemu, a czymś innym jest proszenie o radę o lub o komentarz dotyczący naszych zaproponowanych rozwiązań. Jeżeli koniecznie chcemy już z kimś porozmawiać o problemie, to wymyślmy kilka rozwiązań, i przedstawmy je osobie którą uważamy za bardziej doświadczoną - wtedy nie "zwalamy" odpowiedzialności za rozwiązanie problemu na nią (bo sami wymyśliliśmy już kilka rozwiązań), a możemy się czegoś ciekawego nauczyć. Po za tym zapewniam was (jako swojego rodzaju konsultant) iż dużo fajniej rozmawia się z osobami samodzielnymi o ich rozwiązaniach, niż rozwiązuje, zazwyczaj błahe, problemy ludzi uzależnionych od pomocy innych.

Podsumowując, mamy przynajmniej 7 różnych grup narzędzi pozyskiwania wiedzy które możemy używać samodzielnie, oraz jedną grupę w której o radę (nie o rozwiązanie!) możemy poprosić osobę z większym doświadczenie.

4. Jak samodzielnie rozwiązywać problemy?

Problemy w programowaniu zasadniczo są dwa:

1. Coś się nie kompiluje (kompilator lub interpreter wypisują błędy)
2. Coś działa nie tak jak powinno

W pierwszym przypadku przede wszystkim przeczytajmy jaki błąd jako pierwszy wypisał kompilator/interpreter! Większość kompilatorów/interpreterów wypisuje zazwyczaj numer linii w której wystąpił błąd, opisuje (zazwyczaj w bardzo skróconej formie) jaki to błąd jest, i czasami cytuje fragment kodu. Każda z tych informacji jest dla nas cenna (w miarę możliwości należy się dowiedzieć w jakim formacie dany kompilator wypisuje błędy, i co w nich zawiera), mimo iż nie zawsze te informacje są prawidłowe (jest to swoista pułapka na początkujących programistów). Krótkie wyjaśnienie jeszcze czemu piszę o czytaniu tylko informacji o pierwszym błędzie: często zdarza się że dostajemy 2 strony ekranowe błędów, ale w 99% przypadków tylko pierwszy błąd jest prawdziwy, a całą reszta jest wynikiem tego pierwszego błędu. Tak że po usunięciu pierwszego błędu może się okazać iż więcej nie było, lub że z 2ch stron ekranowych robi się 5 linii.

Co dalej ?

- Po zapoznaniu się z informacją którą dostaliśmy od kompilatora otwórzmy w edytorze linię którą kompilator wskazuje, i rozejrzyjmy się czy nie widać tam nić podejrzanego. Często błędem jest literówka, nadmiarowa spacja (Python), zagubiony średnik, lub inny prosty błąd składniowy.

- Jeżeli nie widzimy nic oczywistego w danej linii, spójrzmy parę linii w górę - często zdarza się iż np. błąd sygnalizowany w linii 40 wynika z niedokończonego wyrażenia w linii 38.

- Kolejnym krokiem jest wrzucenie kodu lub opisu błędu w wyszukiwarki internetowe / oficjalny help - a nuż ktoś kiedyś o to już pytał, i dostał odpowiedź, lub w dokumentacji jest pokazany przypadek wystąpienia takiego błędu.

- W tym momencie zazwyczaj warto sprawdzić czy na pewno kompilujemy plik który nam się wydaje że kompilujemy - nie raz widziałem sytuację w których okazywało się że błąd poprawiliśmy już 10 minut temu, tylko skompilowaliśmy nie ten plik co trzeba.

- Jeżeli to nic nie da, przekopiujmy kilka linii na chwilę do notatnika, i przepiszmy (ręcznie) je z powrotem do naszego programu - eliminuje to niezauważone przez nas literówki (albo je wyłapiemy przy przepisywaniu, albo "przez przypadek" w locie poprawimy) oraz naprawia przypadki w których edytor z jakiegoś niesprecyzowanego powodu dodał jakiś tajemniczy znak w daną linię kodu (zdarza się to np. gdy kopiowaliśmy jakiś fragment kodu z PDF'a, ze strony WWW, lub z komunikatora internetowego).

- Jeżeli żaden z powyższych sposobów nic nie dał, skopiujmy minimalny fragment kodu w którym sygnalizowany jest błąd do oddzielnego projektu, i spróbujmy tam go skompilować - może się okazać że to jakiś inny fragment kodu (np. zapomniana deklaracja preprocesora) powoduje błąd w tym miejscu. Możemy również spróbować w drugą stronę, tj. usunąć z naszego projektu niepotrzebny kod, i zostawić tylko fragment sprawiający trudności.

- Jeżeli i to nic nie dało, ściągnijmy inną wersję kompilatora/interpretera/środowiska, i zobaczmy czy tam problem również występuje. Ewentualnie poprośmy kolegę aby sprawdził czy u niego również się nie kompiluje.

Szczerze mówiąc chyba nie zdarzyło mi się aby po wyczerpaniu powyższej listy kod nadal się nie kompilował ;>

Co natomiast zrobić w przypadku gdy kod się kompiluje, natomiast program nie działa tak jak byśmy chcieli? Zdebugować!

Debugowanie jest bardzo ogólnym terminem, pochodzącym zresztą od pozbywania się prawdziwych (biologicznych ;>) robaków chodzących wśród lamp pierwszych komputerów i powodujących losowe zwarcia. Tak na prawdę debugowanie wcale nie musi oznaczać skorzystania z debuggera, i prawdę mówiąc z debuggera korzysta się może w 1/3 przypadków. Poniżej prezentuje 4 metody debugowania których ja używam (w kolejności losowej, tzn. każdą z poniższych metod należy znać, i wiedzieć jak stosować; żadne nie jest bardziej czy mniej ważna od innej):

1. Print it!

Pierwsza metoda polega na powrzucaniu w kod instrukcji wypisujących dodatkowe informacje (najlepiej ów informacje wypisywać na konsolę lub zapisywać do pliku). Umożliwia to kilka rzeczy:

1. Namierzenie dokładnego miejsca występowania błędu. W tym celu wystarczy powrzucać w różne miejsca w kodzie instrukcje wypisania np. numeru linii, albo różne literki. Następnie uruchamiamy program, i patrzymy które wiadomości i w jakiej kolejności zostały wypisane - dzięki temu dowiadujemy się do którego momentu program dochodzi, które warunki są spełnione, i w które funkcje i zagłębienia program wchodzi. Zazwyczaj zaczyna się na najwyższym poziomie zagłębień, i dodaje z 3-5 "printów" w różne miejsca, a następnie na podstawie informacji które uzyskujemy po uruchomieniu, część "printów" przesuwamy w inne miejsca, za każdym razem zbliżając się o krok do zlokalizowania błędu logicznego.

Np. w poniższym kodzie jedna z funkcji "crashuje" program:

FUNC_A();
FUNC_B();
FUNC_C();


Aby dowiedzieć się która funkcja zawiera błąd, wrzucamy kilka printów:

PRINT "przed A";
FUNC_A();
PRINT "przed B";
FUNC_B();
PRINT "przed C";
FUNC_C();
PRINT "po";


Uruchamiamy i patrzymy co się wypisało:

przed A
przed B
*** CRASH ***


Wiemy więc że wykonanie dotarło do "przed B", ale nie dotarło do "przed C", tak więc błąd jest w funkcji FUNC_B. Idąc dalej przenieśli byśmy "printy" wewnątrz FUNC_B, tak aby namierzyć w końcu dokładną linię błędu.

2. Uzyskanie informacji dlaczego błąd występuje. W tym celu dodajemy "printy" wypisujące wartości zmiennych, wyniki warunków. Jeżeli wiemy że błąd jest w FUNC_B, i wiemy że korzystamy tam z 5ciu zmiennych które mogą mieć wpływ na program, wypiszmy te zmienne. Będziemy wtedy dokładnie wiedzieć co się dzieje, i będziemy mogli zadecydować jak poprawić dany kod.

Rozważmy poniższy kod:

def FUNC_B()
 A=4;
 B=3;
 WHILE(A>B)
   B=A/B;
   B=B-1;


Dodajemy "printy":

def FUNC_B()
 A=4;
 B=3;
 PRINT "przed", A, B;
 WHILE(A>B)
   PRINT "w1", A, B;
   B=A/B;
   PRINT "w2", A, B;
   B=B-1;
   PRINT "w3", A, B;


Uruchamiamy, i spoglądamy na output:

przed 4 3
w1 4 3
w2 4 1
w3 4 0
w1 4 0
*** CRASH ***


Analizujemy wynik, i już wiemy że błąd polega na tym iż B w pewnym momencie osiąga wartość 0, i następuje dzielenie przez 0. Co z tym teraz zrobimy zależy oczywiście od nas, i od tego jak program ma działać ;>

Metoda "Print it!" jest niesamowicie skuteczną metodą debugowania, i można nią z powodzeniem zlokalizować całą masę różnych błędów, w tym takich które np. nie objawiają się przy włączonym debuggerze. Na prawdę zachęcam do stosowania tej metody!

Na koniec jeszcze jedna uwaga (o której przypomniał mi Makdaam) - niektóre języki programowania w niektórych przypadkach buforują tekst przez chwilę przed jego wypisaniem, przez co jeżeli tekst nie został jeszcze wypisany (tj. nadal czeka w buforze), a aplikacja się "wyłożyła" (np. z powodu krytycznego błędu), to ów tekst nigdy nie zostanie wyświetlony. W takim przypadku należy po każdym dodanym "print'cie" dodać również wymuszenie opróżnienia bufora (zazwyczaj komenda od tego ma słowo 'flush' w sobie; przykładowo, w języku C/C++ w przypadku konsoli piszemy fflush(stdout)).

2. "Na pluszowego misia"

Sprawa jest prosta - znajdujemy jakąś cierpliwą osobę (np. pluszowego misia ;>) lub przedmiot, któremu tłumaczymy, dokładnie, od A do Z, jak działa problematyczny fragment naszego kodu, oraz jak objawia się problem. Z bardzo dużym prawdopodobieństwem podczas tłumaczenia przyjdzie nam do głowy na czym problem polega i jak go rozwiązać.

Domyślam się że pomysł brzmi zabawnie, niemniej jednak zaręczam o jego skuteczności. Na koniec dodam że nazwę tej metody usłyszałem na konferencji IGK, podczas prelekcji Adama Sawickiego.

3. Zabawa w procesor

Bierzemy kartkę papieru, lub otwieramy edytor tekstu, wypisujemy sobie zmienne, i ich wartości, używane w problematycznym fragmencie kodu, po czym linia po linii "wykonujemy" program, czyli na chwilę zamieniamy się w procesor/interpreter. Postępując w ten sposób albo natkniemy się na błąd, albo (co zazwyczaj wymaga połączenia z metodą "Print it!") natkniemy się na różnicę w pojmowaniu kodu przez nas i przez interpreter (tj. interpreter inaczej wykonuje kod niż my myśleliśmy że on to robi).

Tą metodę polecam również przy operowaniu na wskaźnikach/referencjach, do testowania zaimplementowanego rozwiązania po jego napisaniu. Nie raz uratowała mnie przed późniejszym debugowaniem ;>

4. Debugger

I w końcu dojechaliśmy do debuggera, czyli wyspecjalizowanego narzędzia pozwalającego na śledzenie przebiegu programu i wartości zmiennych. Debuggery często przerażają początkujących programistów swoją złożonością, niemniej jednak umiejętność korzystania z debuggera często pozwala wyśledzić pewnego rodzaju błędy dużo szybciej niż pozostałymi metodami. Przykładowo, większość debuggerów potrafi z dokładnością do linii kodu wskazać miejsce w którym program się "wysypał" (tj. rzucił nieobsłużonym wyjątkiem i został zabity przez system operacyjny).

Niestety (a może stety) w przypadku niektórych języków i niektórych przypadków, do poprawnej interpretacji informacji przekazanych przez debugger, wymagana jest znajomość języka niższego poziomu, np. assemblera. Szczerze mówiąc, nie taki straszny assembler jak go malują ;>

Prewencja

Często możemy zaoszczędzić czas po prostu starając się stosować pewne środki prewencyjne. Przede wszystkim zalecam zmuszenie kompilatora do wypisywania jak największej ilości informacji o potencjalnych błędach, czy dodatkowych ostrzeżeń. Początkujący programiści mają niestety tendencje do robienia dokładnie na odwrót - tj. wyłączają lub ignorują wszystkie ostrzeżenia, co niestety powoduje iż zamiast poprawić pewne błędy od razu, zaczynają ich szukać gdy nie jest to już takie łatwe. Ostrzeżeń nie usuwa się poprzez ich ukrywanie, tylko poprzez naprawę kodu źródłowego!

Kolejną metodą jest po prostu testowanie kodu co jakiś czas, lub nawet testowanie każdej napisanej funkcji osobno (patrz testy jednostkowe) - napiszmy krótki programik testowy dla danej funkcji, upewnijmy się że dane wyjściowe są tym czego oczekujemy.

Dobrym pomysłem jest również używanie assertów. Assert'y są pewnymi założeniami, które twórca funkcji uważa za oczywiste, i które ma nadzieje że użytkownik funkcji (którym oczywiście często jest po prostu jej twórca) również będzie stosował. Jeżeli użytkownik funkcji nie zastosuje się do założenia, wtedy assert zasygnalizuje błąd. Warto sprawdzić czy dany język ma mechanizm assertów (ewentualnie jak go emulować), i warto z nich korzystać.

Bardzo częstym błędem, za równo początkujących, jak i zaawansowanych programistów, jest ignorowanie tzw. "obsługi błędów". Większość funkcji w każdej bibliotece może z jakiegoś powodu się nie wykonać poprawnie - w takim wypadku funkcja zasygnalizuje błąd, który powinien zostać wyłapany i odpowiednio obsłużony przez program (np. poprzez wypisanie informacji o zaistniałym błędzie). Informacje o tym jak funkcja sygnalizuje błąd, i jakie błędy mogą się pojawić, znajduje się oczywiście w oficjalnej dokumentacji danej funkcji (tj. w dokumentacji języka, biblioteki, czy systemu operacyjnego). Dodam że nie powinno się starać naprawiać takich zgłoszonych-przez-funkcje błędów na siłę - często powoduje to wprowadzenie kolejnych błędów. Czasami lepiej po prostu napisać użytkownikowi że coś poszło nie tak, i pozostawić jemu decyzję co z tym zrobić.


Kończąc temat rozwiązywania problemów dodam jeszcze, ponownie z własnego doświadczenia, że późnym wieczorem, gdy człowiek jest już zmęczony, pewne oczywiste błędy stają się nie do znalezienia. W takim wypadku wystarczy iść spać. Raz, że mózg nadal będzie myślał nad problemem (patrz "wgląd") i być może obudzimy się znając rozwiązanie, a dwa, że wypoczęty umysł może dostrzec oczywistość która nam wcześniej umknęła.

5. Droga ku doskonałości

Każdy początkujący programista prędzej czy później zacznie się zastanawiać czego nowego powinien się nauczyć lub jakie ćwiczenia powinien wykonywać, tak aby stawać się coraz lepszym. Poniżej prezentuje kilka moich przemyśleń:

1. Programuj, programuj, programuj!

Sprawa jest prosta - programowania uczy się programując, tj. jeżeli chcesz być dobrym programistą, musisz stworzyć na prawdę wiele programów/skryptów. Przykładowo, osoby które uważam za dobrych programistów, codziennie coś piszą. A piszą krótkie programiki do przetestowania nowo poznanej funkcji/biblioteki lub zweryfikowania jak język zachowuje się przy zastosowaniu jakiegoś niestandardowego triku, piszą różne funkcje i biblioteki które być może przydadzą się przy następnym projekcie, rozpoczynają nowe małe, średnie i duże projekty, i następnie dłubią przy nich przez kilka dni/tygodni/miesięcy, testują inne języki programowania by następnie zapożyczyć z nich pewne rozwiązania i użyć ich w ich ulubionym języku, próbują modyfikować kody stworzone przez innych, etc. Dzięki temu każdy z nich zdobywa doświadczenie oraz łatwość w posługiwaniu się językiem, oraz uczy się programować (a tego człowiek uczy się całe życie).

Tak więc jeżeli chcesz zostać dobrym programistą - pisz! Napisz codziennie kilkadziesiąt-kilkaset (a może i kilka tysięcy!) linii kodu.

Ludzie oczywiście są różni - jedni mają tyle pomysłów "co by tu napisać" że muszą wybierać za co się wezmą, a inni mają problem z wymyśleniem jednej rzeczy którą chcieli by napisać. Ci pierwsi mają łatwiej, natomiast ci drudzy muszą starać się poszukać jakiś inspiracji. Źródło inspiracji jest oczywiście kwestią indywidualną. Czasami wystarczy wyjść do parku (np. mnie zawsze po wizycie w parku strasznie ciągnie żeby napisać jakiegoś MMORPG ;>), przejrzeć screeny z gier tworzonych przez innych programistów (polecam http://gamedev.pl), czy otworzyć książkę o programowaniu na losowej stronie. Zachęcam do poszukania własnych źródeł inspiracji ;>

Dodam że warto wyznaczać sobie cele które przekraczają nasze możliwości - tak uczymy się najwięcej. Nawet jeżeli w końcu nie skończymy danego projektu, to i tak dużo się nauczymy w ten sposób. Przy okazji, ważna uwaga - dużo osób rozpoczyna jakieś projekty, które po jakimś czasie porzucają, i nie biorą się za nic nowego, ponieważ męczą ich wyrzuty sumienia że porzucili poprzedni projekt, i chcieli by go dokończyć. To że nie kończymy własnych projektów jest jak najbardziej normalne (czy też "jak najbardziej ludzkie), i najgorszą rzeczą jaką możemy zrobić to pogrążyć się w wyrzutach sumienia. Zamiast się zamartwiać że nie skończyliśmy tamtego projektu, weźmy się za następny - tracimy tak dużo mniej czasu, a i być może po kolejnym projekcie wrócimy do poprzedniego. Oczywiście mimo to należy starać się kończyć projekty, ale gdy już projekt padnie, powinniśmy po prostu wziąć się za następny, i spróbować jeszcze raz ;>

2. Poznawaj!

Przy okazji wyboru języka pisałem już o potrzebie poznawania nowych rzeczy. Jako programiście powinniśmy uczyć się następujących rzeczy:

1. Nowych funkcji i bibliotek
2. Algorytmiki i struktur danych
3. Nowych języków
4. Nowych narzędzi ułatwiających programowanie i debuggowanie (debuggerów, profilerów, systemów wersjonowania, IDE, systemów automatycznego generowania dokumentacji, systemów budowania aplikacji, etc)
5. Nowych "specjalności" programistycznych - np. programowania sieciowego, programowania aplikacji webowych, programowania gier, proceduralnego tworzenia grafiki, systemów operacyjnych, pisania pluginów, etc
6. Systemu operacyjnego - API systemowego, oraz tego jak działa i jak jest zbudowany
7. Nowych metod rozwiązywania różnych problemów programistycznych
8. Słów związanych z programowaniem - na początek wymienię choćby: deklaracja, definicja, prototyp, argument funkcji - ale tych słów jest dużo więcej ;>
9. Etc.

Programowanie to ciągła nauka (to stwierdzenie jest prawdziwe dla każdej działki w której chcemy się specjalizować ;>).

3. Dziel się wiedzą!

W środowiskach akademickich krąży taki jeden dowcip (który postaram się w dość luźny sposób zacytować):

Profesor opowiada koledze:
- Tłumaczyłem ostatnio studentom pewną rzecz. Tłumaczę pierwszy raz, nie zrozumieli. Tłumaczę drugi raz, znowu nie zrozumieli. Tłumaczę więc kolejny, nadal nic. Tłumaczę  czwarty raz, sam zrozumiałem, a oni nadal nic...


Dowcip ten zawiera bardzo ważną wskazówkę - pomagając rozwiązać problem innym, sami uczymy się go rozwiązywać. A nawet jeśli wiedzieliśmy już jak to zrobić, to często dowiadujemy się czegoś nowego, lub po prostu utrwalamy sobie trochę informacji. Proszę zauważyć, że jest to dokładnie odwrócenie tego o czym pisałem przy okazji for internetowych - nie prośmy o pomoc, sami pomagajmy - tak uczymy się najwięcej.

Jeżeli uważamy że staliśmy się wystarczająco silni z jakiejś dziedziny, możemy spróbować również napisać jakiś artykuł, kurs czy tutorial. Przy okazji tworzenia takich rzeczy bardzo często musimy przeprowadzić trochę dodatkowych badań jak coś działa, czy poszperać w dokumentacji by upewnić się że faktycznie jest tak jak piszemy - i dzięki temu uczymy się nowych rzeczy, jak i utrwalamy sobie to co już wiedzieliśmy. Dodatkowo, taki artykuł może przydać się w przyszłości jakiemuś mniej lub bardziej początkującemu programiście ;>

Przy okazji tego punktu jednak bardzo ważna uwaga: od braku informacji gorsza jest dezinformacja. Tak więc wypowiadając się na forum czy liście dyskusyjnej, pisząc artykuł czy tutorial, pamiętajmy o tym że jesteśmy przez czytelnika traktowani z dużą dozą zaufania, i że prawdopodobnie wszystko co napiszemy zostanie potraktowane jako "na pewno prawdziwe". Przez to spoczywa na nas duża odpowiedzialność za prawdziwość, prawidłowość i aktualność informacji. Możemy na prawdę dużo zamieszania narobić puszczając w obieg nieprawdziwe informacje, lub po prostu możemy opublikować coś za co później, za 5-10 lat, będziemy musieli się wstydzić.

Pamiętaj, że nie chodzi o to by opublikować artykuł, tylko o to by go napisać. Nawet nieopublikowany artykuł uczy bardzo dużo ;>

4. Znajdź jakieś wyzwanie

Niektórzy ludzie, głównie mężczyźni, bardzo lubią wyzwania. Bardzo motywuje ich współzawodnictwo, konkurowanie z innymi, etc. Jeżeli jesteś takim człowiekiem, wykorzystaj to! Na różnych scenach/forach/serwisach programistycznych często organizowane są różne konkursy (zwane często "compo"), często organizowane po prostu przez nudzących się ludzi (tj. bez nagród). Bierz udział w takich konkursach, dużo można się w nich nauczyć, a także poznać wielu bardzo ciekawych ludzi.

Często również możliwość pochwalenia się innym swoim programem wpływa bardzo motywująco ;>

Koniec, a w zasadzie dopiero początek!

Jeżeli doczytałeś/łaś do tego miejsca, gratuluje wytrwałości i cierpliwości! Dodam że cieszę się podwójnie z tego, gdyż cierpliwość i wytrwałość to bardzo ważne cechy dla programisty ;>
Na koniec chciałbym życzyć Ci powodzenia i wytrwałości!
HF GL!

Gynvael Coldwind
team Vexillium
http://gynvael.coldwind.pl

Notka na temat kopiowania "Poradnika Początkującego Programisty": Niniejszym udzielam zgody na kopiowanie, rozpowszechnianie i publikowanie niniejszego artykułu, pod warunkiem iż forma i treść pozostanie niezmieniona, prawdziwy autor jasno określony, a artykuł będzie prezentowany nieodpłatnie, w pełnej postaci. W przypadku wątpliwości można się ze mną skontaktować, nie gryzę ;>

 

Źródło: Gynvael Coldwind