Pakete

Zusammengehörige Funktionen und Datentypen, die eine logische Einheit bilden, fasst man in Pakete zusammen. Die Standard-Bibliothek, die mit Go mitgeliefert wurde, enthält eine ganze Reihe nützlicher Pakete, zum Beispiel zum Lesen von komprimierten Dateien, zum Berechnen von Prüfsummen oder zum Lesen und Schreiben von XML-Dateien. Insgesamt sind es gut 150 Pakete, die man unter https://golang.org/pkg/ ansehen und aus jedem Go-Programm benutzen kann.

Importieren von Paketen

Um die Funktionen aus einem Paket zu benutzen, muss man es importieren. In den bisherigen Beispielen wurde bisher nur das Paket fmt benutzt. Um mehrere import Anweisungen zusammenzufassen, kann man folgende Syntax benutzen:

import (
    "net/http"
    "io/ioutil"
    "fmt"
)

Ansprechbar sind die dann Pakete unter dem letzten Namensbestandteil, hier also http, ioutil und fmt, beispielsweise:

fmt.Println("Hallo")

Man kann (das wird aber sehr selten gemacht) Pakete unter einem anderen Namen importieren, als es gespeichert ist. Man kann auch Pakete laden, ohne sie zu benutzen. Das wird für Seiteneffekte benutzt (z.B. laden eines speziellen Datenbanktreibers). Auch kann man die Definitionen der Funktionen in den eigenen Namensraum importieren:

import (
    formatiere "fmt"
    . "io/ioutil"
    "net/http"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    formatiere.Println("Hallo")
    // normalerweise: ioutil.ReadFile(...)
    data, err := ReadFile("liesmich.txt")
    ...
}

Hier wird das Paket fmt unter dem Namen formatiere bekannt gemacht und die Definitionen im Paket ioutil in den aktuellen Namensraum importiert (jetzt kann man die Funktion ReadFile() ohne ioutil davor benutzen). Ebenfalls wird das Paket mysql geladen, aber ohne dass man es ansprechen kann. Der Effekt ist, dass die entsprechende init()-Funktion aufgerufen wird, in der der SQL-Treiber registriert wird

Ich erlaube mir aber die Bemerkung, dass man solche »Tricks« wohl überlegt einsetzen sollte, sie können mehr Verwirrung stiften als notwendig.

Sichtbarkeitsregeln

Vielleicht hast du schon bemerkt, dass die Funktionsnamen aus den Paketen, die wir benutzen, alle mit einem Großbuchstaben anfangen (Println(), ReadFile(), …​). Denn nur Funktionsnamen mit einem Großbuchstaben an erster Stelle sind von außen sichtbar. Das ist eine äußerst praktische Eigenschaft von Go, die am Anfang vielleicht etwas Kopfschütteln hervorrufen mag, sich in der Praxis aber hervorragend bewährt. Die Funktion addiere() kann nur innerhalb eines Paketes aufgerufen werden, die Funktion Addiere() (mit großem A) auch von außerhalb. Damit kann man interne Funktionen vor dem Aufruf von außen verstecken. Meist stellt man den Benutzern des Pakets nur wenige Funktionen zur Verfügung, die sie benutzen dürfen und weitere Funktionen, die intern aufgerufen werden. Genau so verhält es sich mit Variablen und Konstanten (darauf kommen ich später zurück) und mit Strukturen (struct).

Um es etwas genauer, aber komplizierter zu formulieren: die identifier (Namen), die auf höchster Ebene eines Paket sichtbar sind, können von anderen Paketen benutzt werden. Diese identifier nennt man auch exported (exportiert).

In diesem Beispiel sind die Struktur Fahrzeug, als auch das Feld Name von anderen Paketen ansprechbar.

type Fahrzeug struct {
    Name    string
    hubraum int
    ps      int
    kmh     int
    baujahr int
}

Ein anderes Paket könnte die Struktur Fahrzeug initialisieren und den Namen setzen, andere Felder nicht. Falls die Struktur Fahrzeug im Paket katalog definiert ist, können wir wie folgt darauf zugreifen:

package main

import (
    "fmt"
    "katalog"
)

func main() {
    pkw := katalog.Fahrzeug{}
    pkw.Name = "Audi 80"
    // was aber nicht geht, weil hubraum
    // mit einem Kleinbuchstaben anfängt:
    // fmt.Println(pkw.hubraum)
}

Eigene Pakete erstellen

Es ist sehr leicht, ein eigenes Paket in Go zu erstellen. Es ist sogar sehr üblich, Funktionalität, die einen gewissen Umfang überschreitet, in ein eigenes Paket zu schreiben. Pakete haben eine ganze Reihe von Vorteilen: Kapselung, Wartbarkeit, Wiederverwendbarkeit und Testbarkeit. Unter Kapselung versteht man wie oben erwähnt das Verstecken von internen Funktionen vor dem Benutzer des Pakets. Wenn man die internen Funktionen (und Strukturen, Variablen etc.) nicht offenbart, kann man sie leicht ändern, ohne dass existierende Programme nicht mehr funktionieren. Die Wartbarkeit wird erhöht, weil man immer einen in sich begrenzten Funktionsumfang überblicken muss. Wiederverwendbarkeit ist eine der großen Errungenschaften von Paketen. Im Idealfall kann man Pakete in anderen Programmen erneut verwenden und somit erheblich Arbeitsaufwand und Fehlerquellen einsparen. Auf die Testbarkeit wird in einem späteren Abschnitt noch einmal genauer eingegangen.

Ein Paket zu erstellen besteht aus wenigen Schritten:

  1. Es muss ein Verzeichnis mit dem Namen des Pakets erstellt werden (z.B. katalog für das Paket katalog).
  2. In diesem Verzeichnis liegen beliebig viele Quelldateien, die zu dem Paket gehören.
  3. Jede Datei muss als Paketdeklaration den Paketnamen enthalten.
Neben der nachfolgenden Erklärung über GOPATH gibt es seit ein paar Jahren die Möglichkeit, Module zu definieren und zu benutzen. Welche Methode man benutzt, kommt auf die lokalen Gegebenheiten an. GOPATH ist m.E. etwas leichter einzurichten, Module sind flexibler. Die Module beschreibe ich an einer anderen Stelle.

Um die Sache ein kleinwenig schwieriger zu machen, kommt jetzt die Umgebungsvariable GOPATH ins Spiel. Das Verzeichnis des Pakets muss in dem Verzeichnis src liegen, das in dieser Umgebungsvariable enthalten ist, wenn es außerhalb der Go-Installation liegt​[1]. Meist setzt man die Variable auf ein festes Verzeichnis (z.B. /home/meinname/go) oder man setzt die Variable für jedes Projekt separat, was in der Praxis wohl häufiger anzutreffen ist. Setzt man also GOPATH auf das aktuelle Verzeichnis, erstellt man in hier ein Unterverzeichnis mit dem Namen src und darunter das Verzeichnis mit dem Paketname. Wollen wir als Beispiel für ein Autohaus einen Katalog der Fahrzeuge programmieren, könnten wir das Paket katalog wie folgt erzeugen (eine Unix-Shell vorausgesetzt):

export GOPATH=$PWD
mkdir -p src/katalog

In diesem Verzeichnis legt man dann eine Datei mit der Endung .go an, beispielsweise katalog.go die mindestens die Zeile package katalog am Anfang der Datei enthält.

Verzeichnisstruktur
Die Variable GOPATH zeigt auf das Verzeichnis, in dem das Verzeichnis src enthalten ist. Das Paket katalog besteht hier aus drei Dateien.

Externe Pakete laden

Es gibt eine Reihe nützlicher Pakete auf bekannten Source-Repositories wie Github, Bitbucket, Google code, Launchpad oder anderen. Go macht es dem Anwender sehr leicht, Pakete und deren Abhängigkeiten von diesen Servern herunter zu laden und in das eigene Programm einzubinden. Der Befehl hierzu heißt go get und hat als Parameter den Pfad zum Repository. go get unterstützt die Versionsverwaltungen Git, Mercurial, Subversion und Bazaar. Möchte man beispielsweise im eigenen Programm das Paket gogit des Autors dieser Einführung benutzen (mit dem Paket kann man von Go heraus auf Git- Repositories zugreifen), lautet der Befehl auf der Kommandozeile:

go get github.com/speedata/gogit

Go erzeugt unterhalb des Pfades, der in der Variable $GOPATH angegeben ist, die Verzeichnishierarchie src/github.com/speedata/gogit. Der Import-Pfad, um das Paket dann zu benutzen, ist github.com/speedata/gogit. Angesprochen, das wurde oben bereits erwähnt, wird das Paket mit dem letzten Namensbestandteil, hier gogit:

package main

import "github.com/speedata/gogit"

func main() {
    repos, err := gogit.OpenRepository("/pfad/zum/repository")
    if err != nil {
        // Fehlerbehandlung
    }
    // ...
}

1. d.h. die Pakete aus der Standardbibliothek müssen nicht in der Variable GOPATH enthalten sein