In Go werden die Schnittstellen fmt.Stringer und error verwendet, um zu steuern, wie der Wert einer Struktur in einen String umgewandelt wird und wie er Fehler implementiert. Diese Schnittstellen bieten universelle Möglichkeiten zum Protokollieren, Ausgeben und Arbeiten mit Fehlern, wodurch der Code flexibler und verständlicher wird.
Historie der Frage:
Seit den ersten Versionen von Go ist die Schnittstelle Stringer entscheidend für die ansprechend kontrollierte Ausgabe von Strukturen. Die Schnittstelle error ist grundlegend für die Fehlerbehandlung auf allen Ebenen des Codes.
Problem:
Häufig erhalten Programmierer wenig informative Ausgaben oder unerwartete Fehlermeldungen, weil sie diese Methoden nicht implementiert haben oder dies nicht standardmäßig getan wurde. Falsch implementierte Methoden können auch zu rekursiven Ausgaben, Paniken und schwer lesbaren Fehlern führen.
Lösung:
Beispielcode:
package main import "fmt" type User struct { Name string ID int } func (u User) String() string { return fmt.Sprintf("User<%d:%s>", u.ID, u.Name) } type MyError struct { Msg string } func (e MyError) Error() string { return "MyError: " + e.Msg } func main() { u := User{Name: "Bob", ID: 10} fmt.Println(u) // ruft String() auf err := MyError{Msg:"fail"} fmt.Println(err) // ruft Error() auf }
Wichtige Merkmale:
Muss man String() oder Error() als Wert- oder Zeiger-Methoden implementieren, oder kann man Zeiger verwenden?
Beide Varianten sind zulässig, aber die Implementierung mit Zeigerempfänger und Wertempfänger beeinflusst, auf welchen Objekttypen die Methode funktioniert. Üblicherweise verwendet man Zeigerempfänger für veränderbare Strukturen.
func (u *User) String() string {...}
Kann man fmt.Sprintf innerhalb von String() oder Error() verwenden?
Ja, aber man muss darauf achten, keine endlose Rekursion auszulösen (zum Beispiel, wenn man eine Struktur mit demselben Typ innerhalb von String() ausgibt). Es wird empfohlen, die Verwendung von fmt.Print innerhalb von String() zu vermeiden, wenn intern wieder String() aufgerufen wird.
func (u User) String() string { return fmt.Sprintf("%v", u.Name) } // sicher
Was passiert, wenn die Methode Error() eine leere Zeichenfolge zurückgibt?
Fehler mit einer leeren Zeichenfolge werden als gültige Werte des Typs error angesehen, aber das Protokollieren macht keinen Sinn. Die Schnittstelle error definiert kein Verhalten für eine leere Zeichenfolge, es ist jedoch allgemein anerkannt, immer eine informative Nachricht bereitzustellen.
Ein Entwickler gibt eine Struktur über %+v aus, ohne String() implementiert zu haben, und erhält dadurch Müll-Dumps von Feldern in den Logs.
Vorteile:
Nachteile:
Der Teamleiter zwingt das Team, String() und Error() für alle öffentlichen Strukturen zu implementieren. Dadurch wird die Geschäftslogik zentralisiert Fehler behandelt, und die Admin-Oberfläche sowie Debug-Logs werden lesbar.
Vorteile:
Nachteile: