Go Home Page
Die Programmiersprache Go

Tutorial: Create a Go Module — Deutsche Übersetzung

Das Original:
https://golang.org/doc/tutorial/create-module
Version of March 3, 2021
Diese Übersetzung:
https://bitloeffel.de/DOC/golang/create-module_20210817_de.html
Stand: 04.08.2021
© 2021 Hans-Werner Heinzen @ Bitloeffel.de
Die Nutzung dieses Werks ist unter den Bedingungen der "Creative Commons Attribution 3.0"-Lizenz erlaubt.
Für Fachbegriffe und ähnliches gibt es hier noch eine Wörterliste.

Übung: Module erstellen

Im Verlauf dieser Übung werden Sie zwei Module erstellen. Erstens eine Bibliothek, die von anderen Bibliotheken oder Programmen importiert werden kann. Zweitens ein Programm, welches das erste benutzt.

Diese Übung behandelt sieben Themen (in sieben Lektionen), die jeweils andere Aspekte der Sprache beleuchten.

  1. Erstellen eines Moduls — Schreiben Sie ein kleines Modul mit Funktionen, die Sie von einem anderen Modul aufrufen können.
  2. Rufen aus einem anderen Modul heraus — Importieren und benutzen Sie Ihr neues Modul.
  3. Rückgabe und Verarbeiten eines Fehlers — Fügen Sie eine einfache Fehlerbehandlung hinzu.
  4. Rückgabe einer zufälligen Grußformel — Verarbeiten Sie Daten mit Slices (das sind Arrays dynamischer Größe in Go).
  5. Rückgabe von Grußformeln für mehrere Adressaten — Speichern Sie Schlüssel/Wert-Paare in einer Map.
  6. Erstellen eines Tests — Benutzen Sie Go's Standardverfahren für Komponententests.
  7. Fertigen und Installieren der Anwendung — Fertigen und Installieren Sie Ihren Kode lokal.
  8. Zusammenfassung — Noch ein paar Hinweise zu Go-Modulen.

Hinweis: Weitere Übungen finden Sie hier.

Voraussetzungen

1. Erstellen eines Moduls, das andere nutzen können

Beginnen wir mit dem Erstellen eines Go-Moduls. Ein Modul versammelt ein oder mehrere zusammengehörige Pakete, die einen eigenständigen, nützlichen Funktionssatz bilden. Zum Beispiel könnten Sie ein Modul mit Paketen zur Finanzanalyse erstellen, so dass andere, die eine Finanzanwendung schreiben, Ihre Vorarbeit nutzen können. Mehr zum Entwickeln von Modulen finden Sie in "Developing and publishing modules".

Go-Kode wird in Paketen gesammelt, und Pakete in Modulen. Ihr Modul benennt Abhängigkeiten Ihres Kodes, die benutzte Go-Version und die Menge der benötigten anderen Module eingeschlossen.

Im Laufe der Zeit ergänzen und verbessern Sie die Funktionalität Ihres Moduls und veröffentlichen immer neue Versionen. Entwickler, deren Kode Funktionen Ihres Moduls aufruft, können vor einer Produktionsübergabe das aktualisierte Paket importieren und mit der neuen Version testen.

  1. In einer Kommandozeile begeben Sie sich in ihr Home-Verzeichnis.

    Unter Linux oder auf dem Mac mit:

    $ cd
    

    Unter Windows mit:

    cd %HOMEPATH%
    
  2. Erstellen Sie für die Go-Modulquellen einen Ordner namens greetings.

    Das tun Sie zum Beispiel aus Ihrem Home-Verzeichnis heraus mit den folgenden Kommandos:

    $ mkdir greetings
    $ cd greetings
    
  3. Initialisieren Sie Ihr Modul mit dem Kommando go mod init, um die Datei go.mod zu erzeugen.

    Rufen Sie das Kommando go mod init und geben Sie den Modulpfad mit — in unserem Beispiel verwenden wir example.com/greetings. Wenn Sie Ihr Modul veröffentlichen, dann ist das die Adresse, von der Ihr Modul durch die Go-Tools kopiert werden kann (download), also die Ihres Quellkode-Depots.

    $ go mod init example.com/greetings
    go: creating new go.mod: module example.com/greetings
    

    Das Kommando go mod init erzeugt eine Datei go.mod, die die Abhängigkeiten Ihres Moduls überwacht. Jetzt gerade enthält die Datei nur den Namen Ihres Moduls und die Go-Version, mit der Ihr Kode funktioniert. Doch für jede Abhängigkeit, die Sie hinzufügen, wird die Datei go.mod das zugehörige Modul mit seiner Version festhalten. Damit bleiben Umwandlungen reproduzierbar, und Sie haben unmittelbare Kontrolle darüber, welche Modulversionen benutzt werden.

  4. Erzeugen Sie mit einem Texteditor eine Datei für Ihren Kode und nennen sie greetings.go.
  5. Fügen Sie dort den folgenden Kode ein, und sichern sie die Datei.
    package greetings
    
    import "fmt"
    
    // Hello gibt einen Gruß an die genannte Person zurück.
    func Hello(name string) string {
        // Rückgabe eines Grußtexts mit eingefügtem Namen.
        message := fmt.Sprintf("Hi %v. Wie geht's.", name)
        return message
    }
    

    Das ist der erste Kode für ihr Modul. Er gibt jedem Rufer, der danach fragt, einen Gruß zurück. In der nächsten Lektion werden wir dann Kode schreiben, der diese Funktion aufruft.

    Im obigen Kode tun Sie folgendes:

    • Sie deklarieren ein Paket greetings, um darin verwandte Funktionen zu sammeln.
    • Sie implementieren eine Funktion Hello, die einen Gruß zurückgibt.

      Diese Funktion nimmt einen Parameter name vom Typ string entgegen. Sie gibt einen string zurück. In Go kann eine Funktion, deren Name mit einem Großbuchstaben beginnt, von einer Funktion außerhalb des Pakets aufgerufen werden. Das ist in Go als exportierter Name bekannt. Mehr über exportierte Namen finden Sie unter "Exported names" in der Go-Tour.

    • Sie deklarieren eine Variable message die den Gruß aufnimmt.

      In Go ist der Operator := eine Abkürzung zum Deklarieren und Initialisieren einer Variablen in einer Zeile. (Go erkennt am Wert auf der rechten Seite den Typ der Variablen.) In Langversion hätten Sie auch schreiben können:

      var message string
      message = fmt.Sprintf("Hi %v. Wie geht's.", name)
      
    • Sie benutzen die Funktion Sprintf aus dem Paket fmt, um eine Grußnachricht zu erzeugen. Beim ersten Argument handelt es sich um einen Formatstring; Sprintf ersetzt dort den Platzhalter %v mit dem Wert des Parameters name. Damit ist der Grußtext vollständig.
    • Sie geben den formatierten Grußtext an den Rufer zurück.

Im nächsten Schritt werden Sie diese Funktion von einem anderen Modul aus aufrufen.

2. Rufen aus einem anderen Modul heraus

In dieser Lektion werden wir Kode schreiben, der die Hello-Funktion des gerade erstellten Moduls aufruft. Unser Kode wird als Programm ausführbar sein und Kode des greetings-Moduls nutzen.

  1. Erstellen Sie für die Go-Modulquellen einen Ordner namens hello. Dorthin schreiben Sie Ihren Rufer.

    Nachdem Sie diesen Ordner angelegt haben, sollten Sie sowohl einen hello- als auch einen greetings-Ordner auf derselben Hierarchiestufe haben:

    <home>/
     |-- greetings/
     |-- hello/
     

    Wenn Sie sich zum Beispiel gerade im greetings-Ordner befinden, können Sie das mit folgenden Kommandos tun:

    $ cd ..
    $ mkdir hello
    $ cd hello
    
  2. Aktivieren Sie die Überwachung der Abhängigkeiten für den neuen Kode.

    Dazu führen Sie das go mod init-Kommando aus, mit dem Modulnamen, zu dem Ihr Kode gehören soll.

    In unserem Beispiel verwenden wir example.com/hello als Modulpfad.

    $ go mod init example.com/hello
    go: creating new go.mod: module example.com/hello
    
  3. Erzeugen Sie mit einem Texteditor im hello-Ordner eine Datei für Ihren Kode und nennen Sie sie hello.go.
  4. Schreiben Sie Kode, mit dem Sie unsere Hello-Funktion aufrufen und geben Sie den zurückgegebenen Wert aus.

    Dazu können Sie in hello.go den folgendes einfügen:

    package main
    
    import (
        "fmt"
    
        "example.com/greetings"
    )
    
    func main() {
        // Gruß besorgen und ausgeben.
        message := greetings.Hello("Rosa")
        fmt.Println(message)
    }
    

    Im obigen Kode tun Sie folgendes:

    • Sie deklarieren ein main-Paket. Als Programm ausführbarer Kode muss sich bei Go im main-Paket befinden.
    • Sie importieren zwei Pakete, example.com/greetings und das fmt-Paket. Damit bekommen Sie Zugriff auf (exportierte) Funktionen dieser Pakete. Indem Sie example.com/greetings importieren — das Paket, das sie vorhin erstellt haben — bekommen Sie Zugriff auf die Funktion Hello. Außerdem importieren Sie fmt, mit seinen Funktionen zur Ein- und Ausgabe von Text (wie etwa Ausgeben von Text auf die Konsole).
    • Sie kommen an einen Grußformel, indem Sie die Hello-Funktion des Pakets greetings aufrufen.
  5. Bearbeiten Sie das example.com/hello-Modul so, dass es das example.com/greetings-Modul benutzt.

    Für eine "echte" Anwendung würden Sie das example.com/greetings-Modul aus einem Quellkode-Depot (repository) veröffentlichen, wo Go-Tools es finden und kopieren könnten; der Modulpfad würde auf die öffentliche Adresse zeigen. Jetzt, da Sie das Modul nicht veröffentlicht haben, müssen Sie noch das example.com/hello-Modul anpassen, damit es den example.com/greetings auf Ihrem lokalen Dateisystem findet.

    Führen Sie zum Bearbeiten des example.com/hello-Moduls das Kommando go mod edit aus, um die Go-Tools vom Modulpfad (wo das Modul NICHT ist) zu einem lokalen Ordner (wo es ist) umzuleiten.

    1. Führen Sie dazu auf der Kommandozeile aus dem Ordner hello folgendes Kommando aus:
      $ go mod edit -replace example.com/greetings=../greetings
      

      Das Kommando legt fest, dass für das Verorten der Abhängigkeit example.com/greetings durch ../greetings ersetzt werden soll. Nach Ausführung des Kommandos sollte die go.mod-Datei im hello-Ordner eine replace-Directive enthalten:

      module example.com/hello
      
      go 1.16
      
      replace example.com/greetings => ../greetings
      
    2. Zum Synchronisieren der Modulabhängigkeiten, die vom Kode verlangt werden aber noch nicht im Modul überwacht werden, führen Sie nun auf der Kommandozeile das go mod tidy-Kommando aus:
      $ go mod tidy
      go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000
      

      Danach sollte die go.mod-Datei des Moduls example.com/hello so aussehen:

      module example.com/hello
      
      go 1.16
      
      replace example.com/greetings => ../greetings
      
      require example.com/greetings v0.0.0-00010101000000-000000000000
      

      Das Kommando fand den lokalen Kode im greetings-Ordner, fügte eine require-Directive hinzu, um festzulegen, dass example.com/hello nach example.com/greetings verlangt; diese Abhängigkeit wurde durch den Import des greetings-Pakets in hello.go erzeugt.

      Die Nummer hinter dem Modulpfad ist eine Pseudo-Versionsnummer — eine generierte Nummer anstelle der semantischen Versionsnummer (die das Modul noch nicht hat).

      Um auf ein veröffentlichtes Modul zu verweisen, würde eine go.mod-Datei üblicherweise auf die replace-Directive verzichten und eine require-Directive mit echter Versionsnummer am Ende benutzen.

      require example.com/greetings v1.1.0
      

      Mehr über Versionnummern erfahren Sie in "Module version numbering".

  6. Um zu sehen, ob der Kode funktioniert starten Sie ihn im hello-Ordner.
    $ go run .
    Hi Rosa. Wie geht's.
    

