J'ai jusqu'à présent évité l'utilisation de log.Fatal
, mais j'ai récemment découvert par hasard ces questions; couverture de code et tests-using-log-fatal .
L'un des commentaires des 100 questions de couverture de code dit:
... Dans la grande majorité des cas
log.Fatal
ne doit être utilisé que dans les fonctions main ou init (ou peut-être certaines choses qui ne doivent être appelées que directement à partir de celles-ci) "
Cela me fait réfléchir, alors j'ai commencé à regarder le code de bibliothèque standard fourni avec Go. Il existe de nombreux exemples où le code de test de la bibliothèque utilise log.Fatal
ce qui semble bien. Il y a quelques exemples en dehors du code de test, comme dans net/http
, illustré ci-dessous:
// net/http/transport.go
func (t *Transport) putIdleConn(pconn *persistConn) bool {
...
for _, exist := range t.idleConn[key] {
if exist == pconn {
log.Fatalf("dup idle pconn %p in freelist", pconn)
}
}
...
}
S'il est préférable d'éviter d'utiliser log.Fatal
, pourquoi est-il utilisé dans les bibliothèques standard, je me serais attendu à ce que je retourne une erreur. Il semble injuste pour l'utilisateur de la bibliothèque de provoquer os.Exit
à appeler et ne donnant aucune chance au nettoyage de l'application.
Je peux être naïf, donc ma question comme meilleure pratique semble être d'appeler log.Panic
qui peut être récupéré et mon application stable théorique à long terme pourrait avoir une chance de renaître de ses cendres.
Alors, que dirait la meilleure pratique pour Go à propos du moment où la connexion devrait être utilisée?
C'est peut-être juste moi, mais voici comment j'utilise log.Fatal
. Conformément aux conventions UNIX, un processus qui rencontre une erreur doit échouer le plus tôt possible avec un code de sortie différent de zéro. Cela m'a amené aux directives suivantes pour utiliser log.Fatal
Lorsque…
func init()
, car celles-ci se produisent respectivement lors du traitement des importations ou avant l'appel de la fonction principale. Inversement, je ne fais que des choses n'affectant pas directement l'unité de travail que la bibliothèque ou cmd est censée faire. Par exemple, je configure la journalisation et vérifie si nous avons un environnement et des paramètres sains. Pas besoin d'exécuter main si nous avons des drapeaux invalides, non? Et si nous ne pouvons pas donner une rétroaction appropriée, nous devrions le dire tôt.cp
, et qu'elle a commencé à être non interactive et à copier récursivement un répertoire. Supposons maintenant que nous rencontrons un fichier dans le répertoire cible qui porte le même nom (mais un contenu différent) qu'un fichier à y copier. Comme nous ne pouvons pas demander à l'utilisateur de décider quoi faire et que nous ne pouvons pas copier ce fichier, nous avons un problème. Parce que l'utilisateur supposera que les répertoires source et cible sont des copies exactes lorsque nous aurons terminé avec le code de sortie zéro, nous ne pouvons pas simplement ignorer le fichier en question. Cependant, nous ne pouvons pas simplement l'écraser, car cela pourrait potentiellement détruire des informations. Il s'agit d'une situation que nous ne pouvons pas récupérer par demande explicite de l'utilisateur, et j'utiliserais donc log.Fatal
Pour expliquer la situation, obéissant ainsi au principe de l'échec le plus tôt possible.Marcus, je suis tombé sur votre réponse, et je pense qu'elle est excellente et très perspicace, et j'ai tendance à être d'accord avec votre ventilation. Il est très difficile de généraliser, même si j'y ai réfléchi un peu plus et en tant que débutant. Je pense que sur le plan théorique, si nous recherchons les meilleures pratiques en informatique, indépendamment du système d'exploitation, du framework de package ou de la bibliothèque, la responsabilité d'un enregistreur est de simplement se connecter. À tous les niveaux, les responsabilités d'un enregistreur:
Un package de journalisation ou tout autre package n'a pas et ne devrait pas avoir le droit de planter un programme s'il fonctionne correctement. Tout middleware ou bibliothèque doit suivre un modèle throw/catch, avec la possibilité pour toutes les exceptions levées d'être interceptées par l'appelant. C'est également un bon modèle à suivre dans une application, car lorsque vous construisez des fondations et des packages qui alimentent diverses parties de votre application, et potentiellement d'autres applications, ils ne doivent jamais planter une application directement. Au lieu de cela, ils devraient lever une exception fatale, permettant au programme de gérer. Je pense que cela répond également à certains de vos points Marcus, car cela fonctionne pour alerter l'appelant immédiatement lorsqu'il n'est pas pris, comme un crash fatal.
Dans la plupart des cas, je peux profiter de l'exploitation de log. Je ne pense pas que cela ait du sens en tant qu'approche à long terme pour gérer les erreurs fatales dans les packages.