Ich habe die letzten Tage genutzt mich in die Programmiersprache Go einzuarbeiten.
Nachdem ich vor allem Beispiele abgetippt und mir Fremdcode angeschaut habe, schrieb ich heute mein erstes eigenes Programm. Wenig innovativ handelt es sich um eine Variante von Hello World, wobei ich versucht habe, einige erweiterte Techniken anzuwenden.
Das Programm ist in der Lage abhängig vom Vorhandensein von Kommandozeilenparametern eine von zwei Greeter
-Implementierungen
zu wählen, die jeweils einen String zurückgeben. Dieser wird dann gefolgt von einem Zeilenumbruch ausgegeben.
Installation von Go
Ich habe zuerst Go 1.10 aus den Paket-Repositories von Archlinux installiert und in der Datei $HOME/.bashrc
die Variable $GOPATH auf die Wurzel meiner Go-Repositories gesetzt.
Das erste Programm
Dann habe ich in diesem Verzeichnis die Struktur für mein Programm angelegt:
mkdir -p softmetz.de/hello/greeter
Das Hauptprogramm
In softmetz.de/hello
wurde die Datei main.go
angelegt:
|
|
Das ist die Einstiegsdatei für das Go-Programm. In Go ist es Pflicht, dass diese Dateien im Package main
sind.
Der import
-Block enthält zwei Einträge. Während fmt
aus der Standardbibliothek Funktionen zur
Formatierung und Ausgabe von Text bereitstellen, ist softmetz.de/hello/greeter
eine Bibliothek, die
zum Programm gehört.
Die Funktion main
ist dann relativ übersichtlich. Zuerst wird eine Instanz vom Interface Greeter erzeugt.
greeter
ist dabei das Package der Bibliothek und NewGreeter
ist ein Funktion, die die Rolle einer Factory
innehält.
Im nächsten Schritt wird die Methode Greeting
aufgerufen, welche die Begrüßung als String zurückliefert.
Zuletzt wird die Begrüßung gefolgt vom Umbruch auf der Standardausgabe gedruckt.
Die Bibliothek greeter
Die Bibliothek besteht aus zwei Dateien. Einmal der eigentlichen Implementierung und dann noch einem Test.
Zuerst die Datei greeter.go
:
|
|
Diesmal entspricht der Package-Name greeter
dem Namen der Bibliothek. Es werden die Bibliotheken log
, os
und strings
aus der Standard-Bibliothek von Go importiert.
In Zeile 9 wird das Interface Greeter
erstellt. Dieses hat eine Methode Greeting
, welche einen String liefert. Go spricht
selbst davon, dass es gleichzeitig eine objektorientierte Programmiersprache ist und auch wieder nicht. Das wird unter anderem
daran deutlich, dass es möglich ist, mit Interface Polymorphismus zu realisieren, aber das Konzept von Klassen und Vererbung
in Go nicht existiert. Stattdessen wird auf Aggregation und Komposition gesetzt.
In den Zeilen 13 bis 17 wird DefaultGreeter
implementiert. Auffällig ist, dass nirgends auf Greeter
verwiesen wird. In
Go wird ein Interface von allen Typen implementiert, die dem Interface entsprechen, also dessen Funktionen mit den gleichen
Parametern und Return-Werten enthält. Damit ist der DefaultGreeter
geeignet, die Rolle eines Greeter
zu vertreten.
In diesem Zusammenhang ist noch wichtig, dass Methoden zu Typen nicht innerhalb der geschweiften Klammern der Typ-Defintion
implementiert werden, sondern ausserhalb. Das ist so ähnlich wie in C++, wo die Klasse im Header definiert wird und die Methoden
dann in der C++-Datei implementiert werden. Das Konzept von this
sowie die Verknüpfung zur klasse wird über die erste
Klammer in Zeile 15 gesteuert. In diesem Fall ist der DefaultGreeter
als g
in der Methode erreichbar.
Die zweite Implementierung von Greeter
ist OsArgsGreeter
. Diese hängt an ein freundliches “Hello” alle Parameter gefolgt
von einem Ausrufzeichen an. Die Kommandozeilenparameter bekommt man in Go über das Array/Slice os.Args
. Hier ist zu
beachten, dass der erste Parameter der Name des Programms ist, also in Etwa wie bei Bash, wo ja $0 auch der Name des Skripts
ist und der erste Parameter $1.
Ab Zeile 26 kommt dann die Factory-Methode, die auch in main.go
schon bemüht wurde. Darin steckt ein Switch-Konstrukt,
welches die Bedingungen in case
-Blöcken prüft. Trifft keine Bedingung zu, wird der default
-Block ausgeführt. Bei Go
muss man keine break
-Anweisungen verwenden, das Konzept des “Durchfallens” existiert nicht.
Testen muss sein
Last but not least braucht ein komplexes Programm natürlich auch Testfälle. Go bringt mit go test
und der testing
-Bibliothek
ein eigenes Testframework mit.
Der Code liegt in einer Datei, die *_test.go
heisst, in diesem Fall greeter_test.go
:
|
|
Das Package lautet diesmal greeter_test
. Das ist keine Regel, aber ich habe es so aus den Testfällen
der Go-Standardbibiliothek übernommen.
Der Import-Block ab Zeile 3 importiert wieder os
und die greeter
-Bibliothken. Außerdem wird das
Test-Framework testing
eingebunden.
Testfälle liegen in Methoden, die Test...(t *testing.T) heissen. Ganz wichtig ist, dass in diesem Framework nur Fehlersituationen betrachtet werden, es gibt kein
Assert` oder ähnliche Konzepte.
Die Tests sollten relativ selbsterklärend sein. Hervorzuheben sind lediglich die Zeilen 12 und 26 verdienen Erklärung. Wie oben schon beschrieben, schreibt Go in das Array os.Args alle Kommandozeienparameter. Da es sich um ein normales Array handelt und nicht etwa um eine Methode, kann es von überall überschrieben werden. Für den Test ist es super, aber ich stelle mir gerade vor was passiert, wenn eine Bibliothek die Parameter modifiziert und damit z.B. Sicherheitsfeatures abschaltet.
Fazit
Go ist eine sehr interessante Sprache und ich plane damit noch mehr zu machen als dieses kleine Programm. Insbesondere interessiert es mich, inwieweit sich Go für Webanwendungen eignet, die sich einfach selbst hosten lassen um damit zu helfen, Silos aufzubrechen.
Das Testframework ist auf den ersten Blick etwas rudimentär, z.B. finde ich t.Errorf("Test failed, expected: %s, got: %s", expected, actual)
sehr redundant, bei JUnit gibt es da mehr out of the box. Es gibt wohl auch schon erste Aufsätze auf das Basis-Testing. Mal
schauen.
Ansonsten lese ich gerade noch Go at Google: Language Design in the Service of Software Engineering worin die Designentscheidungen dargelegt werden.