Glückwunsch! Sie haben gerade zwei funktionierende Module geschrieben.

Im nächsten Schritt werden Sie eine Fehlerbearbeitung ergänzen.

3. Rückgabe und Verarbeiten eines Fehlers

Handhaben von Fehlern ist für zuverlässigen Kode unverzichtbar. In dieser Lektion ergänzen Sie Kode, um einen Fehler aus dem greetings-Modul zurückzugeben und diesen dann im rufenden Programm zu verarbeiten.

  1. Ergänzen Sie greetings/greetings.go um den weiter unten hervorgehobenen Kode.

    Es macht wenig Sinn, einen Gruß zurückzugeben, wenn nicht bekannt ist, wer gegrüßt werden soll. Geben Sie also einen Fehler an den Rufer zurück, wenn name leer ist. Kopieren Sie die entsprechenden Zeilen nach greetings.go und speichen die Datei.

    package greetings
    
    import (
        "errors"
        "fmt"
    )
    
    // Hello gibt einen Gruß an die genannte Person zurück.
    func Hello(name string) (string, error) {
        // Wenn kein Name, dann Fehler mit Fehlermeldung zurückgeben.
        if name == "" {
            return "", errors.New("Name leer")
        }
    
        // Wenn mit Name, dann
        // Rückgabe eines Grußtexts mit eingefügtem Namen.
        message := fmt.Sprintf("Hi %v. Wie geht's.", name)
        return message, nil
    }
    

    Im obigen Kode tun Sie folgendes:

    • Sie Ändern die Funktion so, dass sie zwei Werte zurückgibt: einen string und ein error. Ihr Rufer wird den zweiten Wert prüfen und daran sehen, ob ein Fehler aufgetreten ist. (Funktionen in Go können mehrere Werte zurückgeben. Mehr dazu in "Effective Go" (de).)
    • Sie importieren aus der Go-Standardbibliothek das Paket errors, um daraus die Funktion errors.New benutzen zu können.
    • Sie fügen eine if-Anweisung hinzu, um auf eine ungültige Anfrage hin zu prüfen (ein leerer String, wo ein Name sein sollte), und in diesem Fall einen Fehler zurückzugeben. Die Funktion errors.New liefert einen error, der Ihre Fehlernachricht enthält.
    • Für den Erfolgsfall ergänzen Sie nil (was heißen soll: kein Fehler) in der return-Anweisung. Daran kann der Rufer erkennen, dass die Funktion erfolgreich war.
  2. Verarbeiten Sie in der Datei hello/hello.go den error-Wert, der nun von der Funktion Hello zusätzlich zu dem bisherigen Wert zurückgegeben wird.

    Ergänzen Sie hello.go um den hier hervorgehobenen Kode:

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Festlegen der Eigenschaften des Standard-Loggers:
        // ein Präfix für den Log-Eintrag und ein Schalter, der die
        // Ausgabe von Zeit, Quelldateiname und Zeilennummer unterbindet.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // Anfordern einer Grußnachricht
        message, err := greetings.Hello("")
        // Einen zurückgelieferter Fehler auf der Konsole ausgeben,
        // dann das Programm verlassen.
        if err != nil {
            log.Fatal(err)
        }
    
        // Wenn ohne Fehler, Ausgabe der Nachricht auf der Konsole.
        fmt.Println(message)
    }
    

    Im obigen Kode tun Sie folgendes:

    • Sie richten das log-Paket so ein, dass am Beginn der Protokollnachricht der Kommandoname ("greetings"), aber keine Zeitangabe und keine Quelldateiinfos ausgegeben werden.
    • Sie weisen beide Rückgabewerte von Hello Variablen zu, also auch den error-Wert.
    • Sie vergessen Rosas Namen und geben als Argument für Hello einen leeren Sring mit, um so Ihre Fehlerverarbeitung testen zu können.
    • Sie fragen nach einem Nicht-nil error-Wert. In diesem Fall ist weitermachen sinnlos.
    • Sie nutzen Funktionen des log-Pakets aus der Standardbibliothek, um Ihre Fehlerinformation auszugeben. Tritt ein Fehler auf, so rufen Sie dessen Funktion Fatal zur Ausgabe und zum Abbrechen des Programms.
  3. Um zu bestätigen, dass der Kode funktioniert, starten Sie im hello-Ordner die hello.go-Datei mit go run.

    Da Sie einen leeren Namen mitgeben, tritt jetzt ein Fehler auf.

    $ go run .
    greetings: Name leer
    exit status 1
    

