Nul n'est pas toujours nul
"Quoi? Qu'est-ce qui est écrit ici?" demandez-vous. Maintenant, je vais tout éteindre.
Quand j'ai commencé à apprendre la langue, je ne pensais pas que j'arriverais à cette affaire étroite. Il n'est pas non plus rationnel de modifier une collection itérable.
Par exemple:
func Foo() error { var err *os.PathError = nil return err } func main() { err := Foo() fmt.Println(err) // <nil> fmt.Println(err == nil) // false }
WAT!
Qu'est-ce qu'une interface?
Accédez au fichier de package go runtime / runtime2.go et voyez:
type itab struct { // 40 bytes on a 64bit arch inter *interfacetype _type *_type ... }
Une interface stocke le type d'interface et le type de valeur lui-même.
La valeur de n'importe quelle interface, et pas seulement l'erreur, est nulle dans le cas où les valeurs ET le type ET sont nuls.
La fonction Foo renvoie nil de type * os.PathError, nous comparons le résultat avec nil de type nil, d'où leur inégalité.
Beaucoup le savaient peut-être, mais peu de gens pensent comment s’y mettre en pratique.
Mon exemple
type Response struct { Result ResponseResult `json:"result,omitempty"` Error *ResponseError `json:"error,omitempty"` } type ResponseError struct { Message string `json:"message"` } func (e *ResponseError) Error() string { return e.Message } ... func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... var res handlers.Response _ = json.Unmarshal(body, &res) if res.Error == nil { return } return s.NotifyError(err) }
La réponse a toujours un résultat ou une erreur.
S'il y a une erreur, nous l'envoyons si nécessaire via le service de notification.
A l'intérieur du service, la méthode Error est appelée, et puisque notre valeur est nulle, nous paniquons.
Que faire
Renvoie une interface strictement d'un type d'interface.
En cas d'erreur - type d'erreur.
- Ajouter une déclaration d'erreur de type
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... var res Response _ = json.Unmarshal(body, &res) var err error = res.Error return s.NotifyError(err) }
À ma grande surprise, cette technique ne fonctionne pas non plus.
Il s'avère que lors de l'attribution d'une valeur à la variable err, nous lui transmettons également les informations initiales sur le type, qui n'est pas nul.
- Essayons d'obtenir notre type de source à partir du type d'interface et vérifions sa valeur.
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... if e, ok := err.(*ResponseError); ok && e == nil { return s.NotifyError(err) } return nil }
Oui, cette technique fonctionne.
Mais pour être honnête, nous ne pouvons pas nous permettre de vérifier tous les types d'erreurs que nous transmettrons.
Il peut s'agir de toutes les erreurs du pilote de base de données, de toutes nos erreurs internes et d'autres ordures.
Quelle est l'option la plus rationnelle que je vois:
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { var err error ... var res Response _ = json.Unmarshal(body, &res) if res.Error != nil { return s.NotifyError(err) } return nil }
Tout d'abord, nous avons déclaré une variable d'erreur de type, comme il s'avère avec la valeur et le type nil.
Et avant de passer notre type et notre valeur à cette variable, vérifions notre type et sa valeur sur nil.
Cela nous permettra de ne pas tomber dans la panique.
En fin de compte
Vous pouvez aller encore plus loin et implémenter une erreur «facultative» avec le type Response, OptionalError ou ErrorOrNil, comme ceci:
func (r *Response) ErrorOrNil() error { if r.Error == nil { return nil } return r.Error }
Remarque
Dans les notes du wiki Go, la révision du code est une note dans la rubrique relative à l'interface: renvoyez plutôt un type concret et laissez le consommateur se moquer de l'implémentation du producteur.
Je note que les danses ci-dessus ne concernent pas cela.
Mes notes vous permettent de ne pas tomber dans la panique quand vous savez que vous voulez retourner de l'intérêt, et en cas d'erreurs, vous voulez toujours retourner l'interface.
Mais si vous pouvez vous permettre de retourner un certain type, retournez-le.
Les références
go-internals
Je suis
LinkedIn
Télégramme
Twitter
Github