Go dla początkujących - Część 8: Debugowanie aplikacji
Mateusz Kędziora

Go, język stworzony przez Google, zyskuje coraz większą popularność dzięki swojej prostocie, wydajności i wsparciu dla współbieżności. Pisanie w Go jest przyjemne, ale jak w każdym innym języku programowania, prędzej czy później napotkasz błędy w swoim kodzie. I wtedy zaczyna się debugowanie.
Ten artykuł jest skierowany do początkujących programistów Go, którzy chcą nauczyć się debugować swoje aplikacje. Przejdziemy przez podstawy debugowania, nauczymy się używać debuggera Delve (dlv), a także omówimy inne przydatne techniki, takie jak logowanie. Przygotuj się na praktyczną wiedzę, którą od razu wykorzystasz w swoich projektach.
Dlaczego Debugowanie Jest Ważne?
Wyobraź sobie, że piszesz program, który ma obliczyć średnią ocen studentów. Wszystko wydaje się logiczne, ale program zwraca nieprawidłowy wynik. Co robisz? Możesz zgadywać, przeglądać kod linia po linii, ale to zajmuje dużo czasu i frustruje.
Debugowanie to proces identyfikacji i eliminowania błędów w kodzie. Efektywne debugowanie pozwala:
- Szybko znaleźć źródło problemu: Zamiast spędzać godziny na zgadywaniu, debugger wskaże Ci konkretną linię kodu, która powoduje błąd.
- Zrozumieć, jak działa Twój kod: Obserwując wartości zmiennych i śledząc wykonanie programu, lepiej zrozumiesz, co się dzieje “pod maską”.
- Poprawić jakość kodu: Debugowanie pomaga nie tylko usunąć błędy, ale również zidentyfikować potencjalne problemy i zoptymalizować kod.
Wprowadzenie do Delve (dlv)
Delve (dlv) to popularny debugger dla Go. Pozwala na:
- Ustawianie breakpointów: Zatrzymywanie wykonania programu w określonych miejscach.
- Inspekcję zmiennych: Sprawdzanie wartości zmiennych w trakcie działania programu.
- Śledzenie wykonania programu: Przechodzenie przez kod linia po linii, aby zobaczyć, co się dzieje.
- Wywoływanie funkcji: Uruchamianie wybranych funkcji w trakcie debugowania.
Instalacja Delve:
Aby zainstalować Delve, użyj następującego polecenia:
go install github.com/go-delve/delve/cmd/dlv@latest
Upewnij się, że masz poprawnie skonfigurowane środowisko Go. Po instalacji, dlv
powinien być dostępny w Twojej ścieżce systemowej.
Pierwszy Program do Debugowania
Zacznijmy od prostego programu, który zawiera błąd. Użyjemy go, aby pokazać podstawowe funkcje debuggera Delve.
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5}
sum := calculateSum(numbers)
fmt.Println("Suma liczb:", sum)
}
func calculateSum(nums []int) int {
sum := 0
for i := 1; i <= len(nums); i++ { // Błąd: indeks zaczyna się od 1
sum += nums[i]
}
return sum
}
Opis kodu:
main
funkcja tworzy slice liczbnumbers
i wywołuje funkcjęcalculateSum
, aby obliczyć ich sumę.calculateSum
funkcja iteruje po slice liczb i dodaje każdą liczbę do zmiennejsum
. Krytyczny błąd: Pętlafor
zaczyna indeksowanie od 1, a nie od 0, co powoduje odwołanie się do elementu poza zakresem slice.
Zapisz ten kod jako main.go
.
Uruchamianie Debuggera Delve
Aby uruchomić debugger Delve, otwórz terminal w katalogu, w którym znajduje się plik main.go
, i wpisz następujące polecenie:
dlv debug
Debugger powinien się uruchomić i zatrzymać w funkcji main
. Zobaczysz coś takiego:
Type 'help' for list of commands.
(dlv)
Ustawianie Breakpointów
Breakpoint to punkt w kodzie, w którym debugger zatrzyma wykonanie programu. Umożliwia to sprawdzenie stanu programu w konkretnym miejscu.
Aby ustawić breakpoint w funkcji calculateSum
, użyj polecenia break
(lub jego skrótu b
):
(dlv) break main.calculateSum
Breakpoint 1 set at 0x11bf73a for main.calculateSum() ./main.go:10
Teraz uruchom program dalej, używając polecenia continue
(lub jego skrótu c
):
(dlv) continue
> main.calculateSum() ./main.go:10 (hits breakpoint) (PC: 0x11bf73a)
Program zatrzymał się wewnątrz funkcji calculateSum
, w miejscu, gdzie ustawiliśmy breakpoint.
Inspekcja Zmiennych
W debugerze możesz sprawdzić wartości zmiennych, aby zobaczyć, co się dzieje w programie. Aby sprawdzić wartość zmiennej i
, użyj polecenia print
(lub jego skrótu p
):
(dlv) print i
1
Podobnie, możesz sprawdzić wartość zmiennej sum
:
(dlv) print sum
0
Możesz także sprawdzić zawartość slice nums
:
(dlv) print nums
[]int {1, 2, 3, 4, 5}
Śledzenie Wykonania Programu (Stepping)
Aby przejść do następnej linii kodu, użyj polecenia next
(lub jego skrótu n
):
(dlv) next
> main.calculateSum() ./main.go:11 (PC: 0x11bf79a)
Teraz jesteśmy w linii sum += nums[i]
. Możemy sprawdzić, co się stanie, gdy ta linia zostanie wykonana. Ponownie użyj polecenia print
, aby sprawdzić wartości i
i sum
.
(dlv) print i
1
(dlv) print sum
0
(dlv) next
panic: runtime error: index out of range [1] with length 5
goroutine 1 [running]:
main.calculateSum(0xc00001a1e0?)
./main.go:11 +0xba
main.main()
./main.go:6 +0x44
Ojej! Program wygenerował błąd index out of range
. Teraz już wiemy, że problem leży w tym, że pętla for
zaczyna indeksowanie od 1, zamiast od 0. To dlatego próbowaliśmy odwołać się do elementu nums[5]
, który nie istnieje (slice ma indeksy od 0 do 4).
Naprawa Błędu
Aby naprawić błąd, zmień pętlę for
w funkcji calculateSum
na:
func calculateSum(nums []int) int {
sum := 0
for i := 0; i < len(nums); i++ { // Poprawka: indeks zaczyna się od 0
sum += nums[i]
}
return sum
}
Zapisz zmiany i uruchom program ponownie (bez debuggera). Teraz program powinien działać poprawnie i wyświetlić prawidłową sumę liczb:
Suma liczb: 15
Bardziej Zaawansowane Funkcje Delve
Delve oferuje wiele innych funkcji, które mogą być przydatne podczas debugowania:
step
(lubs
): Wchodzi do wywoływanej funkcji. Jeśli znajdujesz się na linii, która wywołuje inną funkcję,step
pozwoli Ci wejść do tej funkcji i śledzić jej wykonanie.step-out
(lubso
): Wychodzi z aktualnie wykonywanej funkcji. Kontynuuje wykonywanie programu, aż do powrotu z aktualnej funkcji.locals
: Wyświetla listę lokalnych zmiennych w aktualnie wykonywanej funkcji.args
: Wyświetla argumenty przekazane do aktualnie wykonywanej funkcji.breakpoints
: Wyświetla listę wszystkich ustawionych breakpointów.clear
: Usuwa breakpoint. Możesz usunąć breakpoint po numerze (clear 1
) lub po lokalizacji (clear main.calculateSum
).
Debugowanie Kodu Współbieżnego
Go słynie ze wsparcia dla współbieżności. Debugowanie kodu współbieżnego może być trudniejsze, ale Delve oferuje narzędzia, które to ułatwiają:
goroutines
: Wyświetla listę wszystkich aktywnych goroutines.goroutine <id>
: Przełącza do określonej goroutine. Możesz użyć tego polecenia, aby śledzić wykonanie konkretnej goroutine.- Breakpointy warunkowe: Możesz ustawić breakpoint, który zostanie aktywowany tylko wtedy, gdy spełniony jest określony warunek. Na przykład, możesz ustawić breakpoint w goroutine tylko wtedy, gdy wartość zmiennej spełnia określone kryterium.
Przykład breakpointu warunkowego:
(dlv) break main.myGoroutine if myVar > 10
Ten breakpoint zatrzyma program w funkcji myGoroutine
tylko wtedy, gdy wartość zmiennej myVar
jest większa niż 10.
Logowanie: Alternatywna Metoda Debugowania
Oprócz debuggera, logowanie jest bardzo przydatną techniką debugowania. Polega na wstawianiu do kodu instrukcji, które wypisują informacje o stanie programu.
Przykład:
package main
import (
"fmt"
"log"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
log.Println("Początek programu") // Logowanie
sum := calculateSum(numbers)
fmt.Println("Suma liczb:", sum)
log.Println("Koniec programu") // Logowanie
}
func calculateSum(nums []int) int {
sum := 0
log.Println("Początek calculateSum") // Logowanie
for i := 0; i < len(nums); i++ {
log.Printf("i: %d, nums[i]: %d, sum: %d\n", i, nums[i], sum) // Logowanie
sum += nums[i]
}
log.Println("Koniec calculateSum") // Logowanie
return sum
}
Opis kodu:
- Używamy pakietu
log
do wypisywania komunikatów do konsoli. log.Println
wypisuje komunikat z nową linią na końcu.log.Printf
wypisuje sformatowany komunikat (podobnie dofmt.Printf
).
Logowanie jest szczególnie przydatne w sytuacjach, gdy nie możesz użyć debuggera (np. w środowisku produkcyjnym) lub gdy debugujesz kod współbieżny. Można logować do pliku, zamiast do konsoli. To pozwala analizować działanie programu po jego uruchomieniu.
Kiedy Używać Debuggera, a Kiedy Logowania?
- Debugger: Idealny do interaktywnego debugowania, gdy chcesz dokładnie śledzić wykonanie programu i sprawdzić wartości zmiennych w czasie rzeczywistym.
- Logowanie: Przydatne do debugowania w środowiskach, gdzie debugger nie jest dostępny, do monitorowania działania programu w środowisku produkcyjnym i do analizy problemów, które występują sporadycznie.
Często najlepszym rozwiązaniem jest połączenie obu technik. Używaj debuggera do szybkiego znajdowania i naprawiania prostych błędów, a logowania do monitorowania działania programu i diagnozowania trudniejszych problemów.
Praca Domowa
- Stwórz program, który oblicza średnią arytmetyczną liczb w slice. Celowo wprowadź do niego błąd (np. dzielenie przez zero, indeksowanie poza zakresem).
- Użyj debuggera Delve, aby znaleźć i naprawić błąd. Ustaw breakpointy, sprawdzaj wartości zmiennych i śledź wykonanie programu.
- Dodaj logowanie do programu. Wypisz informacje o wartościach zmiennych i kluczowych momentach w działaniu programu.
- Porównaj, jak szybko udało Ci się znaleźć błąd, używając debuggera i logowania.
- Zmień program, aby uruchamiał obliczanie średniej w osobnej goroutine. Spróbuj zdebugować program, używając poleceń
goroutines
igoroutine <id>
.
Podsumowanie
W tym artykule omówiliśmy podstawy debugowania aplikacji Go. Nauczyliśmy się używać debuggera Delve, ustawiać breakpointy, inspekcjonować zmienne i śledzić wykonanie programu. Pokazaliśmy również, jak używać logowania jako alternatywnej metody debugowania.
Pamiętaj, że debugowanie to umiejętność, którą rozwija się z praktyką. Im więcej debugujesz, tym szybciej będziesz znajdować i naprawiać błędy w swoim kodzie. Zachęcam Cię do eksperymentowania z debuggerem Delve i logowaniem w swoich projektach.
Przydatne Linki
- Oficjalna dokumentacja języka Go: https://go.dev/
- Dokumentacja Delve: https://github.com/go-delve/delve
- Tutorial Go (A Tour of Go): https://go.dev/tour/welcome/1
- Effective Go: https://go.dev/doc/effective_go
Powodzenia w debugowaniu! Nie zapomnij sprawdzić pozostałych wpisów na blogu, aby poszerzyć swoją wiedzę na temat Go. Następny artykuł może dotyczyć testowania jednostkowego w Go, co jest kolejnym ważnym elementem tworzenia niezawodnych aplikacji.
Polecane artykuły
Docker vs Kubernetes: Który dla Ciebie w 2025?
Docker i Kubernetes objaśnione! Która technologia lepsza dla początkujących w 2025? Porównanie, przykłady i przyszłość.
Mateusz Kędziora
DevOps: Automatyzacja zadań sysadmina dla programistów
Zautomatyzuj pracę sysadmina w środowisku DevOps! Praktyczne przykłady, skrypty, Ansible, Terraform, Prometheus i Grafana.
Mateusz Kędziora
Automatyzacja Linux/macOS z Bash: Praktyczny Przewodnik
Zacznij automatyzować system Linux/macOS z Bash! Dowiedz się, czym jest Bash, jak pisać skrypty i używać podstawowych komend.
Mateusz Kędziora