Das ist die übliche Fehlerbehandlung in Go: Geben Sie dem Rufer einen Wert zurück, damit er ihn prüfen kann.

Im nächsten Schritt werden Sie ein Go-typisches Slice benutzen, um einen zufällig gewählten Gruß zurückzugeben.

4. Rückgabe einer zufälligen Grußformel

In dieser Lektion ändern Sie den Kode so, dass er anstatt immer mit der gleichen Grußformel zu antworten eine von mehreren vorgefertigten Grußformeln zurückgibt.

Zu diesem Zweck benutzen Sie ein Slice. Ein Slice ist einem Array ähnlich, nur dass seine Größe sich dynamisch ändert, wenn Sie Elemente hinzufügen oder entfernen. Es ist einer der nützlichsten Typen in Go.

Sie werden nun ein kleines Slice mit drei Grußformeln einfügen, und lassen Ihren Kode eine von diesen zufällig wählen und zurückgeben. Mehr über Slices erfahren Sie im Blog-Beitrag "Go slices".

  1. Ändern Sie greetings/greetings.go so, dass Ihr Kode wie dieser hier ausschaut:
    package greetings
    
    import (
        "errors"
        "fmt"
        "math/rand"
        "time"
    )
    
    // Hello gibt einen Gruß an die genannte Person zurück.
    func Hello(name string) (string, error) {
        // Wenn kein Name, dann Fehler mit Fehlermeldung zurückgeben.
        if name == "" {
            return "", errors.New("Name leer")
        }
    
        // Erzeugen eines Grußes in zufälliger ausgewählter Form.
        message := fmt.Sprintf(randomFormat(), name)
        return message, nil
    }
    
    // init versorgt Anfangswerte von Variablen,
    // die später in Funktionen benutzt werden.
    func init() {
        rand.Seed(time.Now().UnixNano())
    }
    
    // randomFormat gibt aus einer Menge von Grußformeln
    // eine zurück; sie wird zufällig ausgewählt.
    func randomFormat() string {
        // Ein Slice von Formatstrings mit Grußformel.
        formats := []string{
            "Hi %v. Wie geht's.",
            "Schön dich zu sehen, %v.",
            "%v! Habe die Ehre!",
        }
    
        // Zufällige Wahl einer Grußformel mithilfe eines
        // zufällig ermittelten Index für das Slice.
        return formats[rand.Intn(len(formats))]
    }
    

    In diesem Kode tun Sie folgendes:

    • Sie fügen eine Funktion randomFormat hinzu, die ein zufällig ausgewähltes Grußformat zurückgibt. Beachten Sie, dass randomFormat mit einem Kleinbuchstaben beginnt, dass es also nur von Kode im selben Paket erreichbar ist; anders gesagt, randomFormat wird nicht exportiert.
    • Dort deklarieren Sie formats als Slice mit drei Grußformaten. Wenn ein Slice deklariert wird, enthält die eckige Klammer keine Größenangabe, wie hier bei []string. Damit sagt man Go, dass sich die Größe dss Arrays, das hinter dem Slice steckt, dynamisch ändern kann.
    • Sie benutzen das math/rand-Paket, um eine Zufallszahl zum Indexzugriff auf das Slice zu erhalten.
    • Sie ergänzen eine init-Funktion um dort das rand-Paket mit der aktuellen Zeit zu "impfen" (seed). init-Funktionen führt Go automatisch bei Programmstart aus, nachdem die globalen Variablen initialisiert worden sind. Mehr zu init-Funktionen finden Sie in "Effective Go" (de).
    • Um einen Formatstring zu erhalten, rufen Sie in Hello die randomFormat-Funktion, und benutzen dieses Format zusammen mit dem Wert von name zum Erzeugen des Grußes.
    • Geben Sie — wie bisher — den Gruß (oder einen Fehler) zurück.
  2. Ändern Sie in hello/hello.go den Kode so, dass er wie unten aussieht.

    Sie ergänzen hier nur den Namen Rosa (oder einen anderen nach Belieben) als Argument für den Aufruf der Hello-Funktion in hello.go:

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Festlegen der Eigenschaften des Standard-Loggers:
        // ein Präfix für den Log-Eintrag und ein Schalter, der die
        // Ausgabe von Zeit, Quelldateiname und Zeilennummer unterbindet.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // Anfordern einer Grußnachricht
        message, err := greetings.Hello("Rosa")
        // Einen zurückgelieferter Fehler auf der Konsole ausgeben,
        // dann das Programm verlassen.
        if err != nil {
            log.Fatal(err)
        }
    
        // Wenn ohne Fehler, Ausgabe der Nachricht auf der Konsole.
        fmt.Println(message)
    }
    
  3. Starten Sie hello.go auf der Kommandozeile im hello-Ordner, um zu bestätigen, dass Ihr Kode funktioniert. Starten Sie mehrmals und beachten Sie die wechselnden Grußformeln.
    $ go run .
    Schön dich zu sehen, Rosa.
    
    $ go run .
    Hi Rosa. Wie geht's.
    
    $ go run .
    Rosa! Habe die Ehre!
    

