Interfaces in Go: reine Typ-Sache

Seite 2: Interface Pollution – Verschmutzung vermeiden

Inhaltsverzeichnis

Interfaces zu definieren und zu verwenden ist in Go sehr einfach. Dieser Umstand führt in der Praxis häufig dazu, dass Entwicklerinnen und Entwickler zu viele Interfaces für Funktionen definieren. Nur weil es ohne großen Aufwand möglich ist, ist es nicht unbedingt sinnvoll. Sind im Code zu viele oder gar unnötige Interfaces definiert, spricht man von Verschmutzung: der Interface-Pollution. Doch wieviel ist zu viel?

Wie eingangs erläutert, müssen Entwickler auf Interfaces zurückgreifen, wenn sie eine Funktion für mehr als nur einen Typ verwenden wollen. Vor diesem Hintergrund lassen sich unnötige Interfaces leicht identifizieren. Interfaces mit nur einer Implementierung sind verzichtbar. Stattdessen genügt es, einen konkreten Typ in der Funktion zu verwenden und so eine unnötige Abstraktion zu vermeiden.

Innerhalb von Funktionen sind Eingangswerte oft von einem Interface-Typ. Während der Laufzeit steckt jedoch ein Wert hinter jedem Interface. Technisch liegt dabei ein Pointer auf einen darunterliegenden Wert an. Über diesen Pointer erfolgt dann der jeweilige Methodenaufruf. Das Interface kann man sich dabei wie eine Hülle um eine andere Variable vorstellen.

Abbildung 2 stellt die Referenz – hier also den Pointer – auf eine andere Variable als Karte dar. Das Interface verdeckt sämtliche Informationen zum darunterliegenden Typ. Durch den Schlitz ist nur die Methode des Interface sichtbar. Das sollten Entwicklerinnen und Entwickler aber stets bedenken, wenn sie anstelle eines konkreten Typs ein Interface definieren. Das Interface verdeckt viele Eigenschaften, auf die sich nicht mehr direkt zugreifen lässt. Auch deshalb ist es notwendig zu prüfen, ob nicht auch eine Interface-Pollution vorliegt.

Variable verdeckt vom Interface (Abb. 2).

Da eine Interface-Variable nur ein Pointer auf eine Variable ist, kann es passieren, dass noch kein Wert zugewiesen wurde. In diesem Fall ist der Wert nil – und die Hülle bleibt leer (s. Abb. 3).

Das nil-Interface (Abb. 3).

Ohne Karte ist aber kein Zugriff auf eine Funktion möglich. Erfolgt dennoch ein Versuch, die Methode aufzurufen, kommt es zu einer Panik wie in Listing 5:

Listing 5: Verwenden eines Interface ohne darunterliegende Variable

var s Stringer // Wert ist nil
fmt.Println(s.String())
// panic: runtime error: invalid memory ...

Da eine Panik zu einem harten Programmabbruch führt, sollten Entwicklerinnen und Entwickler stets auf nil prüfen, um solche Situationen zu vermeiden.

Listing 6: Auf nil prüfen

var s Stringer
if s != nil {
        fmt.Println(s.String())
}
// ...

Go bietet ein Interface, dass sich für jeden beliebigen Typ als Hülle verwenden lässt: das leere Interface interface{} (s. Abb. 4). Diesen Typ nutzen vor allem Entwickler häufig, die aus dynamisch typisierten Sprachen wie JavaScript kommen.

Listing 7: Das leere Interface

var a interface{}
var b int = 2
a = b
var c float64 = 3.1
a = c
var d string = "Hallo"
a = d

Das leere Interface (Abb. 4).

Das leere Interface besitzt keine Methode. Es ist nur eine Hülle ohne weitere Informationen. In diese Hülle passt somit jeder denkbare Typ – jedoch mit dem Nachteil, dass stets alle Elemente des Werts verdeckt sind. Ein leeres Interface bietet überhaupt keinen Kontext. Das macht es schwer, den Code zu verstehen. Es ist nicht möglich zu erkennen, wofür der Autor des Codes die Variablen verwenden will. Auch der Compiler kann bei leeren Interfaces nicht helfen. Daraus ergeben sich häufig Laufzeitfehler, sofern sich nicht alle Eventualitäten im Code abfangen lassen. Obwohl der Unterschied zum Stringer-Interface nur in der einen Methode liegt, ist das leere Interface deutlich nichtssagender. Entwicklerinnen und Entwickler sollten diesen Typ daher möglichst vermeiden.