Die häufigsten Anfängerfehler beim Programmieren haben wenig mit Syntax zu tun. Wer neu anfängt, überspringt die Fehlermeldung, schreibt fünfzig Zeilen, bevor er auch nur eine davon ausführt, ignoriert den Fall ungültiger Eingaben und rät bei Bugs, statt sich die Werte anzusehen. Das sind Gewohnheiten, und Gewohnheiten lassen sich früh korrigieren.
Dieser Artikel behandelt 13 davon. Zu jedem gibt es, wo Code hilft, ein kurzes Go-Beispiel und die Lösung. Go eignet sich hier gut als Lehrsprache, weil Compiler und Tools mehrere dieser Fehler von vornherein ablehnen. Der Abschnitt gegen Ende behandelt diese Fälle.
Kurzfassung
- Lies die ganze Fehlermeldung. Sie nennt dir die Datei, die Zeile und was schiefgelaufen ist.
- Führe deinen Code alle paar Zeilen aus. In kleinen Schritten findest du Bugs leicht.
- Füge keinen Code ein, auch keine KI-Ausgabe, den du nicht Zeile für Zeile erklären kannst.
- Behandle den Fehlerfall. Prüfe in Go jedes
err, bevor du den Wert verwendest, der damit zurückkam. - Benenne Dinge nach dem, was sie enthalten, halte Funktionen klein und teste leere Eingaben und Grenzfälle.
- Nutze Git ab dem ersten Tag, lege Secrets in Umgebungsvariablen ab und schreib einen Test, bevor du glaubst, einen zu brauchen.
- Debugge, indem du Werte ausgibst oder mit Delve durch den Code gehst, nicht indem du wahllos Code änderst.
- Bring es erst zum Laufen, bevor du es abstrahierst oder schneller machst. Bau danach etwas, das kein Tutorial ist.
Inhaltsverzeichnis
- 1. Die Fehlermeldung nicht lesen
- 2. Viel Code schreiben, ohne ihn zwischendurch auszuführen
- 3. Code kopieren, den du nicht erklären kannst
- 4. Nur den Happy Path programmieren
- 5. Vage Namen
- 6. Eine riesige Funktion, die alles macht
- 7. Off-by-one-Fehler und leere Eingaben
- 8. Versionskontrolle nicht vom ersten Tag an nutzen
- 9. Konfiguration und Secrets hardcoden
- 10. Nie einen Test schreiben
- 11. Debuggen durch Raten
- 12. Abstrahieren oder optimieren, bevor es funktioniert
- 13. In Tutorials hängen bleiben und ohne Repro um Hilfe fragen
- Welche dieser Fehler fängt Go für dich ab?
1. Die Fehlermeldung nicht lesen
Anfänger sehen oft roten Text und springen sofort zurück in den Code, um irgendetwas zu ändern. Dabei ist die Fehlermeldung das Nützlichste auf dem Bildschirm. Meist nennt sie die Datei, die Zeile, die Spalte und was schiefgelaufen ist.
Hier ist ein kleiner Config-Loader für einen Server, der nicht kompiliert:
example.gogopackage main import ( "fmt" "os" ) func main() { port := os.Getenv("PORT") fmt.Println("starting server") }
example.texttext./main.go:9:2: declared and not used: port
Lies sie von links nach rechts. main.go ist die Datei, 9 die Zeile, 2 die Spalte, und der Rest sagt, dass die Variable port nie verwendet wird. Verwende sie oder lösch sie. Sonst ist nichts an dem Programm falsch.
Laufzeitfehler funktionieren genauso. Wenn ein Go-Programm in einen Panic läuft, listet der Stack Trace die Funktion und die Zeile auf, in der es passiert ist, mit dem jüngsten Aufruf zuerst. Such die erste Zeile, die auf eine deiner eigenen Dateien zeigt, und fang dort an.
Wenn dir Fehlermeldungen kryptisch vorkommen, liegt das nicht nur an dir. Eine ITiCSE-Arbeitsgruppe hat 2019 die Forschung dazu ausgewertet und kam zu dem Schluss, dass Compiler-Fehlermeldungen Anfängern „erhebliche Schwierigkeiten bereiten“ (Becker et al., 2019). Sie langsam und wörtlich zu lesen ist eine Fähigkeit, und die wird mit Übung schnell besser.
2. Viel Code schreiben, ohne ihn zwischendurch auszuführen
Ein ganzes Programm zu schreiben und es dann zum ersten Mal auszuführen, fühlt sich effizient an. Es macht die Fehlersuche aber teuer. Wenn 80 neue Zeilen scheitern, kann der Bug überall darin stecken. Wenn 5 neue Zeilen scheitern, steckt er in diesen 5.
Die Lösung ist eine kurze Schleife. Schreib ein paar Zeilen, führ sie aus, lies die Ausgabe, wiederhole das. Gib Zwischenwerte aus, während du baust, und lösch die Ausgaben, sobald das Teilstück funktioniert. Go kompiliert schnell genug, dass dich go run . nach jeder kleinen Änderung ein, zwei Sekunden kostet.
3. Code kopieren, den du nicht erklären kannst
Von Stack Overflow oder einem KI-Assistenten zu kopieren ist in Ordnung. Der Fehler ist, Code einzufügen, den du nicht erklären kannst. Wenn er kaputtgeht, und das wird er, weißt du nicht, wo du anfangen sollst. Außerdem überspringst du genau den Teil, in dem du lernst.
Stell dir eine Regel auf. Lies vor dem Einfügen jede Zeile und sag, was sie tut. Wenn du das nicht kannst, lass dir die Zeile von der KI erklären oder schreib sie anhand der Erklärung selbst neu. Die Regel ist heute wichtiger, weil KI für die meisten zum Lernen dazugehört. Unter Menschen, die programmieren lernen, nutzen 73,3 % KI-Tools oder planen es, und 39,5 % nutzen sie täglich (Stack Overflow Developer Survey, 2025). Die ganze Argumentation findest du in solltest du noch Code von Hand schreiben.
4. Nur den Happy Path programmieren
Anfängercode geht meist davon aus, dass die Datei existiert, das Netzwerk läuft und der Nutzer eine Zahl eingegeben hat. Echte Eingaben sind chaotischer. Dieses Programm liest eine Portnummer ein und ignoriert den Fehler:
example.gogopackage main import ( "fmt" "strconv" ) func main() { input := "80a0" port, _ := strconv.Atoi(input) fmt.Println("listening on port", port) }
example.texttextlistening on port 0
Aus dem Tippfehler wurde ohne jede Warnung Port 0. Go gibt Fehler als ganz normale Werte zurück. Die Lösung ist also, sie zu prüfen und zu sagen, was fehlgeschlagen ist:
example.gogopackage main import ( "fmt" "os" "strconv" ) func parsePort(raw string) (int, error) { port, err := strconv.Atoi(raw) if err != nil { return 0, fmt.Errorf("invalid port %q: %w", raw, err) } if port < 1 || port > 65535 { return 0, fmt.Errorf("port %d out of range 1-65535", port) } return port, nil } func main() { port, err := parsePort("80a0") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Println("listening on port", port) }
example.texttextinvalid port "80a0": strconv.Atoi: parsing "80a0": invalid syntax exit status 1
Frag dich bei jeder Eingabe, was passiert, wenn sie fehlt, leer ist, den falschen Typ hat oder zu groß ist. Jede Antwort wird zu einer Prüfung in deinem Code.
5. Vage Namen
data, tmp, res, x2 und thing sagen dem nächsten Leser nichts, und in zwei Wochen bist du selbst dieser Leser. Ein Name sollte sagen, was der Wert enthält, und zwar in der Sprache des Problems.
example.gogo// Hard to follow for _, d := range data { if d.s == 500 { tmp++ } } // Clear for _, req := range requests { if req.Status == 500 { serverErrors++ } }
In Go gilt die Konvention: kurze Namen für kleine Scopes, längere Namen für größere. i ist für eine dreizeilige Schleife völlig in Ordnung. Eine Variable auf Paketebene namens c ist es nicht. Wenn dir für etwas kein guter Name einfällt, macht es oft zwei Dinge.
6. Eine riesige Funktion, die alles macht
Eine 200 Zeilen lange main, die eine Datei liest, sie parst, Summen berechnet und einen Bericht ausgibt, ist schwer zu testen und noch schwerer zu ändern. Teil sie nach Aufgaben auf. Hier ist ein kleines CLI, das HTTP-Statuscodes in einem Access-Log zählt:
example.gogopackage main import ( "bufio" "fmt" "io" "maps" "os" "slices" "strings" ) func main() { if len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "usage: logstats <access.log>") os.Exit(2) } f, err := os.Open(os.Args[1]) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer f.Close() counts, err := countStatusCodes(f) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } printReport(os.Stdout, counts) } // countStatusCodes reads access log lines and counts the status code, // which is the last field on each line. func countStatusCodes(r io.Reader) (map[string]int, error) { counts := make(map[string]int) sc := bufio.NewScanner(r) for sc.Scan() { fields := strings.Fields(sc.Text()) if len(fields) == 0 { continue } counts[fields[len(fields)-1]]++ } return counts, sc.Err() } func printReport(w io.Writer, counts map[string]int) { for _, code := range slices.Sorted(maps.Keys(counts)) { fmt.Fprintf(w, "%s %d\n", code, counts[code]) } }
example.texttext200 2 404 1 500 1
main verdrahtet jetzt nur noch die Teile miteinander. countStatusCodes nimmt einen io.Reader statt eines Dateinamens, sodass ein Test ihm einen strings.Reader übergeben kann, ohne die Festplatte anzufassen. printReport nimmt aus demselben Grund einen io.Writer. Jede Funktion passt auf einen Bildschirm und macht genau eine Sache.
7. Off-by-one-Fehler und leere Eingaben
Schleifen, die einen Schritt zu weit laufen, sind ein Klassiker. Diese hier parst eine CSV-Zeile und verwendet <=, wo sie < braucht:
example.gogopackage main import ( "fmt" "strings" ) func main() { fields := strings.Split("alice,admin,active", ",") for i := 0; i <= len(fields); i++ { fmt.Println(fields[i]) } }
example.texttextalice admin active panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.main() /home/you/csvrow/main.go:11 +0xc0 exit status 2
Der Panic sagt genau, was passiert ist. In Zeile 11 wurde Index 3 auf einen Slice der Länge 3 angewendet, dessen letzter gültiger Index 2 ist. In Go ist die einfachste Lösung, den Index gar nicht selbst zu verwalten: for _, field := range fields kann nicht über das Ende hinauslaufen.
Der unauffälligere Verwandte sind leere Eingaben. Diese Funktion berechnet den Durchschnitt von Request-Latenzen:
example.gogopackage main import "fmt" func averageLatency(ms []float64) float64 { var total float64 for _, v := range ms { total += v } return total / float64(len(ms)) } func main() { fmt.Println(averageLatency([]float64{120, 80, 100})) fmt.Println(averageLatency(nil)) }
example.texttext100 NaN
Keine Requests bedeutet eine Division durch null. Bei Floats ergibt das NaN, bei Integern einen Panic. Frag dich immer, was deine Funktion mit null Elementen, einem Element und der Maximalzahl macht. Diese drei Fälle fangen viele Grenzfall-Bugs ab.
8. Versionskontrolle nicht vom ersten Tag an nutzen
Viele Anfänger schieben Git auf, bis sie ein „richtiges“ Projekt haben. Dann machen sie etwas kaputt, das vor einer Stunde noch lief, und haben keinen Weg zurück. Git ist der Rückgängig-Knopf für dein ganzes Projekt, und der Einstieg kostet fast nichts:
example.bashbashgit init git add . git commit -m "Parse port from PORT env var"
Committe jedes Mal, wenn etwas funktioniert, mit einer Nachricht, die sagt, was sich geändert hat. Wenn du etwas kaputtmachst, zeigt dir git diff, was du seit dem letzten funktionierenden Stand geändert hast. Push auf GitHub oder einen anderen Host, dann hast du zusätzlich ein Backup und ein Portfolio.
9. Konfiguration und Secrets hardcoden
Ein Datenbankpasswort oder einen API-Key direkt in den Quellcode zu schreiben, funktioniert so lange, bis du ihn in ein öffentliches Repo pushst. GitGuardian hat 2025 28,65 Millionen neue hartcodierte Secrets in öffentlichen GitHub-Commits gezählt, 34 % mehr als im Vorjahr (GitGuardian, 2026). Ports, URLs und Zugangsdaten unterscheiden sich außerdem zwischen deinem Laptop und der Produktion. Wer sie hardcodet, muss für ein Deployment den Code ändern.
Lies sie stattdessen aus der Umgebung:
example.gogopackage main import ( "cmp" "fmt" "os" ) func main() { addr := ":" + cmp.Or(os.Getenv("PORT"), "8080") apiKey := os.Getenv("PAYMENTS_API_KEY") if apiKey == "" { fmt.Fprintln(os.Stderr, "PAYMENTS_API_KEY is not set") os.Exit(1) } fmt.Println("listening on", addr) }
cmp.Or gibt den ersten nicht leeren Wert zurück, also bekommt PORT einen sinnvollen Standardwert. Der API-Key bekommt keinen Standardwert, und ohne ihn verweigert das Programm den Start. Wenn du lokale Werte in einer .env-Datei hältst, trag sie vor deinem ersten Commit in .gitignore ein.
10. Nie einen Test schreiben
Anfänger testen, indem sie das Programm ausführen und sich die Ausgabe ansehen. Das funktioniert einmal. Es sagt dir aber nicht, wann eine spätere Änderung etwas kaputtmacht, das vorher funktioniert hat. Go bringt Tests in der Standard-Toolchain mit, du musst also nichts installieren. Korrigiere zuerst averageLatency aus Fehler 7, damit die Funktion meldet, ob überhaupt Daten vorhanden waren:
example.gogofunc averageLatency(ms []float64) (float64, bool) { if len(ms) == 0 { return 0, false } var total float64 for _, v := range ms { total += v } return total / float64(len(ms)), true }
Füg dann einen Tabellentest daneben hinzu:
example.gogofunc TestAverageLatency(t *testing.T) { tests := []struct { name string in []float64 want float64 wantOK bool }{ {"three requests", []float64{120, 80, 100}, 100, true}, {"one request", []float64{42}, 42, true}, {"no requests", nil, 0, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, ok := averageLatency(tt.in) if got != tt.want || ok != tt.wantOK { t.Errorf("averageLatency(%v) = %v, %v, want %v, %v", tt.in, got, ok, tt.want, tt.wantOK) } }) } }
example.texttext--- PASS: TestAverageLatency (0.00s) --- PASS: TestAverageLatency/three_requests (0.00s) --- PASS: TestAverageLatency/one_request (0.00s) --- PASS: TestAverageLatency/no_requests (0.00s) PASS
Leg ihn in eine Datei, deren Name auf _test.go endet, und führ go test ./... aus. Jede Zeile ist ein Fall, ein neuer Grenzfall ist also genau eine Zeile mehr. Die Zeile no requests ist die Eingabe, die in Fehler 7 NaN geliefert hat. Jetzt hat sie eine definierte Antwort und einen Test, der dafür sorgt, dass das so bleibt.
11. Debuggen durch Raten
Die typische Debugging-Schleife von Anfängern sieht so aus: etwas ändern, ausführen, immer noch kaputt, etwas anderes ändern. Nach zehn Runden hat der Code neue Bugs, und der ursprüngliche ist immer noch da. In einer Studie mit 21 Studierenden von sieben Universitäten „nutzten einige wenige Strategien und wendeten sie ineffektiv an“ und bauten beim Debuggen neue Bugs ein (Murphy et al., 2008).
Schau hin, bevor du etwas änderst. Stell eine Vermutung auf, was falsch ist, und überprüfe sie, indem du die tatsächlichen Werte ausgibst:
example.gogolog.Printf("order=%+v total=%d items=%d", order, total, len(order.Items))
%+v gibt die Feldnamen von Structs mit aus, dann musst du nicht raten, welche Zahl welche ist. Wenn die Ausgaben unübersichtlich werden, nimm einen Debugger. Delve ist der Standard für Go, und VS Code und GoLand nutzen ihn beide unter der Haube:
example.bashbashdlv debug . -- access.log (dlv) break main.go:43 (dlv) continue (dlv) print fields []string len: 3, cap: 3, [ "GET", "/api/orders", "200", ]
Zeile 43 ist die Zeile mit counts[...]++ im Log-Zähler aus Fehler 6. Das Programm hält dort an, und du siehst dir den echten Wert von fields an, statt ihn zu erraten.
Wenn du den Bug gefunden hast, schreib einen Test, der ihn reproduziert, bevor du ihn behebst. Dann kann er nicht unbemerkt zurückkommen.
12. Abstrahieren oder optimieren, bevor es funktioniert
Es ist verlockend, Interfaces, Konfigurationssysteme und Plugin-Schichten für ein Programm zu entwerfen, das noch gar nicht läuft. Oder eine klare Schleife durch etwas Cleveres zu ersetzen, weil es vielleicht schneller ist. Beides macht Code schwerer änderbar, bevor du weißt, was er überhaupt tun muss.
Bring es mit dem schlichtesten Code zum Laufen, den du schreiben kannst. Warte, bis du dasselbe dreimal geschrieben hast, bevor du es in eine gemeinsame Funktion auslagerst. Definiere in Go ein Interface, wenn tatsächlich eine zweite Implementierung auftaucht, nicht wenn du dir eine vorstellst. Optimiere erst, nachdem du gemessen hast. Go liefert dir die Werkzeuge dafür: go test -bench für Benchmarks und pprof für Profile.
13. In Tutorials hängen bleiben und ohne Repro um Hilfe fragen
Tutorials geben dir das Gefühl, produktiv zu sein, weil jeder Schritt funktioniert. Wenn du zum ersten Mal eine leere Datei öffnest, merkst du, wie viel wirklich hängen geblieben ist. Irgendwann musst du etwas bauen, durch das dich niemand geführt hat: ein CLI, das Dateien umbenennt, eine kleine HTTP-API, einen Bot für eine Chat-App. Es wird chaotisch, und du lernst daraus mehr als aus den nächsten drei Tutorials. Programmieren lernen 2026 zeigt, wie du diese Praxis strukturierst, und Go lernen enthält einen vollständigen Plan für Go.
Wenn du nicht weiterkommst, fragst du um Hilfe, und wie du fragst, macht einen Unterschied. „Mein Code funktioniert nicht“ bringt dir gar nichts. Reduzier das Problem auf das kleinste Programm, das den Bug noch zeigt, und teil dann dieses Programm, die exakte Fehlerausgabe, was du erwartet hast und was du schon versucht hast. Oft findest du den Bug beim Reduzieren selbst. Für Go gibt dir der Go Playground einen teilbaren Link zu einem lauffähigen Programm.
Welche dieser Fehler fängt Go für dich ab?
Go wurde für große Codebasen entworfen, die von vielen Leuten gepflegt werden, und mehrere seiner Regeln blockieren ganz nebenbei Anfängerfehler, bevor der Code überhaupt läuft.
- Ungenutzte Variablen und Imports kompilieren nicht. Der Compiler lehnt
declared and not usedund"strings" imported and not usedab. Die Go FAQ beschreibt das als Tausch von „kurzfristiger Bequemlichkeit gegen langfristige Build-Geschwindigkeit und Klarheit des Programms“ und merkt an, dass eine ungenutzte Variable auf einen Bug hindeuten kann. gofmtformatiert jede Go-Datei gleich, du diskutierst also nie über Abstände oder die Position von Klammern, und eine unordentliche Einrückung kann keinen Bug verstecken (gofmt).go vetfindet verdächtigen Code. Es meldet zum Beispiel ein%d-Verb, das einen String bekommt. Das kompiliert problemlos, gibt aber Unsinn aus (cmd/vet).- Fehler sind Werte, die du behandeln musst. Eine Funktion, die fehlschlagen kann, gibt einen
errorzurück. Wenn sie einen Wert und einen Fehler zurückgibt, kannst du den Wert nicht verwenden, ohne auch den Fehler entgegenzunehmen, selbst wenn du ihn danach mit einem sichtbaren_verwirfst. Der Happy-Path-Fehler aus Abschnitt 4 wird so zu etwas, das du absichtlich hinschreiben musst (Errors are values). - Tests sind eingebaut.
go testund das Pakettestingwerden mit Go ausgeliefert. Du musst also vor deinem ersten Test kein Framework auswählen (testing). - Der Race Detector findet Data Races. Führ Tests oder Programme mit
-raceaus, dann meldet Go gleichzeitige Zugriffe auf geteilten Speicher mit Datei und Zeile beider Zugriffe (Data Race Detector).
Hier fängt go vet einen Formatierungsfehler ab, der kompiliert:
example.gogopackage main import "fmt" func main() { active := "12" fmt.Printf("%d active users\n", active) }
example.texttext./main.go:7:14: fmt.Printf format %d has arg active of wrong type string
Go hält dich nicht davon ab, eine 200 Zeilen lange Funktion zu schreiben oder eine Variable tmp zu nennen. Die mechanischen Fehler fängt es aber ab, und dir bleibt mehr Zeit für die, bei denen es auf Urteilsvermögen ankommt. Für Go-spezifische Fallen wie verschattete Fehler oder Überraschungen mit append lies häufige Go-Fehler, die du vermeiden solltest.
Häufig gestellte Fragen
Was ist der häufigste Fehler von Programmieranfängern?
In der größten Studie zu Compilerfehlern von Anfängern, die 37 Millionen Kompilierungen von mehr als 250.000 Java-Studierenden umfasste, waren nicht passende Klammern oder Anführungszeichen der häufigste Fehler. Die wurden schnell behoben. Teuer waren die Fehler, die der Compiler nicht meldete, etwa ein ignorierter Rückgabewert. Bis sie behoben waren, vergingen meist mehr als 17 Minuten, oder sie wurden nie behoben (Altadmri & Brown, 2015). Unter den Gewohnheiten bremst vor allem eine alles andere aus: die Fehlermeldung nicht zu lesen.
Wie höre ich auf, immer wieder dieselben Programmierfehler zu machen?
Schreib für jeden Bug, den du behebst, einen Test, damit er nicht unbemerkt zurückkommen kann. Führ eine kurze Liste der Fehler, die du mehr als einmal gemacht hast, und prüf deinen Code vor jedem Commit dagegen. Tools helfen auch. In Go fangen go vet und gofmt eine ganze Klasse von Fehlern automatisch ab.
Ist Go eine gute Sprache für Anfänger, die viele Fehler machen?
Ja. Der Go-Compiler lehnt ungenutzte Variablen und Imports ab, Fehler sind explizite Rückgabewerte, und go vet, gofmt und go test werden mit der Sprache ausgeliefert. Die Fehlermeldungen sind kurz und zeigen auf eine Datei und eine Zeile. Den vollständigen Vergleich findest du in ist Go eine gute erste Programmiersprache.
Sollten Anfänger KI zum Programmieren nutzen?
Nutze sie, um dir Code, Fehler und Konzepte erklären zu lassen, nicht um Code zu schreiben, den du nicht nachvollziehen kannst. Eine gute Regel: Füge nie Code ein, den du am nächsten Tag nicht aus dem Gedächtnis neu schreiben könntest. Dieses Verständnis brauchst du, um KI-Code zu reparieren, wenn er fast richtig ist.
Wie lange dauert es, bis man keine Anfängerfehler mehr macht?
Bei den meisten verschwinden diese Gewohnheiten nach ein paar Monaten regelmäßigen Programmierens, weil jede davon dich Zeit kostet, bis du sie ablegst. Tägliche Übung an echten Programmen bringt dich schneller dorthin als lange, seltene Sessions. Die mechanischen Fehler verschwinden zuerst. Ermessensfragen wie Benennung und Funktionsgröße brauchen länger.
Fang heute an, Go zu schreiben
Jeder Fehler auf dieser Liste lässt sich leichter vermeiden, je mehr Code du schreibst und je schneller du siehst, was schiefgelaufen ist. Genau das ist die Idee hinter LevelUpGo. Jede Lektion ist eine Go-Übung, die auf einer echten Go-Toolchain im Browser läuft. So liest du vom ersten Tag an echte Compilerfehler, behandelst err-Werte und siehst Tests fehlschlagen. Fang mit dem Lernpfad Go Fundamentals an.