Nun wollen wir mithilfe eines Slices mehrere Personen grüßen. Das geschieht im nächsten Schritt.

5. Rückgabe von Grußformeln für mehrere Adressaten

Als letzte Änderung an Ihrem Modulkode werden Sie die Fähigkeit hinzufügen, Grüße für mehrere Personen auf eine einzige Anfrage hin zurückzugeben. Anders formuliert nehmen wir eine Eingabe aus mehreren Werten entgegen, verknüpfen diese Eingabewerte mit Ausgabewerten und erhalten eine Ausgabe, die ebenfalls mehrere Werte enthält. Dazu muss eine Menge von Namen an eine Funktion übergeben werden, die für jeden davon eine Grußformel zurückgeben kann.

Die Sache hat nur einen Haken. Würden wir den Parameter der Hello-Funktion von einem einzelnen Namen zu einer Namensmenge ändern, würde sich auch die Signatur der Funktion ändern. Wenn Sie also das Modul example.com/greetings schon veröffentlicht hätten und andere bereits in ihrem Kode Hello aufgerufen hätten, würden deren Programme unbrauchbar.

In so einem Fall ist es besser, eine neue Funktion mit neuem Namen zu schreiben. Diese neue Funktion wird dann andere Parameter entgegennehmen. So wird Rückwärtskompatibilität für die alte Funktion gewährleistet.

  1. Ändern Sie greetings/greetings.go so, dass Ihr Kode wie dieser hier ausschaut:
    package greetings
    
    import (
        "errors"
        "fmt"
        "math/rand"
        "time"
    )
    
    // Hello gibt einen Gruß an die genannte Person zurück.
    func Hello(name string) (string, error) {
        // Wenn kein Name, dann Fehler mit Fehlermeldung zurückgeben.
        if name == "" {
            return "", errors.New("Name leer")
        }
        // Erzeugen eines Grußes in zufälliger ausgewählter Form.
        message := fmt.Sprintf(randomFormat(), name)
        return message, nil
    }
    
    // Hellos gibt eine Map zurück, in der alle genannten Personen
    // mit einer Grußbotschaft verknüpft sind.
    func Hellos(names []string) (map[string]string, error) {
        // Eine Map, um Namen mit Grüßen zu verknüpfen.
        messages := make(map[string]string)
        // Iterieren über das mitgegebene Slice mit den Namen und
        // und Rufen der Hello-Funktion für jeden Namen.
        for _, name := range names {
            message, err := Hello(name)
            if err != nil {
                return nil, err
            }
            // Verknüpfen von Name und Gruß in der Map.
            messages[name] = message
        }
        return messages, nil
    }
    
    // init versorgt Anfangswerte von Variablen,
    // die später in Funktionen benutzt werden.
    func init() {
        rand.Seed(time.Now().UnixNano())
    }
    
    // randomFormat gibt aus einer Menge von Grußformeln
    // eine zurück; sie wird zufällig ausgewählt.
    func randomFormat() string {
        // Ein Slice von Formatstrings mit Grußformel.
        formats := []string{
            "Hi %v. Wie geht's.",
            "Schön dich zu sehen, %v.",
            "%v! Habe die Ehre!",
        }
        // Rückgabe einer zufällig ausgewählten Grußformel.
        return formats[rand.Intn(len(formats))]
    }
    

    In diesem Kode tun Sie folgendes:

    • Sie fügen eine Hellos-Funktion hinzu, dessen Parameter ein Slice mit Namen anstelle des einzelnen Namens ist. Sie ändern weiterhin den Typ des ersten Ergebnistyps von einem String zu einer Map, damit Sie die Namen verknüpft mit den jeweiligen Grußbotschaften zurückgeben können.
    • Sie lassen die neue Hellos-Funktion die schon bestehende Hello-Funktion aufrufen. Das vermeidet Kodeverdoppelung und es stehen trotzdem beide Funktionen zur Verfügung.
    • Sie erzeugen für die Grüße eine Map, die jeden der Namen (als Schlüssel) mit dem erzeugten Gruß (als Wert) verknüpft. In Go initialisiert man eine solche Map mit der folgenden Sntax: make(map[Schlüsseltyp]Wertetyp). Lassen Sie Hellos diese Map an den Rufer zurückgeben. Mehr zu Maps finden Sie in "Go maps in action"
    • Sie iterieren über alle der Funktion mitgegebenen Namen, stellen für jeden sicher, dass sein Wert nicht-leer ist, und verknüpfen den Namen mit einer Grußbotschaft. In der for-Schleife gibt range zwei Werte zurück: den Index und den Wert des aktuellen Elements. Den Index brauchen Sie nicht, also nutzen Sie den Leeren Bezeichner, (einen Unterstrich), um ihn zu ignorieren. Mehr dazu in "The blank identifier" (de)
  2. In Ihrem hello/hello.go übergeben Sie ein Slice mit Namen und geben dann den Inhalt der zurückerhaltenen Map aus.

    Ändern Sie in hello.go den Kode so, dass er wie hier aussieht:

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Festlegen der Eigenschaften des Standard-Loggers:
        // ein Präfix für den Log-Eintrag und ein Schalter, der die
        // Ausgabe von Zeit, Quelldateiname und Zeilennummer unterbindet.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // Ein Slice mit Namen drin.
        names := []string{"Rosa", "Klara", "Karl"}
    
        // Anfordern der Grußnachrichten für die Namen.
        messages, err := greetings.Hellos(names)
        if err != nil {
            log.Fatal(err)
        }
    
        // Wenn ohne Fehler, Ausgabe der Map auf die Konsole.
        fmt.Println(messages)
    }
    

    Mit diesem Änderungen tun Sie folgendes:

    • Sie legen eine Variable names als Slice-Typ mit drei Namen an.
    • Sie übergeben die names-Variable als Argument an die Hellos-Funktion.
  3. Um zu kontrollieren, dass der Kode auch funktioniert, wechseln Sie auf der Kommandozeile in den Ordner, der hello/hello.go enthält und starten Sie go run.

    Die Ausgabe ist eine Textdarstellung der Map, worin Namen mit Grüßen verknüpft sind, etwa so:

    $ go run .
    map[Karl:Karl! Habe die Ehre! Klara:Hi Klara. Wie geht's. Rosa:Schön dich zu sehen, Rosa.]
    

Diese Lektion stellte Ihnen Maps vor, um damit Schlüssel/ Wert-Paare abzubilden. Sie zeigte außerdem den Gedanken vom Erhalt der Rückwärtskompatibilität auf, indem für eine neue oder geänderte Funktionalität in einem Modul eine neue Funktion implementiert wurde. Mehr dazu in "Keeping your modules compatible"

Im nächsten Schritt werden Sie einen Komponententest mit Go-Standardmethoden erstellen.

6. Erstellen eines Tests

Nun, da Ihr Kode in einen stabilen Zustand ist (Gut gemacht!), fügen Sie noch einen Test an. Testen während des Entwickelns kann frühzeitig Fehler sichtbar machen, die sich mit den Änderungen eingeschlichen haben. In dieser Lektion erstellen Sie einen Test für die Hello-Funktion.

Go's Standardverfahren für Komponententests macht es einfach, ganz nebenbei zu testen. Mit ein paar Namenskonventionen, mit Go's testing-Paket und dem go test-Kommando können Sie Tests im Nu schreiben und ausführen.

  1. Erstellen Sie im greetings-Ordner eine Datei mit Namen greetings_test.go.

    Endet der Dateiname auf _test.go, so signalisiert das dem go-Kommando, dass diese Datei Testfunktionen enthält.

  2. Fügen Sie folgenden Kode in greetings_test.go ein und sichern die Datei.
    package greetings
    
    import (
        "testing"
        "regexp"
    )
    
    // TestHelloName ruft greetings.Hello mit einem Namen
    // und prüft auf einen gültigen Rückgabewert.
    func TestHelloName(t *testing.T) {
        name := "Kurt"
        want := regexp.MustCompile(`\b`+name+`\b`)
        msg, err := Hello("Kurt")
        if !want.MatchString(msg) || err != nil {
            t.Fatalf(`Hello("Kurt") = %q, %v, erwartet: %#q, nil`, msg, err, want)
        }
    }
    
    // TestHelloEmpty ruft greetings.Hello mit einem leeren String
    // und prüft, ob auch ein Fehler zurückgegeben wurde.
    func TestHelloEmpty(t *testing.T) {
        msg, err := Hello("")
        if msg != "" || err == nil {
            t.Fatalf(`Hello("") = %q, %v, erwartet: "", error`, msg, err)
        }
    }
    

    In diesem Kode tun Sie folgendes:

    • Sie implementieren die Testfunktionen im selben Paket wie den zu testenden Kode.
    • Sie erstellen zwei Testfunktionen zum Testen der Funktion greetings.Hello. Testfunktionen haben Namen der Form TestName, wobei Name etwas über den jeweiligen Test sagt. Außerdem erwarten Testfunktionen als Parameter einen Zeiger auf testing.T aus dem testing-Paket. Diesen Parameter brauchen Sie in Ihrem Test für Meldungen und zum Protokollschreiben.
    • Sie implementieren zwei Tests:
      • TestHelloName ruft die Hello- Funktion mit einem Wert name, zu dem die Funktion eine gültige Nachricht zurückliefern soll. Sollte die Funktion einen Fehler oder eine unerwartete Nachricht (eine, die den Namen nicht enthält) zurückgeben, benutzen Sie die Methode Fatalf des Parameters t, um eine Meldung auf der Konsole auszugeben und die Testausführung zu beenden.
      • TestHelloEmpty ruft die Hello- Funktion mit einem leeren String. Dieser Test soll bestätigen, dass Ihre Fehlerbehandlung funktioniert. Sollte die Funktion einen nicht-leeren String oder keinen Fehler zurückgeben, benutzen Sie die Methode Fatalf des Parameters t, um eine Meldung auf der Konsole auszugeben und die Testausführung zu beenden.
  3. Zum Ausführen der Tests starten Sie von der Kommandozeile aus dem greetings-Ordner heraus das Kommando go test.

    Das Kommando go test führt Testfunktionen (also solche, deren Namen mit Test beginnen) aus Testdateien (also deren Namen mit _test.go enden) aus. Sie können auch den Schalter -v (verbose) benutzen, um eine wortreiche Ausgabe mit der Liste aller Tests und deren Ergebnisse zu erhalten.

    Die Tests sollten erfolgreich sein.

    $ go test
    PASS
    ok      example.com/greetings   0.364s
    
    $ go test -v
    === RUN   TestHelloName
    --- PASS: TestHelloName (0.00s)
    === RUN   TestHelloEmpty
    --- PASS: TestHelloEmpty (0.00s)
    PASS
    ok      example.com/greetings   0.372s
    
  4. Pfuschen Sie nun in der greetings.Hello-Funktion, um mal einen scheiternden Test zu Gesicht zu bekommen.

    Die Testfunktion TestHelloName prüft den Rückgabewert für den der Hello-Funktion mitgegebenen Namen. Um ein Scheitern beobachten zu können, ändern Sie die Funktion greetings.Hello so, dass ihr Ergebnis nicht länger den Namen enthält.

    Ändern Sie die Hello-Funktion in greetings/greetings.go wie unten zu sehen. Beachten Sie, dass die hervorgehobenen Zeilen den Rückgabewert der Funktion so ändern, als wäre das Argument name versehenlich entfernt worden.

    // Hello gibt einen Gruß an die genannte Person zurück.
    func Hello(name string) (string, error) {
        // Wenn kein Name, dann Fehler mit Fehlermeldung zurückgeben.
        if name == "" {
            return "", errors.New("Name leer")
        }
        // Erzeugen eines Grußes in zufälliger ausgewählter Form.
        // message := fmt.Sprintf(randomFormat(), name)
        message := fmt.Sprint(randomFormat())
        return message, nil
    }
    
  5. Zum Ausführen der Tests starten Sie von der Kommandozeile aus dem greetings-Ordner heraus das Kommando go test.

    Sie starten go test dieses Mal ohne den Schalter -v. Die Ausgabe wird so nur Einzelheiten für die gescheiterten Tests enthalten, was übersichtlicher ist, wenn Sie viele Tests haben. TestHelloName sollte scheitern, TestHelloEmpty bleibt erfolgreich.

    $ go test
    --- FAIL: TestHelloName (0.00s)
        greetings_test.go:15: Hello("Kurt") = "Hi %v. Wie geht's.", <nil>, erwartet: `\bKurt\b`, nil
    FAIL
    exit status 1
    FAIL    example.com/greetings   0.182s
    

Im nächsten (und letzten) Schritt werden Sie erfahren, wie man kompiliert und installiert, um den Kode lokal ausführen zu können.

7. Fertigen und Installieren der Anwendung

In dieser letzten Lektion lernen Sie ein paar neue go-Kommandos kennen. Das bisher benutzte Kommando go run mag als Abkürzung zum Umwandeln und Starten eines Programms, an dem Sie viel zu ändern haben, nützlich sein, es erzeugt aber keine ausführbare Binärdatei

Diese Lektion führt zwei neue Kommandos zum Umwandeln von Kode ein:

  1. Starten Sie von der Kommandozeile im hello-Ordner das go build-Kommando, um den Kode in eine ausführbare Binärdatei zu wandeln.
    $ go build
    
  2. Zum Prüfen starten Sie von der Kommandozeile aus im hello-Ordner die hello-Binärdatei.

    Das Ergebnis kann anders aussehen, wenn Sie den Kode in greetings.go nach dem Testen geändert haben.

    • Unter Linux oder auf dem Mac:
      $ ./hello
      map[Karl:Karl! Habe die Ehre! Klara:Hi Klara. Wie geht's. Rosa:Rosa! Habe die Ehre!.]
      
    • Unter Windows:
      $ hello.exe
      map[Karl:Karl! Habe die Ehre! Klara:Hi Klara. Wie geht's. Rosa:Rosa! Habe die Ehre!.]
      

    Sie haben jetzt ihre Anwendung zu einer Binärdatei kompiliert, so dass sie ausführbar ist. Doch um sie jederzeit starten zu können, müssen Sie sich entweder in dem Ordner befinden, der die Binärdatei enthält, oder Sie müssen das Kommando mitsamt Pfad angeben.

    Deshalb installieren wir nun die ausführbare Datei, damit Sie sie ohne Angabe des Pfades starten können.

  3. Finden Sie den Go-Installationspfad, also wo das go-Kommando das aktuelle Paket installieren wird.

    Den Installationspfad kann man ermitteln, indem man ein go list-Kommando der folgenden Form ausführt:

    $ go list -f '{{.Target}}'
    

    Die Ausgabe ergibt beispielsweise /home/gopher/bin/hello, was bedeutet, dass Binärdateien nach /home/gopher/bin installiert werden. Diese Angabe brauchen Sie für den nächsten Schritt.

  4. Ergänzen Sie den Suchpfad Ihres Systems um den oben ermittelten Go-Installationspfad.

    So braucht ein Aufruf dann keine Angabe über den Ort Ihrer Binärdatei mehr.

    • Unter Linux oder auf dem Mac führen Sie folgendes Kommando aus:
      $ export PATH=$PATH:/path/to/your/install/directory
      
    • Unter Windows führen Sie folgendes Kommando aus:
      set PATH=%PATH%;C:\path\to\your\install\directory
      

    Alternativ: Wenn bereits ein Ordner für Binärdateien wie $HOME/bin in Ihrem Suchpfad existiert, und wenn Sie dort Ihre Go-Programme installieren wollen, dann können Sie für Go das Installationsziel durch Setzen der GOBIN-Variablen ändern. Dazu nutzen Sie das Kommando go env in der folgenden Form:

    $ go env -w GOBIN=/path/to/your/bin
    

    oder

    go env -w GOBIN=C:\path\to\your\bin
    
  5. Nachdem Sie den Suchpfad aktualisiert haben, starten Sie das Kommando go install zum Kompilieren und Installieren des Pakets.
    $ go install
    
  6. Starten Sie jetzt Ihre Anwendung ganz einfach mit ihrem Namen. Damit's interessanter wird, öffnen Sie ein neues Kommandofenster und starten hello von einem anderen Ordner aus.
    $ hello
    map[Karl:Karl! Habe die Ehre! Klara:Hi Klara. Wie geht's. Rosa:Rosa! Habe die Ehre!.]
    

Damit ist diese Übung zu Ende.

Zusammenfassung

Während dieser Übung haben Sie Funktionen erstellt, die Sie in zwei Module verpackt haben: eine mit Logik zum Erzeugen von Grußformeln; die andere als Konsument für die erstere.

Weitergehende Informationen zum Verwalten von Abhängigkeiten finden Sie unter "Managing dependencies", mehr zur Entwicklung von Modulen unter "Developing and publishing modules".

Und viele andere Eigenschaften der Programmiersprache Go werden präsentiert in der "Tour of Go".