Pracując z osobami zaczynającymi swoją przygodę z programowaniem w SQL czy pythonie zauważyłam pewien powtarzający się błąd, który zainspirował mnie do powstania tego posta. Post spróbuje odpowiedzieć na to jak podejść do pisania kodu i jak nauczyć się naprawiać błędy.
Do kogo jest skierowany ten artykuł? Artykuł jest skierowany do osób początkujących, które mają zero lub mało doświadczenia komercyjnego.
Czego ten artykuł nie zawiera? Celem artykułu nie jest omówienie profesjonalnych narzędzi do debagowania, lecz proste wytłumaczenie jak w ogóle podejść do tego problemu.
Dlaczego pisanie kodu nie wygląda jak w filmach i co robaki mają z tym wspólnego?
Kojarzycie tę sylwetkę programisty w filmach, kiedy siedzi zakapturzony wpatrując się w ekran laptopa z którego pochodzi jedyne światło w ciemnym pomieszczeniu i ekspresowo pisze kod tak, jakby pisał wiadomość do kolegi? No właśnie. Tak się kodu nie pisze.
Być może dla niektórych ta kwestia jest oczywista, ale chcę, żeby to wybrzmiało na początku: w “pisaniu” kodu jest więcej zastanawiania się, dlaczego coś nie działa od rzeczywistego “pisania”. Proces ten jest tak powszechny, że nawet ma swoją nazwę: nazywa się to debugowanie (wym. debagowanie), czyli naprawianie bugów, czyli błędów. Bug z języka angielskiego to nie tylko „robak”, ale także:
an unexpected defect, fault, flaw, or imperfection
Mówimy, że nasz kod ma bugi kiedy nie działa tak jak powinien, czyli zawiera w sobie błędy. Debugowanie to proces szukania tych błędów i ich naprawy.
Błędy w kodzie są normalne
Skoro odkryliśmy już prawdę numer jeden, że programowanie to w dużej mierze debugowanie, czyli naprawianie niedziałającego kodu, to odkryjmy prawdę numer dwa: błędy są nieuniknione.
Jeżeli jesteście na początku nauki programowania i macie myśli typu “gdyby nie ten jeden głupi błąd to wszystko by działało jak należy…” to mam dla was dobrą i złą wiadomość. Zła wiadomość jest taka, że programowanie polega na rozwiązywaniu tego “jednego głupiego błędu”. Programowanie to radzenie sobie z błędami w kodzie. Wiem, nie to chcieliście usłyszeć. Ale tutaj przychodzi też dobra wiadomość: można się nauczyć jak rozwiązywać te błędy.
Początkowe nerwowe reagowanie na błędy jest standardem. Też tak reagowałam. Zastanawiałam się czemu znowu coś mi nie działa. Ile jeszcze razy napotkam na jakiś problem. Czemu rozwiązanie błędu w postaci kilku linijek kodu zajmuje mi kilka godzin pracy. Już tak nie mam, ponieważ z czasem nauczyłam się, że kolejny błąd w kodzie to nie jest problem dotykający mnie osobiście, ale część pracy, którą trzeba wykonać. Jesteśmy w stanie nauczyć się jak podchodzić do debugowania bez stresu i jak efektywnie rozwiązywać bugi, zamiast bezmyślnie panikować.
Artykuł skupi się na tym jak podejść do pisania kodu, aby zminimalizować ilość błędów, oraz jak naprawiać błędy, które już istnieją w kodzie.
Co jest kluczowe w naprawianiu błędu?
Być może dwa punkty, które zaraz przeczytasz wydadzą ci się najgłupszymi rzeczami jakie można napisać, ale mimo wszystko zostań ze mną – później wytłumaczę o co mi chodzi. Póki co muszę to jednak napisać.
Aby naprawić błąd trzeba:
- Zlokalizować gdzie on występuje w kodzie.
- Naprawić go.
Jak piszesz kod a jak powinnaś/powinieneś
Zwizualizujmy problem na prostym przykładzie.
Weźmy sobie prosty przypadek zapytania SQL do napisania: wyciągnij średnią sumę transakcji na kraj. Aby napisać to zapytanie musimy tylko połączyć dwie tabele przez JOIN, pogrupować po krajach i wyciągnąć średnią sumę. Piszesz prosty kod:
SELECT country, sum(amount)
FROM transaction t
JOIN users u ON t.user_id = u.user_id
GROUP country
Uruchamiasz go. Kod nie działa. Co teraz?
Patrzysz na kod i szukasz błędu.
Nie widzisz błędu.
Nie rozumiesz komunikatu z bazy.
Nie wiesz co nie działa.
Co dalej?
Jeżeli chcesz wykonywać kod w tym samym czasie co ja (aczkolwiek nie jest to wymagane do zrozumienia artykułu) to zapraszam:
- Otwórz http://sqlfiddle.com/#!17
- Skopiuj kod z https://github.com/kdyl/crappydata/blob/main/db.sql
- Wklej kod w lewym panelu na stronie
- Kliknij “Build schema”
- Jeżeli pojawił się zielony napis “Schema ready” to baza danych jest gotowa. Możesz pisać queries po prawej stronie i uruchamiać je wciskając “Run SQL”.
Problemem nie jest błąd, który ci wyskoczył, tylko sposób w jaki piszesz kod.
Częstym błędem osób początkujących jest pisanie za dużej ilości kodu naraz. Jest to niepożądane, szczególnie na początku nauki programowania, kiedy pisanie idzie ci gorzej niż doświadczonemu zawodnikowi.
Wbrew pozorom mogła/eś popełnić masę błędów nawet przy tak “prostym” zadaniu. Wpisać złą nazwę tabeli, wpisać złą nazwę kolumny, użyć złej kolumny w jonie, źle wpisać wyrażenia SQL, wpisać je w złej kolejności itd. Teraz musisz zastanawiać się co poszło nie tak, a dopiero po znalezieniu problemu możesz go naprawić. Wróćmy do tych dwóch głupich punktów: 1. znajdź problem; 2. napraw problem; i właśnie teraz wytłumaczmy, dlaczego je w ogóle napisałam na początku.
Przy pisaniu kodu od zera możemy praktycznie wyeliminować punkt numer jeden. Zamiast pisać od razu cały kod możemy rozbić go na pojedyncze operacje i przechodzić do kolejnej dopiero jak upewnimy się, że poprzednia działa poprawnie. Co to zmienia? Widzimy od razu gdzie kod nie działa. Nie musimy spędzać czasu na szukaniu tego błędu. Piszemy fragment kodu -> upewniamy się, że działa -> piszemy kolejny fragment kodu itd.
Rozbijmy nasz przykład na takie fragmenty:
- Piszemy kod, który wyciąga dane z jednej tabeli. Upewniamy się dzięki temu, że połączenie z bazą danych działa, że tabela jest poprawnie nazwana w query. Spróbujmy:
SELECT *
FROM transaction
LIMIT 10
Dostajemy błąd: ERROR: relation "transaction" does not exist Position: 15
. Co to znaczy? Nie ma czegoś takiego jak transaction
. Szybki rzut oka na definicję tabeli i widzimy, że nazywa się ona transactions
nie transaction
. Poprawiamy:
SELECT *
FROM transactions
LIMIT 10
Działa! Lecimy dalej.
2. Łączymy tabele:
SELECT *
FROM transactions t
JOIN users u ON t.user_id = u.user_id
LIMIT 10
Kolejny błąd, który jest podpowiadany przez komunikat błędu ERROR: column u.user_id does not exist.
Rzeczywiście, kolumna w tabeli users nazywa się id
, a nie user_id
. Naprawiamy:
SELECT *
FROM transactions t
JOIN users u ON t.user_id = u.id
LIMIT 10
Działa, lecimy dalej.
3. Agregujemy dane:
SELECT country, sum(amount)
FROM transactions t
JOIN users u ON t.user_id = u.id
GROUP country
Znowu jakiś problem: syntax error at or near "country"
. Okazuje się, że napisaliśmy GROUP
zamiast GROUP BY
. Naprawiamy:
SELECT country, sum(amount)
FROM transactions t
JOIN users u ON t.user_id = u.id
GROUP BY country
Problem rozwiązany, query napisana, zadanie zakończone.
Podsumowując: piszemy mały fragment kodu -> sprawdzamy go -> przechodzimy dalej
Ta zasada się sprawdzi cokolwiek piszecie w jakimkolwiek języku i jakakolwiek skomplikowana jest to rzecz do napisania. Takie sekwencyjne podejście sprawdzi się nawet w przypadku zaawansowanych analiz czy tworzeniu modelu ML, gdzie np. zamiast wrzucać dane od razu do modelu można spróbować “ręcznie” przeanalizować jakie cechy mają wpływ na predykcję.
Jak naprawiać błędy?
Omówiliśmy sobie jak podchodzić do pisania kodu tak, aby od razu znajdywać fragment, który nie działa. Drugim krokiem debugowania jest naprawa błędu. Jak postępować w sytuacji gdy wiemy już jaki fragment nie działa, ale nie wiemy jeszcze jak go naprawić?
1. Spróbujemy przeczytać błąd i go zrozumieć. Serio. Czasami jest jak byk napisane co jest nie tak. Przykład z góry to: column u.user_id does not exist
. Napisane jest, że kolumna nie istnieje oraz jest podana jej nazwa u.user_id
. Problem podany na tacy.
2. Gdy komunikat błędu nie jest do końca jasny lub nie potrafimy go zrozumieć to postarajmy się chociaż wyłapać z czym mniej więcej może być problem. Może to być linijka kodu albo nazwa zmiennej z którą kod sobie nie radzi. Przykład z góry to syntax error at or near "country"
. Chociaż komunikat błędu nie mówi jasno co jest błędem, to wskazuje, że problem pojawia się przy słowie country
. W takiej sytuacji powinniśmy skupić się na kodzie, który jest w pobliżu.
3. Gdy dalej nie wiemy o co chodzi to… googlujemy! Kopiujemy błąd do wyszukiwarki googla (usuwając przy okazji nazwy własne w komunikacie takie jak nazwa tabeli) i szukamy odpowiedzi. Na podstawie błędu: ERROR: relation "transaction" does not exist Position: 15
, usuńmy nazwy własne i wrzućmy do googla ERROR: relation does not exist
.
Pierwszy wynik wyszukiwania odsyła nas do stackoverlow, gdzie najlepiej głosowaną odpowiedzią jest:
Odpowiedź jasno mówi o tym, że you’re not referencing the table name correctly – masz błąd w nazwie tabeli.
Naprawianie błędów na początku jest trudne, ale warto samemu próbować to robić, zamiast biec od razu po pomoc osoby doświadczonej. W ten sposób budujemy w sobie nawyk radzenia sobie z debugowaniem. Oczywiście kiedy mimo chęci nadal nie potrafimy rozwiązać problemu to nie ma nic złego w spytaniu o pomoc doświadczonej osoby.
Co w sytuacji gdy już napisany kod przestał działać?
Podczas pracy często spotkamy się z sytuacją, gdy już działający kod nagle przestaje działać. Wtedy nie jesteśmy w stanie przeskoczyć pierwszego punktu i zaczynamy debugowanie od szukania bugu. Gdy ja spotykam się z takim przypadkiem to moje pierwsze pytanie brzmi:
Czy kod nie zadziałał dla jakiegoś jednego szczególnego przypadku? Czy w ogóle nie działa?
Jeżeli jest to pojedynczy przypadek, to czym on się charakteryzuje? Czym się różni od reszty? Często jest to jakiś nowy typ danych, albo jakiś przypadek brzegowy (ang. edge case), którego nie przewidzieliśmy. Znalezienie tego przypadku pozwoli nam na odtworzenie błędu i łatwiejsze znalezienie jego źródła w kodzie.
Logika działania w takiej sytuacji nie zmienia się szczególnie do tego co pisałam wyżej – rozbijajmy istniejący już kod na poszczególne części i po kolei wywołujemy je, aby zlokalizować, kiedy kod przestaje działać. W przypadku pythona możemy wywoływać linijkę po linijce lub funkcję po funkcji, a w przypadku SQL możemy np. wywoływać po kolei każde CTE.
Profesjonalne debugowanie
Warto wspomnieć, że niektóre programy, np. pycharm mają wbudowany tryb debugowania. Omówienie profesjonalnych narzędzi debugowania wykracza poza cel tego artykułu, ale zasada działania jest podobna: kod jest zatrzymywany w określonym miejscu, aby sprawdzać po kolei do czego prowadzi jego poszczególny fragment i spróbować znaleźć miejsce, w którym nie działa tak jak powinien.
Ukryte bugi
Artykuł skupiał się na oczywistych błędach w kodzie, czyli na sytuacjach, kiedy kod nie działa, ponieważ „wywala błąd”. Warto zwrócić uwagę, że bugi mają także inne oblicza: kod może z pozoru działać poprawnie, ale zwracać nam nie to, co myślimy, że zwraca. W takiej sytuacji nie szukamy fragmentu kodu, który zwraca błąd, ale kodu, który działa w niezamierzony sposób.
W takiej sytuacji schemat działania… znowu jest podobny. Rozbieramy kod na części pierwsze, tym razem jednak nie szukając błędu, a raczej monitorując co kod zwraca i jak się zachowuje.
Podsumowując
- Pisanie kodu nie wygląda tak płynnie jak na filmach, a jego znaczą częścią jest debugowanie, czyli naprawianie błędów.
- Częstym błędem osób początkujących jest pisanie zbyt długiego kodu naraz.
- Lepszym podejściem do pisania kodu od zera jest podzielenie kodu na mniejsze części i przechodzenie do kolejnego fragmentu dopiero w momencie, gdy poprzedni działa poprawnie.
- Znajdowanie bugów w działającym kodzie lub używanie profesjonalnych narzędzi ma podobną zasadę działania i opiera się rozbieraniu kodu na części pierwsze.
- Debugowanie to nie tylko naprawa zwracanych błędów, ale także szukanie złej logiki w pozornie działającym kodzie.
Keywords: debugowanie, co oznacza debugowanie, czym jest debugowanie, co to debugowanie, debugowanie definicja, tryb debugowania co to jest, debugowanie co to, debugowanie programu, co debugowanie, co oznacza debugowanie, jak debugować, jak włączyć debugowanie, debugowanie python, debugowanie SQL, debugowanie intellij, debugowanie pycharm, debugging po polsku, jak pisać kod, jak zacząć programować, jak zacząć programować python, jak zacząć programować SQL, nauka programowania, nauka pisania kodu, jak zacząć programować, dla kogo programowanie, jak się programuje, gdzie programować, kto to programista, co można programować, nauka programowania od podstaw, nauka programowania od zera, jak nauczyć się programowania, jak rozpocząć naukę programowania, nauka programowania jak zacząć, gdzie nauczyć się programowania, gdzie nauczyć się programować, który kurs programowania, co to znaczy nauka programowania, co to jest nauka programowania, co daje nauka programowania, nauka programowania w domu, nauka programowania za darmo, nauka programowania online,
W jakimkolwiek języku pisze zawsze stosuję „debugowanie przez printowanie” czyli pisze fragment i wywalam na ekran żeby zobaczyć czy kod zwraca to co powinien. Bardzo dobry nawyk. Polecam !
Mój kolega nazywa to (trochę złośliwie) Print Driven Testing 🙂 Ale nie przeszkadza mu to robić tego dokładnie w taki sam sposób…
Szczególnie w Pythonie szybkie wyrzucenie wartości zmiennej na ekran (nie mówiąc już o Pandas w Jupyter Notebook…) cholernie pomaga zorientować się, co się właśnie wydarzyło w kodzie. Pisząc kod SQL już tak dobrze nie mamy 🙂
Piękna nazwa 🙂
Wartościowy artykuł 🙂
Ha, GROUP zamiast GROUP BY zauważyłem od razu, no ale dobra, nie jestem początkujący w SQL-u 🙂
Natomiast błędy w nazwach tabel czy kolumn to oczywiście błąd nie do wychwycenia, dopóki nie puścimy zapytania na bazie…
A poza tym dwie uwagi:
1) artykuł nie tylko dla początkujących – mam 20+ lat doświadczenia z SQL-em, a dalej bardziej skomplikowane rzeczy piszę po kawałku (np. zapytania CTE, funkcje czy wielokrotne złączenia). Może się wydawać, że to marnowanie czasu, ale wiele razy okazało się, że pisząc duży fragment kodu naraz (i szukając – nieuniknionych! – błędów) robi się to w sumie o wiele dłużej…
2) porada warta stosowania i artykuł bardzo wartościowy. Zwłaszcza że początkującemu wydaje się zawsze, że koledzy obok to śmigają i piszą z pamięci kod przez 8 godzin i to bez błędów. A to, delikatnie mówiąc, bzdurka – czy ma się miesiąc, rok, czy dwadzieścia lat doświadczenia, zawsze popełnia się błędy i często sięga do dokumentacji. Z pamięci pisze się fragmenty często powtarzane, które są podstawą – SELECT … FROM … ORDER BY … GROUP BY …. ale już sięgając np. do funkcji wbudowanych często sprawdza się dokumentację…
Artykuł dla początkujących, ponieważ wydaje mi się, że osoby początkujące nie mają pojęcia, że tak się pracuje, a tak jak piszesz dla osoby doświadczonej jest to oczywiste 😉
W sumie racja, w tym sensie artykuł dla początkujących, że bardziej doświadczeni sami na to dawno wpadli 🙂