Artykuł pobrano ze strony
eioba.pl
Poradnik Początkującego Programisty
Wychodząc na przeciw przyszłym programistom, postanowiłem stworzyć Poradnik początkującego programisty.
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ę ;>
Autor: Gynvael Coldwind
Przedruk ze strony:
http://gynvael.coldwind.pl/?id=238
Artykuł pobrano ze strony
eioba.pl