====== Go lernen ====== ===== go1.22.2 / Ubuntu 24.04.3 LTS ===== [[https://www.linkedin.com/learning/go-lernen-22741633/in-die-moderne-und-vielfaltige-programmiersprache-einsteigen-und-erste-go-projekte-starten|Linkedin: Go lernen]] ==== Einleitung ==== > apt install golang-go > go version go version go1.22.2 linux/amd64 > vim hallo_gopher.go package main import ( "fmt" ) func main() { fmt.Println("Hallo Gopher!") } > go build hallo_gopher.go > ./hallo_gopher Hallo Gopher! ==== Das Erste Go-Projekt ==== > vim main.go package main import ( "fmt" ) func main() { fmt.Println("Hallo Go-Projekt!") } > go build main.go > ./main Hallo Go-Projekt! > go help build | less > go fmt main.go > go doc fmt.Println package fmt // import "fmt" func Println(a ...any) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error ==== Die Datentypen in Go ==== * Einfache Datentypen * String, für Zeichenketten * ''string'' in UTF-8-Kodierung * Boolean, für boolesche Wahrheitswerte * ''bool'': true oder false * Zahlen, für von Integer bis Gleitkomma * ''int32'' * ''int64'' * ''float32'' * ''float64'' * komplexe Zahlen... * Zusammengesetzte Datentypen * ''Array'', Sequenz mehrerer Elemente mit fixer Länge * ''Slice'', Sequenz mehrerer Elemente mit variabler Länge * ''Map'', Wörterbuch, das Schlüssel auf einen Wert abbildet === Variablen und Konstanten === ... func main() { // Deklaration und Zuweisung in langer Form var s1 string = "eine Zeichenkette" var i1 int = 123 var b1 bool = true fmt.Println(s1, i1, b1) } ... // Diese Variable ist im ganzen Package verwendbar var aPackageVar string = "eine globale Variable" func main() { fmt.Println("Hallo Go-Projekt!") } package main import ( "fmt" ) // Diese Konstante ist im ganzen Package verwendbar const aPackageKons = "eine globale Konstante" func main() { const k1 = "eine Zeichenkette" const k2 = 123 fmt.Println(k1, k2, k2+k2) fmt.Println(aPackageKons) } === Arrays und Slices === Arrays und Slices sind Sequenzen mehrerer Elemente. Arrays haben eine feste Länge. Slices haben eine variable Länge, es entspricht einer "Liste" in ''%%C++%%'' oder ''Java''. package main import ( "fmt" ) func main() { // Array (mit 2 Elementen) initialisieren und füllen array01 := [2]string{"Element1", "Element2"} fmt.Println("unverändert", array01) // ein Element im Array ändern, das erste Element hat den Index "0" array01[1] = "Fritz" fmt.Println("verändert", array01) } package main import ( "fmt" ) func main() { // Slice mit make initialisieren slice01 := make ([]string, 2) slice01[0] = "Anita" slice01[1] = "Rom" fmt.Println(slice01) // Slice mit Werten initialisieren slice02 := []string{"Otto", "Hamburg"} fmt.Println(slice02) // Slice erweitern slice02 = append(slice02, "Bär", "Berlin") fmt.Println(slice02) } === Maps für Schlüssel/Werte-Paare === package main import ( "fmt" ) func main() { // Map mit make initialisieren var map01 = make (map[string]int) map01["Anita"] = 23 map01["Berta"] = 32 fmt.Println(map01) // Map mit Werten initialisieren var map02 = map[string]int{"Papa": 54, "Mama": 45} fmt.Println(map02) // Wert zu einem Schlüssel abrufen fmt.Println("Alter von Papa:", map02["Papa"]) } === Structs mit Feldern nutzen und definieren === In Go können mehrere Werte zu Strukturen ("Structs") zusammengefasst werden. Mit Structs können Sie Daten kapseln, und das ganz ohne Objektorientierung. Ein Struct ist ein einfacher Datenkontainer mit Feldern. package main import ( "fmt" ) type Struktur01 struct { Name string // mit großem Anfangsbuchstaben ist es ein öffentliches Feld alter int // mit kleinem Anfangsbuchstaben ist es ein privates Feld } // Struct einbinden type Struktur02 struct { Struktur01 // das Struct "Struktur02" hat nun alle Felder, die das Struct "Struktur01" hat und es können zusätzliche hinzugefügt werden passwort string } func main() { // normales Struct c := Struktur01{Name: "Alfred Nobel", alter: 63} fmt.Println(c) // ein Struct in ein anderes Struct einbinden d := Struktur02{passwort: "geheim"} d.Name = "Drachentöter" fmt.Println(d) // leeres Struct; es werden die initialen Werte ("0" und "leer") der Felder in Struktur01 ausgegeben var leereStruktur Struktur01 fmt.Println(leereStruktur) } === Pointer === package main import ( "fmt" ) type Struktur01 struct { Name string // mit großem Anfangsbuchstaben ist es ein öffentliches Feld alter int // mit kleinem Anfangsbuchstaben ist es ein privates Feld } func main() { // normales Struct c := Struktur01{Name: "Alfred Nobel", alter: 63} // Pointer p auf Struct c var p *Struktur01 = &c // Pointer mit * auflösen und Wert zuweisen (Überschreiben) (*p).Name = "Gorilla" fmt.Println("c:", c) fmt.Println("Pointer auf c:", p) } === Der leere Wert nil === package main import ( "fmt" ) type Struktur01 struct { Name string // mit großem Anfangsbuchstaben ist es ein öffentliches Feld alter int // mit kleinem Anfangsbuchstaben ist es ein privates Feld } func main() { // Pointer ist nil var p *Struktur01 // prüfen ob der Pointer p nil ist fmt.Println("Pointer p ist nil:", p == nil) // Slice ist nil var s []float64 fmt.Println("Slice s ist nil:", s == nil) // Map ist nil var m map[string]float64 fmt.Println("Map m ist nil:", m == nil) } === Fehler === //In Go gibt es keine Spezialbehandlung für Fehler. In Go sind Fehler Werte wie Strings, Integer oder Struts.// package main import ( "fmt" ) func main() { // Error err := fmt.Errorf("Benutzer %v wurde nicht gefunden", "Spenzer") fmt.Println("Error err:", err) } //In Go gibt es für Fehler den Typ "error". "error" ist ein gewöhnlicher Go-Datentyp, jedoch mit besonderer Bedeutung.// ==== Den Kontrollfluss strukturieren ==== === Kontrollstruktur if/else === package main import ( "fmt" ) func main() { // if zweifler := "gehen" if zweifler != "laufen" { fmt.Println("Du sollst jetzt nicht laufen.") } // if/else geschwindigkeit := 100 if geschwindigkeit < 120 { fmt.Println("Du bist zu langsam.") } else if geschwindigkeit == 120 { fmt.Println("Die Geschwindigkeit stimmt.") } else { fmt.Println("Du bist zu schnell.") } } === Kontrollstruktur Switch === package main import ( "fmt" "time" ) func main() { // Switch mit Ausdruck geschwindigkeit := 100 switch { case geschwindigkeit < 120: fmt.Println("Du bist zu langsam.") case geschwindigkeit == 120: fmt.Println("Die Geschwindigkeit stimmt.") default: fmt.Println("Du bist zu schnell.") } // Switch über Typ switch time.Now().Weekday() { case time.Saturday: fmt.Println("Es ist Sonnabend.") case time.Sunday: fmt.Println("Es ist Sonntag.") default: fmt.Println("Es ist ein Arbeitstag.") } } === for-Schleife === package main import ( "fmt" ) func main() { // klassische for-Schleife for i := 1; i < 7; i++ { fmt.Println("Durchlauf (for)", i) } // while-Schleife j := 1 for j < 7 { fmt.Println("Durchlauf (while)", j) j++ } // Endlos-Schleife //k := 1 //for { // fmt.Println("Durchlauf (while)", k) // k++ //} // for-each-Schleife mit range slice := []string{"Otto", "Didi", "Heinz"} for index, character := range slice { fmt.Println(index, "Character", character) } } === Übung === package main import ( "fmt" ) func main() { // Übung: Alter vom Hund in Menschenjahren und Hundejahren for dogAge := 1; dogAge < 11; dogAge++ { HumanAge := dogAge * 7 fmt.Printf("Dog: %d, Human %d\n", dogAge, HumanAge) } } ==== Funktionen und Methoden ==== === einfache Funktionen === package main import ( "fmt" ) func humanAge1(dogAge int) int { return dogAge * 7 } func humanAge2(dogAge int) (humanAge int) { humanAge = dogAge * 7 return } func main() { // Übung: Alter vom Hund in Menschenjahren und Hundejahren hassoAge := 3 fmt.Println("1. Menschenalter von Hasso", humanAge1(hassoAge)) fmt.Println("2. Menschenalter von Hasso", humanAge2(hassoAge)) } === Funktionen mit mehreren Parametern und Rückgaben === package main import ( "fmt" ) func humanAge(dogAge int) int { return dogAge * 7 } func humanAges(dog1, dog2 int) (int, int) { return humanAge(dog1), humanAge(dog2) } func main() { human1, human2 := humanAges(3, 5) fmt.Println("Human Ages:", human1, ",", human2) } === Methoden an Strukturen definieren === package main import ( "fmt" ) // Struct type Struktur01 struct { Name string // mit großem Anfangsbuchstaben ist es ein öffentliches Feld alter int // mit kleinem Anfangsbuchstaben ist es ein privates Feld } // Methode func (c Struktur01) methode01() { fmt.Println("Ausgabe der Methode:", c.Name) } func main() { // normales Struct c := Struktur01{Name: "Alfred Nobel", alter: 63} // Aufruf Methode an Struct c.methode01() fmt.Println(c) } === Mit Fehlern in Funktionen umgehen === package main import ( "fmt" ) // Struktur type Congressman struct { Name string AccountBalance float64 } // Fehler als Rückgabewert // Der Kontostand eines bestechlichen Abgeordneten wird von dieser Methode erhöht // ist ein Abgeordneter nicht bestechlich, wird ein Fehler ausgegeben func (c Congressman) bestechung(amount float64) error { // ist der Name des Abgeordneten nicht Peter, so ist er nicht bestechlich if c.Name != "Peter" { return fmt.Errorf("%v ist nicht koruppt", c.Name) } c.AccountBalance += amount fmt.Println(c.Name, "hat", c.AccountBalance) return nil } func main() { //c := Congressman{Name: "Jacjie", AccountBalance: 8000.0} c := Congressman{Name: "Peter", AccountBalance: 8000.0} // Fehler behandeln err := c.bestechung(5000.0) if err != nil { fmt.Println("Ein Abgeordneter konnte nicht bestochen werden:", err) } fmt.Println(c) } === generische Funktionen === package main import ( "log" ) // generische Funktion func humanAge[T int | float64](dogAge T) T { return dogAge * 7 } func main() { // Funktion mit Typ Integer aufrufen var i int = humanAge(3) log.Println(i) // Funktion mit Typ Fließkomma aufrufen var j float64 = humanAge[float64](4.9) log.Println(j) } === anonyme Funktionen === package main import ( "fmt" "net/http" ) func main() { // anonyme Funktion definieren und ausführen func () { fmt.Println("Hallo Welt") }() // anonyme HTTP-Handler-Funktion http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hallo Welt")) }) http.ListenAndServe(":8080") } === verzögerte Ausführung === package main import ( "fmt" ) // mit defer kann eine Ausführung verzögert werden func halloDefer() { defer fmt.Print("Welt\n") // defer wird auch bei einem Fehler ausgeführt // hier greifen wir in einem Slice auf eine Stelle (Index) zu, // die nicht existiert -> Fehler //var s = []string{} //fmt.Println("Huch ", s[42]) fmt.Print("Hallo ") } func main() { // verzögerte Ausführung mit defer halloDefer() } //Typische Anwendungsfälle für ''defer'' sind "Dateien schließen", "Dateien löschen" oder "Speicher freigeben".// ==== Schnittstellen mit Interfaces ==== === Das Interface === package main import ( "fmt" ) type Congressman struct { Name string } func (c Congressman) greet() { fmt.Println("Hallo", c.Name) } type Enemy struct{} func (e Enemy) greet() { fmt.Println("Go to hell!") } // Interface definieren type Greeter interface { greet() } // Interface nutzen func passBy(g1, g2 Greeter) { g1.greet() g2.greet() } func main() { // Interface c := Congressman{Name: "Frank"} e := Enemy{} passBy(c, e) } //Interfaces werden in Go nicht explizit, sondern implizit integriert.// === Das Interface Stringer aus der Std-Lib === package main import ( "fmt" ) type Struktur01 struct { Name string } func (c Struktur01) Sring() string { return "Hallo! " + c.Name } func main() { c := Struktur01{Name: "Alfred Nobel"} fmt.Println(c) } $ go doc fmt.Stringer package fmt // import "fmt" type Stringer interface { String() string } Stringer is implemented by any value that has a String method, which defines the “native” format for that value. The String method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print. === Das leere Interface === package main import ( "fmt" ) func main() { // Das leere Interface var i interface{} = "Hallo" // Type Assertion heißt in anderen Sprachen Cast -> Typ-Umwandlung // Type Assertion ohne Prüfung var s string = i.(string) fmt.Println(s) // Type Assertion mit Prüfung s2, ok := i.(string) fmt.Println(s2, "ist es ein String?", ok) // falsche Type Assertion ohne Prüfung erzeugt Panic var f float64 = i.(float64) fmt.Println(f) } === Das io.Reader-Interface JSON verarbeiten === package main import ( "fmt" "strings" "encoding/json" ) func main() { // normales Struct const jsonBody = `{ "Nachricht": "Hallo Leser!" }` var jsonMap map[string]string r := strings.NewReader(jsonBody) json.NewDecoder(r).Decode(&jsonMap) fmt.Println(jsonMap) } ==== Nebenläufig programmieren ==== === Mit Go-Routinen und Channels nebenläufig programmieren === package main import ( "fmt" ) func Hallo(gruss string) { fmt.Println("Hallo", gruss) } func main() { // Go-Routine go Hallo("Welt") } package main import ( "fmt" ) // Deklarieren und Initialisieren //a := make(chan string) // // Nachricht über Channel senden //a <- "Hallo" // // Nachricht von Channel empfangen, // "Pfeil" gibt Richtung des Datenflusses an //value = <-a // //func Hallo(gruss string) { // fmt.Println("Hallo", gruss) //} // //func main() { // // Go-Channel // go Hallo("Welt") //} func main() { // 1. Channel erzeugen money := make(chan int) go func () { // 3. Receive auf Channel money amount := <-money fmt.Println("empfangen:", amount, "€") }() // 2. an Channel senden money <- 200 } package main import ( "fmt" "time" ) func cashflow(money chan int) { amount := <-money fmt.Println("empfangen:", amount, "€") } func main() { money := make(chan int) go cashflow(money) money <- 1000 time.Sleep(2 * time.Second) } package main import ( "fmt" "time" ) func cashflow(money chan int) { // mit Select kann man gleichzeitig auf mehrere Channels lauschen select { case amount := <-money: fmt.Println("empfangen:", amount, "€") case <-time.After(1 * time.Second): fmt.Println("...kein Geld bekommen!!!") } } func main() { money := make(chan int) go cashflow(money) // money <- 1000 time.Sleep(2 * time.Second) } package main import ( "fmt" "time" "sync" ) func worker(id int) { fmt.Printf("Worker %d startet\n", id) // durch schlafen wird aufwändige Verarbeitung simuliert time.Sleep(time.Second) fmt.Printf("Worker %d ist fertig\n", id) } func main() { // Parallele Worker mit der WaitGroup var wg sync.WaitGroup // hier werden 5 Worker in einer Schleife nacheinander gestartet for i := 1; i <= 5; i++ { wg.Add(1) // weil sich alle Worker die Variable "i" teilen, brauchen wir hier noch eine eigene Kopie von "i" i := i go func() { defer wg.Done() worker(i) }() } // hier warten wir, bis alle Worker fertig sind wg.Wait() } package main import ( "fmt" "time" "math/rand" ) func speaker(name string, debate chan int) { for { microphone := <-debate // auf Mic warten (Nachricht empfangen) fmt.Printf("Turn %v: %v says '%v'\n", microphone, name, randomAnswer()) time.Sleep(400 * time.Millisecond) microphone++ debate <- microphone // Mic zurückgeben (Nachricht senden) } } func randomAnswer() string { answers := []string{"Ich habe Recht", "Nimm dieses Argument.", "Aber ich habe diese Erfahrung gemacht.", "Du ***"} return answers[rand.Intn(len(answers)-1)] } func main() { debate := make(chan int) go speaker("Jackie", debate) go speaker("Frank", debate) microphone := 1 debate <- microphone time.Sleep(2 * time.Second) <-debate } ==== Praktische Aufgabe: Webserver für Go Proverbs ==== === Basisprogramm: Webserver Proverbs === > go mod init proserve > go get github.com/jboursiquot/go-proverbs go: downloading github.com/jboursiquot/go-proverbs v0.0.2 go: added github.com/jboursiquot/go-proverbs v0.0.2 package main import ( "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hallo Proserve!\n")) }) http.ListenAndServe(":8000", nil) } > go build Webserver_Proverbs.go > ./Webserver_Proverbs & [1] 274944 > jobs [1]+ Läuft ./Webserver_Proverbs & > curl localhost:8000 Hallo Proserve! > killall Webserver_Proverbs > jobs [1]+ Beendet ./Webserver_Proverbs //Dieses Programm werden wir im folgenden erweitern...// === Ersten Plain-Text-API-Endpoint umsetzen === package main import ( "fmt" "net/http" "github.com/jboursiquot/go-proverbs" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hallo Proserve!")) }) // Plain Text Endpunkt http.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, proverbs.Random().Saying) }) http.ListenAndServe(":8000", nil) } > curl localhost:8000/ ; echo Hallo Proserve! > curl localhost:8000/text ; echo Make the zero value useful. === JSON-Endpoint === package main import ( "fmt" "net/http" "github.com/jboursiquot/go-proverbs" "encoding/json" ) type Proverb struct { Saying string `json:"saying"` Link string `json:"link"` } func HandleProverbJson(w http.ResponseWriter, r *http.Request) { randomProverb := proverbs.Random() p := Proverb { Saying: randomProverb.Saying, Link: randomProverb.Link, } err := json.NewEncoder(w).Encode(p) if err != nil { http.Error(w, "uups, da ist etwas schief gegangen!", http.StatusInternalServerError) } } func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hallo Proserve!")) }) // Plain Text Endpunkt http.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, proverbs.Random().Saying) }) // JSON Endpunkt http.HandleFunc("/api", HandleProverbJson) http.ListenAndServe(":8000", nil) } > curl localhost:8000/api ; echo {"saying":"interface{} says nothing.","link":"https://www.youtube.com/watch?v=PAAkCSZUG1c\u0026t=7m36s"} === Unit-Test: JSON-Endpoint === package main import ( "net/http" "net/http/httptest" "testing" ) type TestHandleProverbJson(t *testing.T) { // 1. HTTP Recorder erstellen recorder := httptest.NewRecorder() // 2. HTTP Request req, _ := http.NewRequest)"GET", "/api", nil) // 3. HTTP-Händler aufrufen HandleProverbJson(recorder, req) // 4. Ergebnis prüfen if recorder.Code != http.StatusOK { t.Errorf("fehlerhafter Status %v, erwarteter Status %v", recorder.Code, http.StatusOK) } } === Fehler behandeln === package main import ( "fmt" "net/http" "github.com/jboursiquot/go-proverbs" "encoding/json" "log" ) type Proverb struct { Saying string `json:"saying"` Link string `json:"link"` } func HandleProverbJson(w http.ResponseWriter, r *http.Request) { randomProverb := proverbs.Random() p := Proverb { Saying: randomProverb.Saying, Link: randomProverb.Link, } err := json.NewEncoder(w).Encode(p) if err != nil { http.Error(w, "uups, da ist etwas schief gegangen!", http.StatusInternalServerError) } } func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hallo Proserve!")) }) // Plain Text Endpunkt http.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintf(w, proverbs.Random().Saying) if err != nil { http.Error(w, "uups, da ist etwas schief gegangen!", http.StatusInternalServerError) } }) // JSON Endpunkt http.HandleFunc("/api", HandleProverbJson) err := http.ListenAndServe(":8000", nil) if err != nil { log.Fatal(err) } } > ./Webserver_Proverbs & [1] 293523 > ./Webserver_Proverbs 2026/01/27 16:32:12 listen tcp :8000: bind: address already in use > killall Webserver_Proverbs === HTML-Seite für Proverbs === package main import ( "fmt" "net/http" "github.com/jboursiquot/go-proverbs" "encoding/json" "log" "html/template" ) type Proverb struct { Saying string `json:"saying"` Link string `json:"link"` } func HandleProverbJson(w http.ResponseWriter, r *http.Request) { randomProverb := proverbs.Random() p := Proverb { Saying: randomProverb.Saying, Link: randomProverb.Link, } err := json.NewEncoder(w).Encode(p) if err != nil { http.Error(w, "uups, da ist etwas schief gegangen!", http.StatusInternalServerError) } } func HandleProverbPage(w http.ResponseWriter, r *http.Request) { t, _ := template.New("page").Parse(`

Your Proverb: {{ .Saying }}

`) err := t.Execute(w, proverbs.Random()) if err != nil { http.Error(w, "uups, da ist etwas schief gegangen!", http.StatusInternalServerError) } } func main() { http.HandleFunc("/", HandleProverbPage) // Plain Text Endpunkt http.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintf(w, proverbs.Random().Saying) if err != nil { http.Error(w, "uups, da ist etwas schief gegangen!", http.StatusInternalServerError) } }) // JSON Endpunkt http.HandleFunc("/api", HandleProverbJson) err := http.ListenAndServe(":8000", nil) if err != nil { log.Fatal(err) } }
> ./Webserver_Proverbs & [1] 296083 > curl localhost:8000/ ; echo

Your Proverb: The bigger the interface, the weaker the abstraction.

> killall Webserver_Proverbs
=== Das Makefile === BINARY = "Webserver_Proverbs" clean: rm -f ${BINARY} build: clean go build -o ${BINARY} . ==== Das Zen von Go ==== - **einfach** * Go erreicht viel mit wenig. Ein Programm sollte eine Sache tun, und die gut. - **bescheiden** * Unser Kode soll klar, idiomatisch und verständlich sein und seinen Zweck gut erfüllen. Clever ist da hinderlich. - **Handeln durch Nichthandeln** * Probleme eliminieren und Umwege vermeiden. Bewusstes Nichthandeln als Alternative zur Problemlösung und Umsetzung nutzen. ==== Ideen für erste Projekte ==== * Go eignet sich besonders für... * Microservices * Serverless Funktions * Kommandozeilen-Tools * DevOps und Cloud-Automatisierung ===== ...weiter machen... ===== [[https://www.linkedin.com/learning/code-challenges-fur-go/kompakte-programmierratsel-in-go-mit-unterschiedlichen-schwierigkeitsgraden-losen|Code-Challenges für Go]]