Go ist eine schlanke Programmiersprache und kommt mit 25 Keywords aus. Go ist weder ausdrücklich funktional noch ausdrücklich objektorientiert. Dennoch lassen sich beide Paradigmen in Go umsetzen. Dieses Kapitel führt in die Grundlagen der Programmiersprache Go ein.
Das Kapitel beginnt mit der Installation einer Go-Entwicklungsumgebung und der Beschreibung des Go-Workspace. Anschließend werden die Basiskonstrukte von Go beschrieben: Packages strukturieren Go-Dateien auf oberster Ebene und definieren die Sichtbarkeit enthaltener Elemente. Funktionen implementieren Programmlogik mithilfe von Schleifen, Bedingungen und Variablen. Typen stehen entweder in Form von Standard- oder benutzerdefinierten Typen zur Verfügung.
Nach Einführung dieser grundlegenden und aus anderen Programmiersprachen bekannten Sprachkonstrukte fährt das Kapitel mit den Besonderheiten von Go fort: Pointer, Interfaces und Methoden. Pointer ermöglichen die explizite Unterscheidung von Werte- und Referenztypen. Interfaces trennen Schnittstelle und Implementierung von Typen. Methoden implementieren Funktionen auf benutzerdefinierten Typen, die auf Instanzen des jeweiligen Typs gebunden werden.
Im letzten Abschnitt werden mit Arrays und Maps zwei der neben den Standardtypen wichtigsten Go-Typen beschrieben. Arrays implementieren typisierte Listen fester Länge. Maps dienen der Verwaltung typisierter Key-/Value-Paare.
Go-Distributionen stehen zum Binär-Download für MacOS, Windows und Linux unter https://golang.org/dl/ bereit. Unter MacOS wird die Go-Distribution nach /usr/local/go installiert. Damit die Go-Tools im Terminal funktionieren, muss die PATH-Variable um den Pfad /usr/local/go/bin erweitert werden. Führen Sie das Go-Tool in einem neuen Terminalfenster mit dem Parameter version aus:
$ go version go version go1.11 darwin/amd64
Entspricht die Ausgabe dem Beispiel, dann hat alles funktioniert, und die Go-Entwicklungsumgebung steht bereit. Weitere Installationshinweise sowie Anleitungen für die Go-Installation unter Windows und Linux finden Sie hier https://golang.org/doc/install.
Das grundlegende Konzept einer Go-Entwicklungsumgebung ist der Go-Workspace. Der Go-Workspace ist ein zentrales Verzeichnis, das über die Umgebungsvariable GOPATH referenziert wird. Unterhalb des GOPATH finden sich die drei Unterverzeichnisse bin, pkg und src:
bin/ restvoice pkg/ linux_amd64/ github.com/rwirdemann/restvoice/ usecase.a src/ github.com/rwirdemann/restvoice/ main.go usecase/ create_invoice.go
Go-Programmewerden compiliert und zu einem statischen Binary gelinkt. Die resultierende Binärdatei landet im Verzeichnis bin des Go-Workspace. Zusammengehörige Dateien, sogenannte Packages, werden zu einer package. a-Datei übersetzt und in eines der plattformspezifischen Unterverzeichnisse von pkg abgelegt. Das Unterverzeichnis src enthält die Dateien mit den Go-Sourcen, geordnet in Unterverzeichnisse, die die Package-Struktur widerspiegeln.
Die zugrunde liegende Idee des Go-Workspace ist, dass alle Projekte inklusive der benötigten Abhängigkeiten an zentraler Stelle abgelegt werden. Projekte werden schnell gefunden und können sich benötigte Bibliotheken teilen. Die Nutzung mehrerer Workspaces ist durch die Verwendung der Umgebungsvariable GOPATH einfach möglich.
Der Go-Workspace ist prinzipiell eine gute Idee, birgt aber auch eine Reihe praktischer Probleme. So ist es nicht ohne Weiteres möglich, unterschiedliche Versionen eines Packages im selben Workspace zu verwalten. Es ist auch nicht möglich, eine Bibliothek zu veröffentlichen, die eine bestimmte Version einer abhängigen Bibliothek benötigt.
Mit Version 1.5 wurde Go um eine Dependency-Management-Technik, das sogenannte Vendoring erweitert. Mit Vendoring kann man einem Projekt weitere Abhängigkeiten aus einem projektlokalen vendor-Verzeichnis zufügen. Seit Go-Version 1.11 steht mit Go Modules eine Ablösung des Vendoring ins Haus. Die Beispiele in diesem Buch funktionieren GOPATH-basiert. Die Beschäftigung mit Vendoring oder besser Go Modules ist an dieser Stelle nicht erforderlich. Spätestens wenn es in die Praxis geht, müssen Sie sich mit dem Thema Dependency Management beschäftigen. Das Go-Wiki enthält eine gute Einführung zur Funktionsweise und Benutzung von Go Modules [ The18].
Erstellen Sie den Go-Workspace mit den drei Unterverzeichnissen bin, pkg und src und exportieren Sie dessen Verzeichnis in der Umgebungsvariable GOPATH:
$ cd $HOME $ mkdir -p go/bin go/pkg go/src $ export GOPATH=$HOME/go
Anschließend wird im Verzeichnis src eine Datei main.go mit folgendem Inhalt erstellt:
1 package main 2 3 func main() { 4 println("Hello, Jo") 5 }
Die Funktion main im Package main ist der Einstiegspunkt in ein Go-Programm und wird beim Start des Programms ausgeführt. Das Kommando go run compiliert und führt das Programm aus:
$ cd $GOPATH/src $ go run main Hello, Jo
Das Kommandozeilentool go ist das zentrale Werkzeug einer Go-Distribution. Entsprechend parametrisiert lassen sich damit alle wichtigen Entwicklungsaufgaben ausführen. Einige Beispiele:
go build main.go # Übersetzt und legt das Binary im aktuellen Verzeichnis ab go install main.go # Übersetzt und legt das Binary im Verzeichnis "bin" ab go test # Führt die Tests im aktuellen Package aus
Ein vollständige Dokumentation der Go-Tools finden Sie unter https://golang.org/cmd/go.
Go-Quellcode ist auf Dateien mit dem Suffix .go verteilt. Die Struktur einer Go-Datei wird auf oberster Ebene durch sechs Keywords bestimmt:
package import var const type func
Das erste Statement einer Go-Datei ist immer package, gefolgt von einem oder mehreren import-Statements zum Import referenzierter Packages. Anschließend folgen Variablen- und Konstantendeklarationen sowie Typ- und Funktionsdefinitionen in beliebiger Reihenfolge und Anzahl.
Go-Files werden in Packages organisiert. Alle Dateien eines Verzeichnisses gehören zum selben Package. Der letzte Teil des Pfadnamens bestimmt den Package-Namen. Gemäß dieser Konvention liegt die Datei memory.go im Verzeichnis $GOPATH/go101/cache und gehört zum Package cache:
1 package cache 2 3 func Write(key string, value string) { 4 ... 5 }
Packages definieren Namensräume. Die Sichtbarkeit von Typen, Funktionen, Variablen und Konstanten wird mithilfe von Groß- und Kleinschreibung definiert. Alles Kleingeschriebene ist ausschließlich innerhalb des Packages sichtbar. Alles Großgeschriebene ist auch außerhalb des Packages sichtbar. Packages werden über das import-Statement importiert. Öffentliche Typen, Variablen und Funktionen werden genutzt, indem der Package-Name dem importierten Element vorangestellt wird:
1 package main 2 3 import "cache" 4 5 func main() { 6 cache.Write("1", "Kalle") 7 }
Die Funktion main importiert das Package cache und ruft die exportierte Funktion cache. Write auf. Das main-Beispiel zeigt eine Ausnahme in Bezug auf die Namenskonvention für Packages: Die Go-Datei mit der Funktion main gehört zum Package main, unabhängig vom Verzeichnis, in dem sie liegt.
Initialisierung
Jede Datei eines Packages kann eine init-Funktion enthalten, in der paketweite Initialisierungsaufgaben ausgeführt werden. Init-Funktionen werden beim Laden eines Packages automatisch ausgeführt:
1 package auth 2 3 var kf []byte 4 5 func init() { 6 if kf, err := ioutil.ReadFile("public.key"); err != nil { 7 log.Fatalf("Could not open public key file: %s", "public.key") 8 } 9 }
Init-Funktionen sind Package-intern und können aus keinem anderen Package heraus aufgerufen werden. Ein anonymer Package-Import sorgt für die Ausführung von init, ohne dass eine öffentliche Funktion des importierten Packages aufgerufen werden muss. Dies ist zum Beispiel dann sinnvoll, wenn eine Bibliothek Initialisierungscode enthält, der nie explizit aufgerufen wird, für das Funktionieren des Programmes aber einmalig ausgeführt werden muss. Das Laden eines Datenbanktreibers ist ein Beispiel:
import _ "github.com/go-sql-driver/mysql"
Der Import...