Błędy w kodzie zdarzają się każdemu. Skłonny nawet jestem stwierdzić, że najgłupsze błędy zdarzają się najczęściej najlepszym - wtedy automatycznie spada czujność na najprostsze zapisy, a że kompilator nie zawsze trafnie wyraża się o błędzie, to może człowieka szlag trafić. Oczywiście, tym wstępem nie mówię, że czuję się najlepszy - wprost przeciwnie. Ale błędy zdarzają mi się bardzo często - do tego dołóżcie moją nerwowość, gdy coś nie idzie zgodnie z planem i już macie wizerunek owłosionego człowieka łamiącego klawiaturę i rzucającego nią w monitor. A normalnie jestem siłą spokoju.
Ostatnio zresztą zrobiłem sobie małą przerwę przy Apokalipsie (muszę się zastanowić, co dalej z modelami) i zająłem się dalszą rozbudową mojego frameworka. Jako że nie chce już mi się siedzieć nad oknem windowsowym, shadery są dalej dla mnie niewiadomą (a przynajmniej na tą chwilę), to postanowiłem poszukać czegoś, czego jeszcze nie próbowałem. Oczywiście, podchody na niezawodnej stronie Ultimate Game Programming przyniosły oczekiwany skutek i dzięki temu od 3-4 dni siedzę nad błędami w kodzie. Ale jeśli się uda, to będzie kolejny ciekawy "ficzer" w frameworku.
Ale do rzeczy - analiza kodu przykładowego i jego "przepisanie" (z własnymi poprawkami i potrzebą automatyzacji np. renderingu) skłoniła mnie do refleksji nad rodzajami błędów, jakie mogą czekać na programistów.
1. Nieudana kompilacja - błędy składniowe
Szczerze mówiąc, jest to chyba najpiękniejszy błąd dla programisty. Tzw. "syntax error" często tyczy się braku średnika czy innych znaków, które nieopatrznie postawiliśmy w złych miejscach. I nawet, jeśli liczba ostrzeżeń tego rodzaju jest liczona w setkach, to poprawienie tego nie nastręcza wielkich trudności. Niestety - rzadko kiedy tylko takie bugi nam się przytrafiają...
2. Nieudana kompilacja - błędy dotyczące niewłaściwego używania elementów kodu
Te błędy już gorsze, choć nie sprawiają, że człowiek chce się rzucić z mostu. Najczęściej spotykane to nieodpowiednie odwołania do metod czy pól albo błędy rzutowania. O dziwo, te bugi wcale nie są trudne do zauważenia i zrozumienia. Problem może pojawić się w momencie, kiedy mamy naprawdę rozbudowany kod i jedna zmiana pociąga za sobą inne - tak chociażby bywa, gdy początkowo decydujemy się, żeby funkcja zwracała np. wartość typu float*, a potem jednak okazuje się, że nie ma zwracać i trzeba zmieniać i dostosowywać. A potem się okazuje z kolei, ze zwraca - ale nie kod, tylko właściciel, bo już mu się... Chce.
3. Nieudana kompilacja - nieudane linkowanie
Według mnie najgorszy typ błędów na etapie kompilacji. Kiedyś był dla mnie jeszcze straszniejszy, ale zweryfikowałem ten pogląd jak zacząłem regularnie spotykać się z "errorami" kolejnego rodzaju. Na czym zwykle polegają wywody linkera? Na dyrektywach include i tych dziwnych napisów z dwoma dwukropkami. Mówiąc jaśniej - należy uważać z umieszczaniem odwołań do odpowiednich plików nagłówkowych. Zwykle automatycznie umieszczałem je w pliku nagłówkowym klasy myśląc, iż plik źródłowy sam sobie pobierze odpowiednie headery. A tu g...uzik. W dodatku, często komunikaty linkera są bardzo zawiłe i nie zawsze trafiają w gusta kodera. Dlatego należy rozważnie operować makrami, a w ostateczności stosować coś takiego:
#ifndef _FILE_XYZ_H_ #include "xyz.h" #define _FILE_XYZ_H_#endifOdpowiednio użyte powinno pomóc - dzięki temu unikniemy sytuacji, gdy klasy na szczycie "drzewa" są znowu dołączane w głównym pliku. U mnie w kodzie ten problem jest chociażby z klasą CTextureManager, bo wiele klas chciałoby mieć własny kod odpowiedzialny za teksturowanie.
A jakie mogą być inne błędy ujawniające się tutaj? Wprawdzie mogą one też być wywołane wcześniej, ale najczęściej tutaj okazuje się, że dla definicji jakiejś metody klasy nie umieściliśmy np. CKlasa::Metoda. Jednak te bugi są stosunkowo łatwe do wytropienia.
4. Udana kompilacja, ale są jakieś przekłamania w aplikacji
Najgorsze błędy zaczynają się tutaj, choć ten rodzaj nie musi być jeszcze tak uciążliwy. Bywa jednak, że znalezienie błędu zajmuje sporo czasu i kosztuje wiele neurytów. Często sa to po prostu przeoczenia, a w szczególnych przypadkach niepoprawne rozumowanie pewnych elementów programu. Mogę podać przykład na podstawie pisania Apokalipsu - przeoczeniem były chociażby źle podane współrzędne wierzchołków ścian czy chociażby różne dziwne rzeczy związane z przechodzeniem do menu i z powrotem, a niepoprawnym rozumowaniem popisałem się przy implementacji otwierania drzwi - troszkę to wtedy trwało, zanim doszedłem co jest źle.
5. Udana kompilacja, ale podczas działania programu są jakieś komunikaty - choć aplikacja działa
To już gorsze rzeczy, bo dotyczą najczęściej typowych rzeczy dla języka - albo niepoprawne użycia wskaźników albo tzw. "heap corruption" czyli przekraczanie indeksów tablicy połączone z niepoprawnym zwolnieniem zasobów. Tu już trzeba się wgłębić w kod, a ja na razie doszedłem tylko do jednej rzeczy, o której wiedza czasami się przydaje - nie zawsze nawet najbardziej poprawne operowanie pamięcią za pomocą new i delete jest przyjmowane bez błędów. Dlatego często liczę się z "nieprofesjonalizmem" i stosuję zwykłą deklarację zmiennych - tyczy się to zwłaszcza tablic dynamicznych, a najczęściej std::vector (nie wiem, może po prostu ja nie umiem tego używać).
6. Udana kompilacja, ale program się nie chce uruchomić
Najbardziej znienawidzony chyba komunikat Windowsa - i zawsze to durne pytanie czy wysyłać raport czy też nie. No cóż - na tym etapie już trzeba mocno się napracować, bo błąd może się znajdować dosłownie wszędzie - najczęściej chyba w takich rzeczach jak WinAPI czy pobieranie rozszerzeń OpenGL. Ostatnio miałem też problem z Apokalipsem w trybie release - jak się okazało, nie pasowało mu ciągłe kasowanie ostatnich danych o ścianach kolizyjnych i dołączanie ich na nowo, dla aktualnego stanu obiektów ruchomych w grze. Ale z kolei, gdy zamienił mechanikę kasowania/dołączania na nadpisywanie, kod działał już poprawnie. Niestety, przy WinAPI czy obecną partią frameworka nie mam już tyle szczęścia.
Tak mniej więcej się to przedstawia. Z pewnością coś pominąłem albo nie opisałem z należytą dokładnością. Myślę jednak, że tego typu artykuł może podziałać relaksująco na początkujących, którzy np. denerwują się niezrozumiałymi komunikatami kompilatora. W jaki sposób? Zawsze może być przecież gorzej...
Pozdrawiam i dziękuję - SceNtriC.