Go: proposition : laisser "if err != nil" seul ?

Créé le 28 juin 2019  ·  314Commentaires  ·  Source: golang/go

La proposition Go2 #32437 ajoute une nouvelle syntaxe au langage pour rendre le passe-partout if err != nil { return ... } moins encombrant.

Il existe diverses propositions alternatives : #32804 et #32811 car l'original n'est pas universellement apprécié.

Pour jeter une autre alternative dans le mix : Pourquoi ne pas le garder tel quel ?

J'en suis venu à aimer la nature explicite de la construction if err != nil et en tant que telle, je ne comprends pas pourquoi nous avons besoin d'une nouvelle syntaxe pour cela. Est-ce vraiment si mauvais que ça ?

FrozenDueToAge Proposal Proposal-Hold error-handling

Commentaire le plus utile

il ne devrait y avoir qu'une seule façon de faire les choses

Tous les 314 commentaires

Je seconde ceci. J'aime vraiment la façon dont décorer chaque erreur avant de la renvoyer ajoute une documentation lisible par l'homme à la source (généralement, nous formatons nos erreurs comme "ne pouvait pas [ce que je fais dans ces lignes de code] : [erreur précédente]") et aussi aux utilisateurs erreurs de lecture.

Les erreurs générées de cette manière sont extrêmement informatives et beaucoup plus faciles à lire que les traces de pile. Les erreurs imprimées qui incluent des traces de pile supposent généralement que vous avez un accès direct aux sources (les administrateurs peuvent ne pas avoir cet accès) et que vous connaissez réellement votre chemin dans le code.

Les erreurs sans aucune forme de contexte ou de traçage (la chaîne nue "EOF") sont absolument inutiles. Je pense qu'avoir des raccourcis qui facilitent le retour des erreurs nues fera que les programmes Go imprimeront beaucoup d'erreurs inutiles.

Si quoi que ce soit, nous devrions pousser et soutenir les erreurs de décoration avec le contexte, peut-être avec de nouvelles règles vétérinaires et de peluches.

J'aime aussi le contrôle d'erreur explicite. try est déroutant et le retour implicite est étrange.

Je pense qu'au lieu de repenser les erreurs, nous pourrions essayer une approche alternative pour raccourcir ces contrôles.

Voici un exemple auquel je ne suis pas forcément d'accord :

value, err := foo()
return err if err != nil

Cela permettrait une approche plus courte mais toujours explicite. Et ça permettrait d'ajouter du contexte !

Cela dit, les ifs en ligne sont une chose Ruby et ne se sentent pas très Goish, mais ce n'est qu'un brainstorming. Peut-être trouverons-nous autre chose.


EDIT : j'ai ajouté une proposition pour cela ici : https://github.com/golang/go/issues/32860

il ne devrait y avoir qu'une seule façon de faire les choses

[...]Pourquoi ne pas le garder tel quel ?

Je pense qu'il est juste de dire que nous connaissons tous la réponse à cette question. Il suffit d'aller lire l'une des différentes propositions pour connaître la réponse si sincèrement vous ne la connaissez pas.

OMI, il y a trop peu de détails ici pour que nous ayons une discussion ciblée (c'est-à-dire que je ne pense pas que cela puisse être considéré comme une proposition) et cela se transformera bientôt en un autre hangar à vélos plein de secousses et d'idées qui rendent le code moins lisible .

Tellement ça.

On peut dire que je suis entré dans Go à cause de cette gestion d'erreur explicite. Il se situe quelque part entre le try-catch implicite que de nombreux langages recherchent et les types de fonctions comme Option ou Maybe, qui favorisent le retour à l'utilisateur et la gestion explicite.

Je ne sais pas si une nouvelle construction résoudrait vraiment ce problème. Si vous avez enveloppé if err := nil dans une fonction d'assistance comme celle-ci, cela pourrait aider un peu (pardonnez mon rouillé Go):

func handleErr(err error, cb func(error)) {
        if err := nil {
                cb(err)
        }
}

Mais le problème qui rend cette fonction d'assistance moins utile en général est le système de types, qui est un sujet différent.

Je seconde ceci. if err != nil { return err } ne fait partie d'aucun code de notre base de code. Par conséquent, l'essai "macro" n'a aucun sens. Nous ne retournons que les erreurs encapsulées, avec un message décrivant le contexte.

L'ajout de contexte via defer n'a pas de sens non plus, car nous voulons renvoyer différents messages d'erreur pour distinguer les différents types d'erreurs. Un try(fn(), "my error message: %w") pourrait être utile cependant. Mais même dans ce cas, la construction if err != nil peut toujours être préférable, en raison des longueurs de ligne plus courtes.

Franchement, je ne veux pas d'un retour implicite que try fournit. Si nous avions des génériques, je préférerais de loin une solution qui utilise plutôt un comportement monade.

type Result<T> interface {
  Expect(err error) T
  OrElse(defaultValue T) T
}

func From<T>(value T, err error) Result<T> { ... }

Pour moi, c'est beaucoup plus propre que la fonction intégrée actuellement proposée, bien que d'autres modifications soient nécessaires pour ce qui précède car vous auriez une prolifération de méthodes renvoyées (valeur, erreur) et résultat

La proposition actuelle de try , n'ayant aucun moyen de décorer explicitement les erreurs, ne répond pas à mes besoins. Je ne peux pas imaginer l'utiliser. Franchement, ça pourrait aussi bien s'appeler code_smell .

Cela n'a peut-être pas de sens de le changer, car le mauvais problème essaie d'être résolu.

Le code que nous connaissons n'est pas la gestion des erreurs.

if err != nil {
  return err
}

Il s'agit nil gestion de l'erreur

Si je devais démontrer cela dans une autre langue, Ruby.

begin
 some_method_that_raises_an_error
rescue => e # catch any exception
  retry e        # throw it up again
end

Cela relaie le même comportement que le code golang. Lorsque nous détectons qu'une exception s'est produite et que nous la relâchons ensuite. Nous le jetons simplement dans la pile.

Dans le golang, nous le return .

Où se produit le _traitement des erreurs_ réel ?

Nous avons tous eu des expériences similaires de l'échec de ce modèle. Par exemple, recevoir une erreur file not found puis passer un long moment à rechercher le _lanceur_ d'origine de cette erreur.

C'est pourquoi je pense que la proposition try (et d'autres) sont fautives. Nous n'avons pas un bon modèle pour gérer réellement les erreurs.

J'ai vu la vérification de la chaîne err.Error() , les assertions de type, etc. pour inspecter réellement l'erreur.
Nous avons besoin d'un modèle pour cette incohérence. Il semble que xerrs résolve ce problème, mais il ne semble pas encore complet.

Je soutiens le maintien de err!=nil check tel quel.

Chaque fois que je creuse dans une importante base de code Go, je me demande comment je pourrais réduire une partie du passe-partout. Je reviens toujours à :

  • Ces chemins de code existent d'une manière ou d'une autre.
  • Même si vous n'êtes pas obligé de masquer le chemin de code, donner aux gens la possibilité de le masquer en fera le comportement par défaut (car apparemment, nous mesurons toujours à quel point une langue est difficile à utiliser par le nombre de lignes).
  • Si le comportement par défaut masque les chemins de code, je serais à la recherche de nouveaux bogues de "nettoyage manquant".
  • La signification et les modèles d'erreurs renvoyées sont suffisamment diversifiés pour que cette proposition ne capture qu'une partie du problème perçu
  • Si seulement une partie est capturée, nous obtiendrons sûrement un tas de solutions
  • Avec un tas de solutions viendrait la tentation d'avoir une magie adaptative de cas d'utilisation pour les enrouler
  • Que s'il s'agissait réellement d'un problème, les gens sont libres de créer leur propre solution simple ou d'utiliser un modèle adopté en masse. Je n'ai rien vu de tel. Peut-être que je n'ai pas assez cherché.

Le suivi des problèmes est utile pour de nombreuses choses, mais il n'est pas utile pour une discussion détaillée d'un sujet complexe. Le suivi des problèmes ne fournit aucun fil et répondre à un message spécifique est difficile. Puisqu'il n'y a pas de proposition réelle ici, juste une réponse à d'autres propositions, je vous encourage vraiment fortement à prendre cette discussion sur la liste de diffusion golang-nuts.

Si je peux me permettre, je crois que c'est la réponse. Cette nouvelle proposition d'erreur est en conflit direct avec les objectifs du langage.

La raison pour laquelle j'aime le golang est sa simplicité et son utilisation claire du flux de contrôle. L'une des choses que je méprise le plus à propos de Java est la construction try throw. C'est tellement dégoûtant. Il encourage la gestion des erreurs terribles. L'envoi d'exceptions dans la pile d'appels est une méthode horrible et dégoûtante de gestion du flux de contrôle. De plus, cela encourage à tout envelopper dans un chèque géant et à l'appeler un jour au lieu d'une auto-documentation et d'une gestion explicite de chaque situation d'erreur.

If err != nil encourage une bonne gestion des erreurs, est auto-documenté et encourage une bonne documentation quant au cas spécifique, et c'est honnêtement l'une des choses que j'aime le plus à propos de go. Faire cette nouvelle interruption de flux de contrôle, en utilisant des retours et des paramètres désordonnés et quelque peu ambigus, et une sémantique déroutante n'est pas dans l'esprit du langage que j'ai appris à adorer.

La verbosité n'est pas une mauvaise chose. La verbosité est inutile, mais je dirais que la gestion des erreurs de go n'est pas inutile. Cela fait partie du charme de la langue.

Je ne pourrais pas être plus d'accord. La gestion explicite des erreurs est l'une des meilleures fonctionnalités du langage IMO. J'ai toujours l'impression que beaucoup de ceux qui sont dérangés par cela ne sont pas encore habitués.

Il n'est pas bon que les problèmes soient séparés, mais je pense que deux opinions sont fusionnées en une seule opinion dans ce cas.

  1. Nous n'aimons pas la nouvelle syntaxe (essayez ou la nouvelle syntaxe if-err)
  2. De toute façon, nous ne voulons pas ajouter de nouvelle syntaxe

Les icônes de vote GitHub ne peuvent pas interpréter la seconde.

La gestion explicite des erreurs dans go est l'une des raisons pour lesquelles j'aime golang. Je ne comprends pas pourquoi un développeur de go voudrait qu'il en soit autrement. Je pense que la proposition d'ajouter une nouvelle syntaxe vient principalement de personnes à l'aise avec la syntaxe utilisée dans d'autres langues. cela peut prendre un certain temps pour s'y habituer, mais cela fonctionne parfaitement une fois que vous vous y êtes habitué.

J'ai écrit #32811 et je soutiens davantage cette proposition... Je préfère laisser la gestion des erreurs seule. Je pense que les réactions des emoji à cette proposition en disent long.

Personnellement, je suis d'accord pour laisser le traitement des erreurs tel quel. L'une des choses que j'aime à propos de Go, c'est que le langage est minimal et qu'en général, il y a une façon de faire les choses. En ajoutant une nouvelle syntaxe pour la gestion des erreurs, nous allons créer un monde où x% du code utilise la méthode actuelle et y% utilise la nouvelle méthode. Cela, parmi d'autres problèmes déjà discutés, créera des bases de code incohérentes. Personnellement, je ne pense pas que la valeur de la nouvelle syntaxe de gestion des erreurs vaut les compromis, car je considère que la syntaxe existante est suffisante/suffisante.

En tant que nouveau venu dans le Golang, l'une des choses que je trouve rafraîchissantes dans le langage est la gestion explicite des erreurs. J'ai beaucoup travaillé en Java, Ruby, Python et Node, et gérer les erreurs est tellement plus onéreux qu'en Go. Je préférerais voir le « chemin » clair des erreurs, plutôt que de le laisser sous-entendre par une construction de langage qui le rend plus vague.

ˋretourner ... si ...ˋ suggestion de @andreynering est en fait assez intelligent à mon humble avis. Maintient le code explicite (pas de rupture de flux de contrôle cachée) tout en réduisant le passe-partout (une ligne).

D'accord, laissez if err != nil tranquille.

Je préfère le format actuel. Il est clair et un modèle facile à enseigner. La mise à niveau des nouveaux ingénieurs est simple, car ils peuvent apprendre un modèle simple et le répéter. Il demande également aux utilisateurs de considérer au moins l'erreur dans le contexte actuel, en s'assurant qu'au moins l'ingénieur reconnaît qu'une erreur peut se produire ici et que je dois réfléchir à ce qu'il faut faire.

J'ai écrit #32804 et je préférerais de loin que les choses NE changent PAS. Si votre code est long, c'est parce qu'il fait beaucoup de choses. Si vous avez beaucoup de code de gestion des erreurs, c'est parce que vous gérez bien tous vos cas.

S'il vous plaît, n'ajoutons pas des choses juste pour le plaisir d'ajouter des choses.

J'apprécie la simplicité de la gestion des erreurs telle quelle.

Attend est juste un anagramme pour except, et je préfère ne pas l'utiliser. Merci d'avoir commencé ça.

S'il vous plaît, ne changez pas mon Saint Graal.

Il y a eu des retours de la communauté écrasants demandant une gestion des erreurs plus rationalisée (à partir de l'enquête annuelle). L'équipe Go s'attaque maintenant à ce problème.

@icholy Bien sûr, mais les propositions actuelles laissent beaucoup à désirer. Ils semblent tous obscurcir la gestion des erreurs, revenir à plus d'implémentations de style try/catch/finally, faire remonter la gestion des erreurs hors contexte ou la rendre plus compliquée. Puisque le Go est censé être un langage simple, je pense que beaucoup d'entre nous espéraient une option simple. Personnellement, je n'en ai vu aucun que j'aime, donc je pense que la meilleure option est de garder le modèle actuel.

Une plainte était de devoir le taper, mais pratiquement tous les éditeurs ont des raccourcis pour insérer des extraits de code, donc ce n'est vraiment pas un gros problème. C'est peut-être ma propre expérience d'avoir utilisé Go depuis la version antérieure à 1.0, mais j'aime la simplicité et la redondance ne me dérange pas.

@kevineaton tu penses que try est compliqué ?

Je suis entièrement d'accord avec cela. Je ne suis même pas personnellement convaincu que nous devons faire quoi que ce soit - je suis d'accord que les contrôles if err != nil semblent maladroits à première vue, mais je n'ai rien vu de proposé qui résolve réellement le problème sans violer largement les choses mêmes qui se passent est populaire pour.

@icholy après avoir passé dix ans à écrire Java et Python avant Go, je pense que c'est possible. Je pense que vous rencontrez la capture d'exceptions Pokemon, ou le chaînage de plusieurs exceptions, et sinon, introduisez encore plus de frais généraux et de passe-partout. Je ne reviendrais pas à ce style de gestion des erreurs si je pouvais jamais l'éviter, car cela entraînait presque toujours des maux de tête et de la confusion, SURTOUT lors de l'enseignement. J'enseigne également l'informatique en plus de mon travail quotidien en tant qu'architecte logiciel, je suis donc orienté vers l'enseignement de nouveaux développeurs et le mentorat. Je choisirais Go et c'est une gestion d'erreur simple par rapport à une gestion d'erreur plus potentiellement plus compliquée ou nuancée n'importe quel jour.

Le suivi des problèmes est utile pour de nombreuses choses, mais il n'est pas utile pour une discussion détaillée d'un sujet complexe.

N'est-ce pas la vérité. Mais nous y sommes.

if err != nil ne disparaîtra pas si try est ajouté. Je pense que try ajoutera de la clarté aux chemins de code qui sont soit des transmissions d'erreurs lourdes, soit où de nombreuses erreurs différentes peuvent être résumées facilement dans un seul gestionnaire d'erreurs différées. . Je ne vois pas vraiment comment try encourage à ne pas gérer les erreurs beaucoup plus qu'un tas de if-err-return-err vides. Il est facile d'ignorer la gestion des erreurs, que try soit là ou non. Je pense que try est l'une des meilleures suggestions pour la gestion des erreurs à ce jour, car il semble qu'il sera facile de lire le code qui l'utilise.

Mes deux cents non sollicités, ça ne ressemble tout simplement pas à un "Go". C'est trop magique et nous préférons les constructions implicites aux constructions explicites.

À partir de la FAQ

Pourquoi Go n'a-t-il pas l'opérateur ?: ?
_Il n'y a pas d'opération de test ternaire dans Go. Vous pouvez utiliser les éléments suivants pour obtenir le même résultat :_

if expr {
   n = trueVal
} else {
    n = falseVal
}

La raison ?: est absente de Go est que les concepteurs du langage avaient trop souvent vu l'opération utilisée pour créer des expressions d'une complexité impénétrable. La forme if-else, bien que plus longue, est incontestablement plus claire. Un langage n'a besoin

@ianlancetaylor

Le suivi des problèmes est utile pour de nombreuses choses, mais il n'est pas utile pour une discussion détaillée d'un sujet complexe. Le suivi des problèmes ne fournit aucun fil et répondre à un message spécifique est difficile. Puisqu'il n'y a pas de proposition réelle ici, juste une réponse à d'autres propositions, je vous encourage vraiment fortement à prendre cette discussion sur la liste de diffusion golang-nuts.

Vous pouvez répondre à un message spécifique. Je viens de répondre au tien. :)

Puisqu'il n'y a pas de proposition réelle ici, juste une réponse à d'autres propositions,

Une proposition pour moi signifie un appel au changement. Cette question particulière est anti-changement. Proposez-vous que nous créions une proposition pour _pas_ modifier la gestion des erreurs ? Je pense que le système de proposition est génial, mais il laisse le statu quo sous-représenté.

après avoir passé dix ans à écrire Java et Python... J'enseigne également l'informatique en plus de mon travail quotidien d'architecte logiciel

@kevineaton as -tu fini de sucer ta propre bite ?

Ce numéro fonctionne comme un sondage de longue durée dans un lieu semi-officiel où pratiquement tout le monde peut facilement voter pour ou contre des propositions.

Ne pas changer la langue pour supprimer if err != nil est une proposition parfaitement cromulée qui ne nécessite pratiquement aucun détail supplémentaire. Je ne sais pas quel est le problème. Non, ce n'est pas terriblement long et difficile à grok. Cela ne le rend pas mauvais, mauvais ou insuffisant.

+1, si rien de mieux, la bonne chose sera une très bonne information de stacktrace (sans trucs de danse de cadres), je suppose que x/errors parvient déjà, mais j'aimerais quelque chose de rapide dans un avenir proche, comme le marquage func s en utilisant le mot-clé throws qui renverrait un mot-clé error + try , empêchant l'ombrage de var d'erreur (que je déteste personnellement), quelque chose comme ceci :

func a() (int) throws {
  throw &someError{}
}

anInt, err := try a()

@icholy C'était incroyablement déplacé. C'est un lieu de discussion et la communauté Go est censée être une communauté accueillante. Il n'y a pas de place pour ce genre de remarque. Je crois que Socrate avait quelque chose à dire sur les insultes dans un débat.

La gestion des erreurs actuelle est sujette aux erreurs humaines. Il est assez facile d'oublier de vérifier err pour le moment. S'il y a déjà des vérifications dans la portée (et la plupart du temps il y en a), le compilateur ne se terminera pas avec unused variable . La gestion des erreurs doit être stricte - soit vous _ une erreur, soit vous la vérifiez - aucun tir à la jambe ne devrait être possible.

@kevineaton tu penses que try est compliqué ?

try est une odeur de code. Il force l'indentation dans tout votre bloc de code au lieu d'un seul endroit. De plus, la nature de « bulle » de la gestion des exceptions crée de facto un comportement non déterministe dans le code et plusieurs points de sortie.

La beauté d'utiliser plusieurs valeurs de retour au lieu de try est qu'il y a une valeur à vérifier lorsque votre fonction est terminée et un point de sortie de votre fonction (à moins, bien sûr, d'utiliser des instructions de garde ou d'autres retours explicites).

try blocs de

@fillest Bien que cela rende le code un peu moins lisible, je pense que ce serait une valeur ajoutée en termes de sécurité / gestion des erreurs explicites. Si vous regardez en arrière les objectifs initiaux concernant la façon dont nous gérons les erreurs dans Go, je pense que ce serait une bonne itération pour éviter la classe de bogues que vous citez tout en poursuivant l'esprit explicite d'être bon.

La gestion des erreurs actuelle est sujette aux erreurs humaines. Il est assez facile d'oublier de vérifier l'erreur pour le moment. S'il y a déjà des vérifications dans la portée (et la plupart du temps il y en a), le compilateur ne se terminera pas avec une variable inutilisée. La gestion des erreurs doit être stricte - soit vous _ une erreur, soit vous la vérifiez - aucun tir à la jambe ne devrait être possible.

@fillest La modification proposée de la gestion des erreurs facilite le "tir à la jambe" et les erreurs sont plus prononcées car elles peuvent être gérées paresseusement.

J'ai arrêté d'utiliser Go en raison du manque de génériques, de propension standard, de GC, du manque de limites de ressources/comptabilité et de la charge de travail générée par les noobs PHP qui ne comprenaient pas ce que fait un compilateur. Haskell, C# et d'autres ont assez bien résolu la gestion des erreurs... la proposition Go 2 semble correcte si elle a une gestion explicite des cas comme auparavant (incertain).

La gestion des erreurs est au cœur de la programmation. Modéliser la logique métier (aussi complexe soit-elle) est toujours plus simple que de répondre aux conditions invalides générées par cette logique. Le simple fait de transmettre une erreur est une odeur de code. Je souhaite que Go n'encourage pas ce comportement mais favorise les modèles de gestion des erreurs. Les débutants sont souvent confus avec tout ce code de gestion des erreurs, car ils n'ont pas réalisé à quel point la gestion des erreurs est centrale.

Entièrement d'accord, car la fonction intégrée try n'aidera pas à envelopper les erreurs et à leur ajouter des informations, même pour un instant.

Avant de réécrire avec try :

_, err := doSomething()
if err != nil {
    return nil, errors.Wrap(err, "failed to do something")
}

_, err = doOtherThing()
if err != nil {
  return nil, errors.Wrap("failed to do the other thing")
}

Imaginez ce qui sera après la réécriture avec try .

Puisque try agit déjà comme une fonction à 1 argument en mettant son argument entre parenthèses, il pourrait accepter un 2ème argument qui est le code d'emballage d'erreur.

try(extract_value(try(get_data(1), errors.Wrap(err, "failed to get data")), errors.Wrap(err, "failed to get data")))

Où la err devrait être implicitement introduite (de manière hygiénique). Ensuite, si try est utilisé comme fonction à 1 argument, il renverrait simplement son erreur inchangée.

Je suis d'accord, la seule chose "sucre syntaxique" qui pourrait rendre la gestion des erreurs un peu plus simple est de nous laisser faire quelque chose comme ce qui suit lorsque nous avons plusieurs retours de nos fonctions ... les traits de soulignement seraient simplement les valeurs par défaut de quels que soient les types de retour

if err != nil {
    return _, _, err
}

@sorenvonsarvort ça ne me semble pas si mal :

var errContext string 

defer func() {
  // err is a named return
  if err != nil {
    err = fmt.Errorf("%v: %w", errContext, err)
  }
}()

errContext = "failed to do something"
_ := try(doSomething())

errContext = "failed to do other thing"
_ := try(doOtherThing())

D'après ce que je comprends, vous pouvez également toujours utiliser if err != nil { ... } si cela est plus clair pour cette section de code particulière.

try brille dans d'autres cas. Imaginez quelque chose comme :

func trySomeComplexOp() (r result, err error) {
  a := try(step1())
  b := try(step2(a))
  c, d := try(step3(b))
  return try(lastStep(c, d)), nil
}

Un code comme le code ci-dessus peut être beaucoup plus propre que si vous deviez saupoudrer if err != nil blocs de try fait bien à cette fin.

Il y a eu des retours de la communauté écrasants demandant une gestion des erreurs plus rationalisée (à partir de l'enquête annuelle). L'équipe Go s'attaque maintenant à ce problème.

C'est une minorité vocale et je parie qu'une bonne partie d'entre eux n'utilise même pas Go

@sirkon sur quoi basez-vous cette déclaration ?

@sorenvonsarvort ça ne me semble pas si mal :

Un code comme le code ci-dessus peut être beaucoup plus propre que si vous deviez saupoudrer if err != nil blocs try porte bien à cette fin.

En Russie, nous appelons cela «экономия на спичках». Utilisez google translate pour obtenir un sens.

Pour ceux qui ne l'ont pas déjà fait dans ce fil, je recommanderais de lire ce commentaire sur le problème de la proposition originale de try . Il traite des meilleures pratiques générales en matière de contexte d'erreur et de la manière dont elles peuvent être exprimées avec try .

Je pense que le contexte d'erreur est peut-être devenu un peu dogmatisé dans la communauté Go. Je sais que je suis personnellement tombé dans le piège et que j'ai trop contextualisé mes erreurs, ce qui a entraîné des messages très longs, répétitifs et difficiles à lire. Il y a beaucoup de nuances concernant quand contextualiser les erreurs et quand ne pas le faire.

J'aime que try soit fondamentalement un raccourci et réduit le code passe-partout. Mais nous perdons la possibilité d'envelopper les erreurs avec des informations supplémentaires. Cependant, le changement suivant pourrait résoudre ce problème :

f := try(os.Open(filename))

devient

f := try(os.Open(filename), "open data file")

Bien sûr, si vous avez besoin de faire beaucoup plus que cela, la façon "complète" de faire un err != nil est toujours disponible.

Je suis d'accord avec cela, mais je vais respecter la demande de l'équipe de go d'avoir plus d'expérience avec le changement avant d'avoir un avis final.

Mais mon expérience préliminaire avec le changement semble soutenir qu'il est vraiment inutile. J'ai 2 programmes "du monde réel" avec environ 10 000 lignes chacun et j'exécute tryhard sur les deux montre qu'aucun d'entre eux ne bénéficierait de ce changement. Cela s'explique facilement par le fait que les deux ajoutent toujours du contexte aux erreurs. J'ai d'autres programmes "jouets" plus petits dans Go et tryhard ont trouvé 1 cas où j'aurais pu utiliser try dans l'un d'entre eux, mais c'est tout.

J'admets que d'autres personnes peuvent traiter les erreurs différemment de moi et j'admets la possibilité que try puissent être utilisés de manière positive. Le code source de tryhard lui-même a quelques cas consécutifs de return err , que s'il utilisait try je ne pense pas que la lisibilité serait tellement compromise. Mais je crains juste les abus, car ceux-ci auront un impact sur la lisibilité. Un bon exemple est fourni ici . Et puis déterminer ce qui est un bon usage ou non sera une toute autre histoire.

De plus, j'aime la façon dont les gens peuvent généralement lire le code go même s'ils ne programment pas go eux-mêmes. Cela les obligera à apprendre la magie de try , spécialement parce qu'elle fait une chose différente des autres try qu'ils ont vus dans d'autres langues. C'est également vrai pour les nouveaux arrivants dans la langue, c'est juste une autre fonctionnalité qu'ils devront apprendre dans une langue qui se targue d'être simple en ayant "juste les fonctionnalités dont vous avez besoin".

Attendons voir. Je vais expérimenter davantage avec ce changement, mais je ne suis pas certain que cela changera ma position.

Il y a eu des retours de la communauté écrasants demandant une gestion des erreurs plus rationalisée (à partir de l'enquête annuelle). L'équipe Go s'attaque maintenant à ce problème.

@icholy Comme je l'ai dit, j'aime ajouter du contexte aux erreurs. À cet égard, « une gestion des erreurs plus rationalisée » signifie pour moi de meilleures façons de fournir ce contexte et d'en tirer des informations. Par exemple, avec tout le contexte que j'ai ajouté à mes erreurs, il devrait être trivial de demander "le contexte" si l'erreur a été causée par un délai d'attente. Mais ce n'est pas. Vous devez généralement utiliser pkg/error , créer vos propres "structures d'erreur" et/ou créer des méthodes pour les utiliser qui, selon l'implémentation, peuvent recourir à des recherches de chaînes. Je préférerais voir quelque chose qui m'éviterait de créer des structures et des méthodes entières que des choses qui m'épargneraient un seul if très occasionnellement au mieux. Et comme cela a été dit précédemment, pouvez-vous vraiment l'appeler "gestion des erreurs" lorsque ce changement fournit fondamentalement un moyen plus pratique de ne pas vraiment gérer l'erreur ?

vous pensez que try est compliqué ?

Isolément try n'est pas compliqué, mais les changements de langue ne sont pas considérés isolément. Envisager:

  • Augmentation de la charge cognitive due à l'apprentissage de la sémantique d'une nouvelle fonction intégrée
  • Réduction de la lisibilité due à l'utilisation de try car il est plus court, dans les cas où if err != nil {return ... errors.Wrap() } aurait dû être utilisé à la place

Je fais écho ci-dessus aux sentiments que la simplicité (avoir un seul moyen de vérifier les erreurs) est plus important qu'un bref moyen de vérifier les erreurs.

N'est-il pas possible de gérer une erreur à travers cette déclaration globale et si c'est quelque chose à ignorer, alors comment les situations de panique sont-elles gérées ?? Je soutiendrais une meilleure gestion des erreurs car d'autres paradigmes de programmation gèrent les erreurs aujourd'hui, mais ne pas ignorer

Je ne vois aucun problème avec la proposition try ?
Si vous souhaitez utiliser l'ancien comportement ou si vous avez besoin d'une autre manière de gérer les erreurs, pourquoi ne pas réutiliser l'ancienne méthode ? Personne ne vous met un couteau dans le cou, vous utilisez la syntaxe try ?
try syntaxe de

Et si gofmt était modifié de telle sorte qu'une seule instruction si les blocs restaient sur une seule ligne ?

De cette façon, nous aurions pu gérer les erreurs encapsulées sur une seule ligne au lieu de trois dans la plupart des cas. Cela réduirait l'espace vertical occupé par la gestion des erreurs et donnerait l'impression que la gestion des erreurs n'occupe pas plus de la moitié de l'espace vertical dans la fonction moyenne.

Voici à quoi cela ressemblerait :

// we already have an err in scope
err = frub.Confozzle(foo, bar, baz)
if err != nil { return errors.Wrap(err, "confozzling didn't work") }

Je pense que le contexte d'erreur est peut-être devenu un peu dogmatisé dans la communauté Go. Je sais que je suis personnellement tombé dans le piège et que j'ai trop contextualisé mes erreurs, ce qui a entraîné des messages très longs, répétitifs et difficiles à lire. Il y a beaucoup de nuances concernant quand contextualiser les erreurs et quand ne pas le faire.

L'emballage d'erreurs et les éléments d'impression de trame/erreur de pile rendraient beaucoup plus simple. Je pense qu'il y aura une bonne solution pour cela dans le temps mais pas pour le moment. Personnellement, je préférerais attendre avec l'introduction de fonctionnalités go2 plus puissantes jusqu'à ce que le polymorphisme paramétrique soit réglé car il peut potentiellement être utile lors de la conception du reste des fonctionnalités

Je suis tout à fait d'accord pour le laisser tel quel. C'est un peu trop verbeux mais c'est assez simple à suivre.

Si je pouvais juste réduire

if err := foo.x(a, b); err != nil {
    return err
}

if err := foo.y(); err != nil {
    return err
}

if err := foo.z(c); err != nil {
    return err
}

à quelque chose comme

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
    return err
}

ce serait

@henvic Je pense que le problème est que vous supposez que vous voulez gérer les trois erreurs de la même manière. Go vous oblige à réfléchir à la façon de gérer chaque erreur individuellement. Le fait d'avoir des contrôles d'erreur distincts le montre clairement. Les agréger en un seul oblige le développeur à revenir en arrière et à vérifier de manière cognitive, ces erreurs doivent-elles _vraiment_ être traitées de la même manière ? Je pense qu'avec cette proposition, nous perdons de la clarté et avons forcé la réflexion sur les erreurs pour quelques touches de moins.

@sanbornm , tu as raison. Je suis d'accord.

Ce check pourrait également être un opérateur checkNonZero qui ne prendrait qu'un seul argument de retour et retournerait à la première valeur non nulle (comme null). Mais en plus d'être trop vague, et d'affecter encore plus la langue. Cela ne donnerait même que légèrement un indice que vous ne devriez pas l'utiliser si vous vous attendez, par exemple, à io.EOF. Une autre possibilité serait peut-être de compter sur go vet pour au moins vous informer des cas les plus courants (comme io.EOF lors de la lecture à partir d'un tuyau)... mais cette idée ne sonne pas bien à tout à moi.

Je ne vois aucun problème avec la proposition try ?
Si vous souhaitez utiliser l'ancien comportement ou si vous avez besoin d'une autre manière de gérer les erreurs, pourquoi ne pas réutiliser l'ancienne méthode ? Personne ne vous met un couteau dans le cou, vous utilisez la syntaxe try ?
try syntaxe de

Nous vivons tous en communauté. Chaque jour, je travaille avec beaucoup de code écrit par d'autres personnes. Donc le changement de langue m'affectera même si je ne l'utilise pas.

Dans les grands projets, les pires journaux sont générés par une fonction que nous avons oubliée que quelqu'un a écrite, qui appelle une bibliothèque qui appelle une bibliothèque qui appelle une bibliothèque qui a lancé un new Exception() générique que rien n'a intercepté jusqu'à ce qu'un gestionnaire d'exception "Pokemon" ne le fasse et enregistré une erreur générique. Les deuxièmes pires sont les mêmes, mais avec une trace de pile impénétrable de plusieurs centaines de lignes, avec laquelle je suppose que nous pouvons éventuellement comprendre la cause (heureusement, une simple recherche de github.com/<us>/<ourproject> trouve la plupart des informations pertinentes, mais il y en a parfois beaucoup). Malgré leur nom, les "Exceptions" ne sont malheureusement pas exceptionnelles dans les grands projets Java.

Pendant ce temps, même lorsqu'il y a beaucoup de contexte redondant, de simples chaînes d'erreur Go comme "narf: Error unpoiting the zort: foo: Unexpected bar in baz: {\"ork\": \"morpork\"}" ont été (d'après mon expérience) généralement très faciles à interpréter, tant que nous avons fait preuve de diligence pour intégrer le contexte important quelque part dans la valeur d'erreur réelle. S'il s'avère qu'un contexte important manque, c'est _également_ généralement assez évident. Le "correctif" dans ces cas ajoute plus de contexte et attend une autre erreur, donc ce n'est pas parfait, mais dans l'ensemble, je préfère toujours cela au fait de faire défiler les traces de la pile et/ou de me fier aux dépendances des dépendances de mes dépendances pour "jeter " ou " élever " des messages d'erreur sains. J'apprécie vraiment la façon dont le nom panic() semble empêcher la plupart des développeurs Go de déployer si généreusement ce qui est essentiellement la même fonctionnalité de langage. Ne faisons pas de error et panic la même chose après tout.

J'ai parfois rencontré des situations où une fonction avait une douzaine de modes de défaillance, et la grande majorité d'entre eux méritaient le même message d'erreur. La répétition ne me dérange pas vraiment, mais il y a généralement quelqu'un d'autre dans mon équipe que cela dérange, nous faisons donc un compromis en déclarant une fermeture au début de la fonction pour gérer ces erreurs courantes.

func foo(a, b, c SomeArgType) (x, y, z SomeReturnType, err error) {
  handleError := func(handleErr error) (x, y, z SomeReturnType, err error) {
    log.WithFields(logrus.Fields{
      "package": "foo",
      "func": "foo",
      "arguments": map[string]SomeArgType{"a": a, "b": b, "c": c},
      "error": handleErr,
    }).Error("Error fooing the bar")
    return reasonable, default, values, handleErr
  }

  err := doABunchOfThings()
  if err != nil {
    return handleError(err)
  }
}

Ce qui, certes, est encore une solution imparfaite à certains égards. Mais j'aime que cela permette aux futurs développeurs de comprendre facilement quand et ce que foo retourne, sans que le flux de contrôle ne saute trop.

Si, d'une manière ou d'une autre, ce "problème" de répétition est extrêmement courant dans de nombreux packages au lieu (comme je le vois habituellement) confiné à une poignée de fonctions irréductiblement complexes dans un package irréductiblement complexe, un "fonctor" à l'échelle du projet pourrait probablement être utilisé pour une fin similaire, et (soupir) si un concept de types paramétriques finit par être ajouté au langage, vous pouvez cacher encore plus de détails derrière une usine de gestion des erreurs sans avoir besoin de vous fier à try/catch.

@thomasf

L'emballage d'erreur et le cadre de pile/l'impression d'erreurs rendraient beaucoup plus simple

Je suis d'accord.

Personnellement, je préférerais attendre l'introduction de fonctionnalités go2 plus puissantes jusqu'à ce que le polymorphisme paramétrique soit réglé car il peut potentiellement être utile lors de la conception du reste des fonctionnalités

Quelques personnes dans la proposition originale try ont également discuté de l'attente de génériques, mais je ne vois pas clairement en quoi le polymorphisme paramétrique rendrait la proposition try différente. C'est déjà intégré, donc ce n'est pas limité aux limites de ce que nous pouvons exprimer dans le langage.

@icholy

Il y a eu des retours de la communauté écrasants demandant une gestion des erreurs plus rationalisée (à partir de l'enquête annuelle). L'équipe Go s'attaque maintenant à ce problème.

Juste pour répondre à ceci; il y avait aussi une majorité au Royaume-Uni en faveur du Brexit. Bien sûr, l'UE entraîne également des inconvénients auxquels le grand public a réagi. Cependant, une fois que toutes les alternatives ont été évoquées, il semblait que rester dans l'UE ne serait pas si mal après tout.

Maintenant, ce n'est pas du tout mon intention de politiser cela, et vous pouvez être en désaccord avec ce qui précède. Mais ce que je veux montrer, c'est que même lorsqu'une majorité considère initialement que quelque chose est une nuisance, cela peut toujours être la meilleure solution une fois que toutes les alternatives ont été examinées.

Je ne suis pas très convaincu de la gestion des erreurs, mais cela pourrait être un argument pour laisser les choses telles qu'elles sont.

Dans un environnement de codage professionnel, nous tirons parti des pratiques actuelles de gestion des erreurs pour annoter les systèmes de traçage et décorer les journaux. Soit dit en passant, le retour implicite est un peu comme l'utilisation de panique dans une fonction de bibliothèque exportée, en ce sens qu'il masque la lisibilité immédiate du contrôle de flux.

@icholy

Il y a eu des retours de la communauté écrasants demandant une gestion des erreurs plus rationalisée (à partir de l'enquête annuelle). L'équipe Go s'attaque maintenant à ce problème.

Juste pour répondre à ceci; il y avait aussi une majorité au Royaume-Uni en faveur du Brexit. Bien sûr, l'UE entraîne également des inconvénients auxquels le grand public a réagi. Cependant, une fois que toutes les alternatives ont été évoquées, il semblait que rester dans l'UE ne serait pas si mal après tout.

Vous n'avez pas besoin de considérer sérieusement la demande de cette personne : le décompte des émoticônes montre que les gens n'aiment généralement pas la proposition de try et qu'ils aiment généralement celle-ci « laissez-la telle quelle ».

PS dans ma pratique, une grande majorité de personnes qui n'aiment pas Go dans son domaine principal (services réseau, utilitaires CLI) ne l'ont même pas utilisé. Je préférerais donc ignorer leurs opinions.

Nous avons besoin de meilleures options, moins controversées, que la proposition try .
Je ne vois pas l'urgence de solutions hâtives.

@velovix Je pense que je déteste le polymorphisme paramétrique plus que la "gestion des erreurs try/catch", mais s'il devenait une fonctionnalité de langage, je pourrais voir plusieurs façons d'éviter le besoin d'une autre fonctionnalité de langage intégrée.

D'une part, lorsque le code que les gens ne veulent pas répéter est un code passe-partout comme :

foo, err := Foo()
if err != nil {
  log(err)
}
bar, err := Bar(foo)
if err != nil {
  log(err)
}
// ...

Ensuite, une combinaison de fonctions paramétriques, d'inférence de type et de modèles de conception d'objets de style _peut-être_ ou _optionnel_ réduirait directement (heh) le passe-partout sans recourir à des stratégies de flux de contrôle non linéaires étranges :

func<T> DoWithErrorLogging(f func(any...) (T, error), args... any) T {
  t, err := f(args...)
  if err != nil {
    log(err)
  }
  return t
}
// ...
foo := DoWithErrorLogging(Foo)
bar := DoWithErrorLogging(Bar, foo)

OMI, tout cela serait bien, bien pire que Go1. Mais mieux que d'avoir ces mots-clés _plus_ try/catch dans la langue.

Honnêtement... Dans l'état actuel des choses, je pense que mes changements "de rupture" préférés pour Go2 résoudraient simplement tous les petits inconvénients de Go1, comme les net/http défaut étant des globals partagés mutables imbriqués dans des globals partagés mutables (juste faire la norme cleanhttp Hashicorp, en gros), ou (*time.Timer).Reset() ayant une valeur de retour inutile que vous devez juste connaître, ou l'ensemble du package syscall . Go3 peut être libéré presque immédiatement après cela avec toutes les tumeurs que les gens veulent y développer ; Je ne vois pas pourquoi les petits et les grands changements doivent tous être effectués dans une seule version.

Je suis en faveur de try ... lorsqu'il est utilisé avec parcimonie. Je soupçonne que les projets populaires ajouteront des directives sur quand / s'ils sont d'accord avec l'utilisation de try, et que les projets petits / nouveaux / à une seule personne souffriront parfois de mauvaises erreurs - et donc d'un manque d'utilisation - en raison de try ment trop souvent. Ces projets mourront ou seront réparés ou fourchus.

Je ne vois vraiment pas l'ajout de try à la langue comme si horrible. Si les pires craintes des gens s'avèrent fondées, son utilisation sera désapprouvée. Les personnes inexpérimentées l'utiliseront sans discernement, tandis que d'autres ne le feront pas. Ce n'est pas la fin du monde.

Si try est ajouté, je l'utiliserai probablement dans certains cas. Ces cas sont ceux où une erreur est renvoyée, mais je pense qu'il est tellement peu probable qu'il y ait réellement une erreur que je ne vois pas l'intérêt d'ajouter du contexte, et je renvoie simplement l'erreur telle quelle. Par exemple, si je viens de créer un système de fichiers qui remplit un disque dont je sais qu'il fait 1 To, je peux être certain qu'il y a de la place pour créer un fichier de 1 Ko ou un répertoire. Si cela échoue, je ne veux pas ignorer l'erreur - cela pourrait indiquer un bogue ailleurs, une défaillance matérielle, etc. Cependant, cela ne vaut pas vraiment la peine de faire des efforts pour annoter chaque erreur incroyablement peu probable.

Je n'aime pas la façon dont try(..) met simplement une paire de parenthèses/crochets de plus sur la pile mentale d'un codeur à laquelle penser lors de la frappe. Et depuis le plus longtemps que je puisse imaginer !

Alors c'est mieux :
valeur, err := foo()
renvoie err si err != nil

Mais c'est quand même si courant. J'aimerais donc que smt comme celui-ci soit possible d'une manière ou d'une autre :

valeur, vérifiez err := foo()

Si le Go veut être un langage lisible, la capacité de faire des pensées difficiles à lire ou à comprendre doit être minimisée.

Si Go veut avoir une bonne gestion des erreurs, cela devrait encourager les erreurs à avoir un contexte supplémentaire lorsqu'elles montent dans la pile d'appels. L'exigence d'utiliser le report pour gérer les erreurs semble déroutante. Que faire si votre gestionnaire d'erreurs a une erreur ? Les Defers sont exécutés dans l'ordre de la pile, devons-nous déclarer les gestionnaires à l'envers ?

Si les déclarations sont simples et laissent peu de place à l'ambiguïté ici. J'ai l'impression que try résout un problème plutôt qu'un vrai problème d'ingénierie. J'aime cette proposition car elle permet à la majorité silencieuse de se prononcer enfin sans comprendre pleinement toutes les facettes de ces propositions complexes.

@icholy S'il vous plaît soyez poli. Veuillez prendre connaissance du code de conduite de Gopher : https://golang.org/conduct. Merci.

Tout le monde : en plus de mon commentaire ci-dessus (https://github.com/golang/go/issues/32825#issuecomment-506740412), veuillez tenir compte de https://golang.org/wiki/NoPlusOne. Il n'est pas utile de faire un commentaire en disant un peu plus que "Je suis d'accord" ou "Je ne suis pas d'accord". Utilisez plutôt les boutons emoji. Merci.

@sanbornm

(Je suis d'accord qu'il est possible de répondre à un message ; j'ai dit que c'était gênant, pas impossible. Et mon point sur le threading tient toujours, en ce sens que ce mini-thread est perdu dans un blizzard d'autres commentaires.)

Une proposition pour moi signifie un appel au changement. Cette question particulière est anti-changement. Proposez-vous que nous créions une proposition pour ne pas modifier la gestion des erreurs ? Je pense que le système de proposition est génial, mais il laisse le statu quo sous-représenté.

Il n'est pas nécessaire de créer la proposition A en disant que la proposition B ne doit pas être adoptée. Votez contre la proposition B à la place. Pour une discussion détaillée de la proposition B, utilisez cette proposition ou la liste de diffusion.

(Je comprends que la proposition B dans ce cas est verrouillée ; le fait que cette proposition ait 77 commentaires en moins d'un jour montre pourquoi. Ce niveau de discussion fonctionne simplement mieux sur une liste de diffusion plutôt que sur le suivi des problèmes.)

@ianlancetaylor

(Je suis d'accord qu'il est possible de répondre à un message ; j'ai dit que c'était gênant, pas impossible. Et mon point sur le threading tient toujours, en ce sens que ce mini-thread est perdu dans un blizzard d'autres commentaires.)

Assez juste, cela a du sens. Les listes de diffusion sont excellentes, mais personnellement, je trouve qu'il est plus facile de contribuer via GitHub dans ce cas. Je n'ai pas grand chose à dire à part que la gestion des erreurs actuelle est excellente et je souhaite qu'elle reste la même. Les emoji/votes sont parfaits pour cela. Vous ne voulez probablement pas que 100 personnes écrivent "Veuillez laisser le traitement des erreurs seul" dans la liste de diffusion où 100 "votes" suffiraient.

Parce que ce problème est verrouillé, il ne peut plus être "voté" avec Emojis. C'est pourquoi je crois que ce problème a été créé en premier lieu.

Point secondaire mais connexe, la gestion des dépendances n'a pas été bien gérée. Dep a très bien fonctionné et le mod go a été choisi (de ce qui semblait) de nulle part [1]. Je comprends que c'est la raison pour laquelle le système de proposition a été créé. J'ai juste l'impression que le système de proposition dans ce cas pourrait sous-représenter la communauté si les problèmes sont verrouillés et qu'on nous dit d'aller sur les listes de diffusion.

[1] https://twitter.com/_rsc/status/1022588240501661696

Edit : L'équipe Go et la communauté font pour la plupart un travail incroyable en écoutant les commentaires de la communauté. J'apprécie tout le travail qui y est consacré. Les sondages Go en sont un excellent exemple.

@sanbornm

Dep a très bien fonctionné

Besoin d'être en désaccord ici. Les modules Go ont finalement résolu ce problème mal connu de « gobindata » avec leur mise en cache persistante par https://proxy.golang.org

Ce mec n'a même pas réalisé le problème et jouait à la place avec de la fantaisie « assurer » via des VCS.

@sirkon C'est un peu hors sujet, mais vous n'en avez pas besoin si vous vendez des services comme le faisait Dep.

En l'état, je pense que je préférerais laisser les choses telles qu'elles sont à moins que plus de contraintes ne soient ajoutées, comme 1 instruction try par ligne. La raison est de considérer cet exemple de la proposition - il semble assez inoffensif info := try(try(os.Open(file)).Stat()) mais il fuit les descripteurs de fichiers au-delà de la portée de ce que le flux de contrôle normal ferait. Je pense que nous verrons une augmentation des fuites de ressources de fichiers avec des implémentations de io.Closer ou d'autres fonctions de nettoyage auxquelles les gens peuvent échapper à la recherche d'un code plus compact.

Peut-être que certaines personnes considéreront que c'est sans conséquence car f ne sera plus en direct et donc éligible pour GC immédiatement et à un moment donné, le finaliseur s'assurera que f est fermé. Je pense que cela change les conventions claires précédentes (prises en charge par linter) d'utilisation de defer aujourd'hui qui sont liées à un bloc fonction. Lorsque le bloc fonction est quitté, la ressource est libérée. S'appuyer sur le ramasse-miettes ne garantit pas que vous n'épuiserez pas les ressources (les valeurs par défaut de la limite de gestion des fichiers ouverts typiques peuvent être comprises entre 1k et 4k) - ce qui est facilement dépassé avec un simple chemin de fichier.Walk qui ne ferme pas les fichiers qu'il stats.

En résumé, je pense que cette syntaxe telle qu'elle est implémentée offre un risque subtil dans la gestion des ressources dans Go car elle manque du ctor/dtor et repose sur des machines GC de niveau inférieur très éloignées des blocs de code pour éviter les fuites de ressources. Transformer quelque chose qui semble inoffensif en une condition d'erreur potentielle (trop de fichiers ouverts).

var n int
for _, name in try(os.Readdir(...)) {
   n += try(getSize(name))
}
func getSize(name string) (int, error) {
   return try(try(os.Open(name)).Stat()).Size
}

Éditer:
Pour les contraintes, je pense en fait que si cela n'était valable que sur le côté droit d'une affectation, ce serait mieux que de dire 1 par ligne, car a, b := try(get("a")), try(get("b")) est assez raisonnable. Mais cela laisse toujours la possibilité de faire try(os.Open(name)).Stat() - ce qui si vous deviez annuler try(), mais seulement lorsque vous n'êtes pas sur le RHS d'une affectation, vous vous retrouvez avec quelque chose qui n'est pas très fonctionnel comme à tous.

@cstockton wow super trouvaille !

Rust a en fait une macro similaire ( ? si je me souviens bien) qui fait exactement ce que ce try voulait faire, mais ils ont un raii approprié, donc ce n'est pas un problème dans cette langue et un énorme trou dans notre cas

@sanbornm oui, garder la moitié d'Internet dans votre référentiel semble en effet une excellente idée.

Finishing Touch

Puisque quelqu'un a mentionné un utilitaire qui compterait le nombre d'endroits où try me ferait gagner du temps/des efforts , j'ai décidé de l'exécuter sur mes plus gros projets de travail, plus tout le reste dans mon ancien répertoire GOPATH GitHub.

| Projet | LOC* | essayer les candidats |
|-----------|------|----------------|
| appel1 | 2047 | 3 |
| pompe1 | 1030 | 0 |
| docs1 | 4576 | 8 |
| hugoutil | 604 | 1 |
| tout le reste | 8452 | 23 |

  • Code Go uniquement, à l'exclusion des commentaires, selon l'utilitaire cloc .

Gardez à l'esprit que le contenu de "tout le reste" comprend des hacks rapides et du code que j'ai écrit lorsque j'apprenais le Go.

Ma conclusion générale est que pour moi au moins, la proposition try n'aiderait pas à rationaliser ma gestion des erreurs à un degré valable.

La principale raison pour laquelle j'aime go est que sa spécification restreint les codeurs à un petit sous-ensemble de syntaxe disponible pour d'autres langages. Parce qu'il s'agit d'un si petit ensemble de fonctionnalités, il est facile d'apprendre l'ensemble des fonctionnalités. Un futur développeur pourra probablement regarder mon code et savoir ce que j'ai fait. Chaque nouvelle chose ajoutée au langage diminue les chances que le futur développeur sache cette chose. L'extrême de la pente glissante est un langage dont la complexité rend difficile le grok, comme C++ ou scala.
J'aimerais ne voir aucun ajout de syntaxe pour aller 1. Mettez-les plutôt dans aller 2.

@miekg, veuillez ajouter ce lien https://github.com/golang/go/issues/32825#issuecomment -506882164 dans la proposition. L'exemple disqualifie complètement l'idée de ce mot clé récent try .

image

Je suis tout à fait d'accord pour le laisser tel quel. C'est un peu trop verbeux mais c'est assez simple à suivre.

Si je pouvais juste réduire

if err := foo.x(a, b); err != nil {
  return err
}

if err := foo.y(); err != nil {
  return err
}

if err := foo.z(c); err != nil {
  return err
}

à quelque chose comme

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
  return err
}

ce serait

Si vous parliez d'un type "Peut-être", il nécessite d'abord un type de variante.

Gardons if err != nil ça marche, c'est clair, ce n'est vraiment pas verbeux, et ça a du sens dans le flux du code. Lorsque vous lisez le code avec cette construction, vous savez ce qu'elle va faire.
Gardons ça, n'ajoutons pas try

Lorsque je lis du code, je veux que les lignes qui font le travail soient clairement lisibles, sans ou avec un minimum de manipulations d'erreurs.

Les 3 lettres 'err' au même niveau me conviennent. Je ne voudrais pas qu'une fonction "vérifier" enveloppe le code important, car le code important serait à un deuxième niveau (vous vous souvenez de lisp?), Et je ne voudrais pas "essayer" une ligne avant, car le code important viendrait en retrait et sur la deuxième ligne.

res, err := begin_job()
si erreur != nil {
handle_error()
}

err = continue_job(res)
si erreur != nil {
handle_error()
}

Avec ce code, vous pouvez lire le flux du cas de non-erreur en lisant les premières lignes de blocs (comme je lis les titres des docs quand je dois le lire rapidement)

Puisque quelqu'un a mentionné un utilitaire qui compterait le nombre d'endroits où try me ferait gagner du temps/des efforts , j'ai décidé de l'exécuter sur mes plus gros projets de travail, plus tout le reste dans mon ancien répertoire GOPATH GitHub.

Projet LOC* essayer des candidats
appel1 2047 3
pompe1 1030 0
docs1 4576 8
hugoutil 604 1
tout le reste 8452 23

  • Code Go uniquement, à l'exclusion des commentaires, selon l'utilitaire cloc .

Je pense que try est plus nécessaire dans les grands programmes. En tirant simplement de la mémoire, je pense que les programmes avec des tailles LOC d'environ 15-20k et plus en ont davantage besoin car c'est à ce moment-là que vous pourriez commencer à obtenir des couches qui n'ont qu'à transmettre des erreurs car elles sont correctement spécifiées et gérées dans un système fermé par à la fois côté envoi et côté réception. Cela dépend beaucoup du type de programme dont il s'agit. Je n'utiliserais probablement pas beaucoup d'essais non plus dans des programmes plus petits

Je pense que l'essai est plus nécessaire dans les programmes plus importants.

Bon point. J'ai essayé tryhard sur heptio/contour, 28,7k lignes de texte source, tryhard a trouvé 12 substitutions.

Je pense que l'essai est plus nécessaire dans les programmes plus importants.

Bon point. J'ai essayé tryhard sur heptio/contour, 28,7k lignes de texte source, tryhard a trouvé 12 substitutions.

WOW! 12 vs 28,7K lignes, cela nécessite vraiment un mot-clé dédié !

Eh bien, je suis plus intéressé par votre POV sur ceci :

stat := try(try(os.Open(fileName)).Stat())

Je pense que c'est plus courant si votre programme est un peu plus monolithique et ne fait pas partie d'une intégration de services entre de nombreux services. Lorsque je recherche simplement fmt.errorf ou errors dans github sur ce référentiel ( heptio/contour ), il n'y a que très peu de résultats, il est donc difficile d'avoir un aperçu rapide. dit que cela varie probablement beaucoup d'un programme à l'autre, même pour les programmes plus importants.

Supposons que vous ayez un seul programme qui n'utilise pas beaucoup de bibliothèques externes. Ensuite, vous pouvez avoir une AuthorizationError spécifique (et vous savez que toutes les erreurs renvoyées sont suffisamment spécifiques avec toutes les erreurs io déjà gérées et encapsulées) qui contiennent déjà vos métadonnées utilisateur et peuvent être propagées sans changer quelques couches sans trop de changements aux choses qui ont réellement besoin pour les gérer jusqu'à la couche de requête.

Je pense que c'est plus courant si votre programme est un peu plus monolithique et ne fait pas partie d'une intégration de services entre de nombreux services. Lorsque je recherche simplement fmt.errorf ou errors dans github sur ce référentiel, il n'y a que très peu de résultats, il est donc difficile d'avoir un aperçu rapide. Mais comme je l'ai dit, cela varie probablement beaucoup d'un programme à l'autre. programmer, même pour des programmes plus importants.

Supposons que vous ayez un seul programme qui n'utilise pas beaucoup de bibliothèques externes. Ensuite, vous pouvez avoir une AuthorizationError spécifique qui contient déjà vos métadonnées utilisateur et peut être propagée sans changer quelques couches sans trop de changements aux choses qui doivent réellement les gérer jusqu'à la couche de demande.

Vous n'avez pas d'idée. Les annotations permettent de trouver facilement une erreur de chemin s'est produite. Nous avons également os.NotExist mais c'est à peine un bon indice sur le chemin d'erreur.

@thomasf voici un autre point de données, à partir d'une copie de travail de plusieurs années de juju/juju,

529628 lignes sources, tryhard a trouvé 1763 (0,3%) remplacements.

Oui bien sûr. Puisque vous avez été impliqué dans les deux, ce ne sont probablement pas de bons exemples de différentes manières d'écrire des programmes. Je n'ai même pas le temps d'essayer le programme tryhard atm et encore moins de le faire fonctionner correctement sur des sources variées (ce qui pourrait être impossible à collecter de toute façon car il omet le code source fermé s'il est collecté via github)

529628 lignes sources, tryhard a trouvé 1763 (0,3%) remplacements.

Comme quelqu'un (citation nécessaire) l'a judicieusement dit, try ne facilite pas la gestion des erreurs. Il est plus facile de ne pas les manipuler.

Si vous analysez le code et trouvez de nombreux remplacements de try , tout ce qui vous dit, c'est que le code ne fait rien avec les erreurs, sauf les renvoyer. Ce n'est probablement pas un bon code. Devrions-nous permettre aux gens d'être paresseux et de ne pas s'inquiéter des erreurs ? N'est-ce pas l'une des raisons pour lesquelles Go n'a pas d'exceptions, précisément pour éviter cela ?

Je ne vais pas prendre position là-dessus. Cependant, ce que je trouve, c'est leur peu de preuves à l'appui pour suggérer que

une. il y a beaucoup d'endroits où try est applicable aux bases de code go existantes
b. la gestion des erreurs en général constitue une partie importante du SLOC, sur la base de mes propres mesures et des chiffres évoqués par l'équipe Go, réf https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

Si vous analysez le code et trouvez de nombreux remplacements de try , tout ce qui vous dit, c'est que le code ne fait rien avec les erreurs, sauf les renvoyer. Ce n'est probablement pas un bon code. Devrions-nous permettre aux gens d'être paresseux et de ne pas s'inquiéter des erreurs ? N'est-ce pas l'une des raisons pour lesquelles Go n'a pas d'exceptions, précisément pour éviter cela ?

  1. Vous portez des jugements de valeur sur un code théorique dont vous ne savez rien et qui n'est pas une bonne habitude.
  2. Je ne vois pas en quoi try est plus facile à abuser que ce que nous avons maintenant, go n'a pas de fonctionnalités pour appliquer la gestion des erreurs et il est déjà très facile de l'ignorer. Pour moi, try vise à rendre le code plus facile à lire S'il n'a pas besoin de gérer les erreurs.

Peut-être qu'il n'y a pas besoin de try parce qu'il ne sera pas nécessaire dans de nombreux endroits, mais divertissons-nous avec l'idée et proposons des cas d'utilisation au lieu d'être simplement grincheux à ce sujet...

Je ne peux pas penser à beaucoup de situations pratiques moi-même comment je l'utiliserais, mais il n'existe pas encore, il est donc difficile de dire si je concevrais certaines choses différemment si cela existait. Une chose qui me vient à l'esprit est que je pourrais do consiste à regrouper un certain nombre d'actions dans une fonction anonyme comme celle-ci en utilisant try et toujours gérer l'erreur avant de la renvoyer à l'appelant. Cela pourrait rendre certains codes beaucoup plus lisibles.

var v1, v3 string
if err := func() error {
    try(onething())
    v = try(twothing())
    try(otherthing())
    v3 = try(somethingg())
}(); err != nil {
  ... handle error...
}

Je pense que ce pourrait être une bonne idée à ce stade d'écrire un site Web pour conserver les données de tryhard sur différents packages et les visualiser. Peut-être que modifier un peu golang/gddo (godoc.org) peut faire l'affaire.

Je préfère laisser if err != nil seul. Mais si nous devons ajouter quelque chose pour la gestion des erreurs, voici une nouvelle proposition qui ajoute le mot-clé throws pour cela.

32852

Sans répéter certains des arguments déjà exposés ici, je fais écho au sentiment de laisser if err != nil tel quel.

La perspective que je peux offrir est la suivante : en tant que personne qui a enseigné le Go à des centaines de nouveaux arrivants (à la fois en programmation et en Go à partir d'autres langages), if err != nil n'a jamais été un problème pour eux. Les programmeurs expérimentés de mes ateliers trouvent cela inhabituel au début mais apprennent rapidement à aimer la nature explicite de la gestion des erreurs dans Go.

Il y a des préoccupations plus larges que nous pouvons aborder dans la langue et la réaction claire de la communauté à ce problème indique que if err != nil n'en fait pas partie.

Go est parfait pour tant de raisons. Le principal d'entre eux est « if err != nil ». Cela peut sembler verbeux, mais pour les personnes qui apprennent à coder, cela facilite le débogage de votre code et sa correction.

@davecheney

Je ne vais pas prendre position là-dessus. Cependant, ce que je trouve, c'est leur peu de preuves à l'appui pour suggérer que

une. il y a beaucoup d'endroits où try est applicable aux bases de code go existantes
b. la gestion des erreurs en général constitue une partie importante du SLOC, sur la base de mes propres mesures et des chiffres évoqués par l'équipe Go, réf https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

Je crains que, dans le climat actuel, tous les exemples que nous trouvons soient simplement rejetés comme "eh bien, ce n'est probablement pas un bon code".

Voici un exemple :

llorllale:~/go/src/github.com/hyperledger/fabric$ cloc --exclude-dir=vendor .
    2406 text files.
    2256 unique files.                                          
    3130 files ignored.

http://cloc.sourceforge.net v 1.60  T=6.69 s (272.8 files/s, 58350.9 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
Go                             1751          54365          34149         294005
YAML                             35            547           2171           2060
Bourne Shell                     26            354            325           1312
make                              3            135             96            418
CSS                               1             40             14            140
HTML                              3              7              5             63
Python                            1             50            103             57
Bourne Again Shell                1              1              6             50
Java                              3              7              4             26
XML                               2              1              4              2
--------------------------------------------------------------------------------
SUM:                           1826          55507          36877         298133
--------------------------------------------------------------------------------
llorllale:~/go/src/github.com/hyperledger/fabric$ tryhard -l . | grep -v vendor | less | wc -l
1417

En toute honnêteté, les données sur le nombre d'emplacements que tryhard a trouvés peuvent être confondues par des conventions qui nécessitent des erreurs d'emballage. Par exemple, si votre convention d'entreprise doit

if err != nil {
   return errors.Wrap(err) 
} 

...
if err != nil {
   return errgo.Notef(err, "error doing x") 
} 

cela ne serait pas signalé par tryhard.

Nous avons une telle convention dans mon entreprise. Faire une simple recherche et remplacement pour revenir à l'erreur nue me donne ces résultats :

Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
Go                                    2488          40317          15901         297038

tryhard rapporte 2736 remplacements, mais faire un examen manuel de l'emballage restant semble être sous-estimé d'environ 1850, donc j'estimerais un total d'utilisations d'environ 4500 try dans notre base de code de

(Personnellement, je suis en faveur de l'explicitation actuelle de la gestion des erreurs et cela ne me dérange pas.)

Par exemple, si votre convention d'entreprise doit
[Envelopper les erreurs avec un message personnalisé]
cela ne serait pas signalé par tryhard.

C'est le point - la proposition try simplifie uniquement les retours nus if err != nil return err , elle ne prend pas en charge les erreurs d'emballage avec un message et un contexte personnalisés.

Je pense que la seule répétitivité de if err != nil pourrait être corrigée en spécifiant également les valeurs zéro des autres valeurs de retour. La langue pourrait être mise à jour pour éliminer cela. Par exemple:

Dans le Go d'aujourd'hui, si j'ai une fonction avec cette signature :

func add(x, y string) (int, error)

Quelque part dans la fonction, je devrais écrire:

func add(x, y string) (int, error) {
    // ...
    if err != nil {
        return 0, err
    }

Forcer l'écrivain à répéter les mêmes valeurs zéro tout au long de la fonction.

Ce serait beaucoup plus facile (et avec peu de coûts pour la verbosité et la lisibilité des erreurs) si le langage pouvait automatiquement remplir les valeurs zéro pour les valeurs de retour sans erreur :

func add(x, y string) (int, error) {
    // ...
    if err != nil {
        return ..., err
    }
    // ...
}
func main() {
    add("8", "beep") // returns 0, error(`strconv.ParseInt: parsing "beep": invalid syntax`)
}

Je peux dire par expérience avec beaucoup de code qui interagit avec les requêtes et les appels de base de données, devoir répéter les valeurs zéro à travers les fonctions est le seul point négatif de la gestion des erreurs de style Go. Sinon, je suis d'accord avec le sentiment de cette proposition : laissez if err != nil tranquille !

Remarque : oui, les valeurs de retour nommées peuvent _en quelque sorte_ accomplir cela (https://play.golang.org/p/MLV8Y52HUBY), mais après avoir implémenté quelques fonctions dans mes propres bases de code à l'aide de cette technique, on m'a rappelé combien d'un pied -gun les valeurs de retour nommées sont ; Je finis toujours par occulter la valeur de retour nommée.

Par exemple, si votre convention d'entreprise doit
[Envelopper les erreurs avec un message personnalisé]
cela ne serait pas signalé par tryhard.

C'est le point - la proposition try simplifie uniquement les retours nus if err != nil return err , elle ne prend pas en charge les erreurs d'emballage avec un message et un contexte personnalisés.

C'est vrai, je pensais à la variante qui permettait d'ajouter une chaîne descriptive. La grande majorité (~4000/4500) de nos retours d'erreur est un errgo.Mask(err) sans contexte, que je considérais comme équivalent à un try() sans description, mais actuellement ce serait une réduction de fonctionnalité puisque errgo ajoute des informations sur la pile et n'essaye pas (encore).

@ianlancetaylor il y a une proposition ici. @miekg propose qu'en tant que l'un des leaders de notre langage, vous ne poursuiviez plus le remplacement de if err != nil par une autre construction qui contredit l'esprit de gestion des erreurs tel que décidé par les auteurs originaux de Go. Pour moi personnellement, j'ai l'impression que vous essayez d'affirmer l'insignifiance de cette demande en la déplaçant vers golang-nuts au lieu de la traiter comme nos autres propositions. Ce n'est peut-être pas votre intention, mais c'est l'impact que je ressens.

Notre méthode de gestion des erreurs est unique et je pense que c'est une énorme valeur ajoutée par rapport aux autres langages. Cela a complètement changé ma perception des erreurs dans les systèmes que je construis et, par conséquent, je suis devenu un ingénieur logiciel plus fort. Je ne veux pas que nous flattions la minorité bruyante, ou les étrangers, dans l'intérêt d'obtenir plus de développeurs Go. Je pense que nous devrions adopter des lignes dures sur certaines choses, la façon dont nous choisissons de gérer les erreurs en fait partie, car cela nous rend meilleurs en éliminant la brièveté du code.

C'est une opportunité pour l'équipe au sein de Google de renforcer la confiance et la confiance avec la communauté, ou de poursuivre la trajectoire sur laquelle nous sommes actuellement et qui n'est pas bonne pour la langue, l'écosystème ou ses utilisateurs.

Je demande à l'équipe Go d'accepter cette proposition telle quelle, tout en continuant à rechercher d'autres itérations linguistiques sans rapport qui sont une valeur ajoutée plus claire.

Le tracker n'a peut-être pas de threads, mais je préférerais personnellement avoir la garantie que cette proposition est traitée à titre officiel et non reléguée au groupe Google où elle peut sombrer discrètement dans l'obscurité.

Le sujet a déjà été discuté sur le groupe Google aussi.

La version actuelle de #32437 n'est pas satisfaisante. La fonction intégrée try() cache de nombreux chemins d'exécution à l'œil non averti. La proposition originale avec check and handle était très compréhensible et le mot-clé check s'est démarqué.

Maintenant, la fonction intégrée try() ressemble à une fonction - il n'est pas évident qu'elle puisse modifier le flux de contrôle. Nous avons aussi panic(), mais c'est (je crois) toujours sur une ligne à part, a un nom bien visible et son utilisation est rare. try() d'autre part, pourrait se cacher à l'intérieur d'une expression complexe.

@theckman Robert a conçu les premières itérations de Go avec Rob et Ken, et Robert et Russ ont rejoint l'équipe très tôt. Ils travaillent sur Go depuis le début. Je pense que nous pouvons leur faire confiance pour savoir si une proposition "contredit l'esprit de gestion des erreurs tel que décidé par les auteurs de Go originaux".

Je n'aime pas le principe d'une proposition qui figerait la gestion des erreurs telle qu'elle est aujourd'hui. Une telle proposition interdirait toute proposition future sur ce sujet.

Pourquoi ne pas simplement accepter d'itérer la conception à la place ? Nous avons eu la proposition check/handle. Mais certains inconvénients ont été discutés. Cela a conduit à la proposition d'essai. Certains inconvénients de cette proposition sont discutés maintenant. Peut-être que cela conduira à une autre, meilleure proposition, jusqu'à ce que la bonne approche soit trouvée.

Notre méthode de gestion des erreurs est unique

La gestion des erreurs dans Rust est conceptuellement similaire à ce que nous faisons dans Go (les erreurs sont des valeurs, un flux de contrôle explicite, sauf que nous utilisons plusieurs valeurs de retour lorsqu'elles utilisent des types de somme à la place). Rust avait le même problème que Go avec la gestion des erreurs détaillées. Cela a conduit Rust à ajouter l'essai ! macro, et éventuellement le ? opérateur. Je dirais que la communauté Rust est encore plus stricte que la communauté Go en ce qui concerne la gestion des erreurs (les RFC et les discussions sur la gestion des erreurs sont éclairantes). Ils ont trouvé un moyen de réduire la verbosité de la gestion des erreurs sans la pente glissante d'une mauvaise gestion des erreurs. Je suis sûr que nous pouvons aussi.

la trajectoire sur laquelle nous sommes actuellement et qui n'est pas bonne pour la langue, l'écosystème ou ses utilisateurs

De quoi parles-tu? Go s'améliore constamment. C'est incroyable d'avoir accès gratuitement à un langage, à des outils et à une documentation aussi formidables (comme dans la liberté d'expression).

@theckman Robert a conçu les premières itérations de Go avec Rob et Ken, et Robert et Russ ont rejoint l'équipe très tôt. Ils travaillent sur Go depuis le début. Je pense que nous pouvons leur faire confiance pour savoir si une proposition "contredit l'esprit de gestion des erreurs tel que décidé par les auteurs de Go originaux".

Je n'aime pas le principe d'une proposition qui figerait la gestion des erreurs telle qu'elle est aujourd'hui. Une telle proposition interdirait toute proposition future sur ce sujet.

Pourquoi ne pas simplement accepter d'itérer la conception à la place ? Nous avons eu la proposition check/handle. Mais certains inconvénients ont été discutés. Cela a conduit à la proposition d'essai. Certains inconvénients de cette proposition sont discutés maintenant. Peut-être que cela conduira à une autre, meilleure proposition, jusqu'à ce que la bonne approche soit trouvée.

Notre méthode de gestion des erreurs est unique

La gestion des erreurs dans Rust est conceptuellement similaire à ce que nous faisons dans Go (les erreurs sont des valeurs, un flux de contrôle explicite, sauf que nous utilisons plusieurs valeurs de retour lorsqu'elles utilisent des types de somme à la place). Rust avait le même problème que Go avec la gestion des erreurs détaillées. Cela a conduit Rust à ajouter l'essai ! macro, et éventuellement le ? opérateur. Je dirais que la communauté Rust est encore plus stricte que la communauté Go en ce qui concerne la gestion des erreurs (les RFC et les discussions sur la gestion des erreurs sont éclairantes). Ils ont trouvé un moyen de réduire la verbosité de la gestion des erreurs sans la pente glissante d'une mauvaise gestion des erreurs. Je suis sûr que nous pouvons aussi.

la trajectoire sur laquelle nous sommes actuellement et qui n'est pas bonne pour la langue, l'écosystème ou ses utilisateurs

De quoi parles-tu? Go s'améliore constamment. C'est incroyable d'avoir accès gratuitement à un langage, à des outils et à une documentation aussi formidables (comme dans la liberté d'expression).

L'histoire du développement de Rust montre que les mecs derrière cela n'avaient aucune idée de ce qu'ils faisaient. Ils ont essentiellement copié les principes de traitement des erreurs de Haskell, mais ceux-ci ne correspondent pas à la programmation impérative (du monde réel ?). Leur macro ? n'est qu'une solution de contournement pour le système de traitement des erreurs initialement défaillant.

@ianlancetaylor il y a une proposition ici. @miekg propose qu'en tant que l'un des leaders de notre langage, vous ne poursuiviez plus le remplacement de if err != nil par une autre construction qui contredit l'esprit de gestion des erreurs tel que décidé par les auteurs originaux de Go. Pour moi personnellement, j'ai l'impression que vous essayez d'affirmer l'insignifiance de cette demande en la déplaçant vers golang-nuts au lieu de la traiter comme nos autres propositions. Ce n'est peut-être pas votre intention, mais c'est l'impact que je ressens.

Notre méthode de gestion des erreurs est unique et je pense que c'est une énorme valeur ajoutée par rapport aux autres langages. Cela a complètement changé ma perception des erreurs dans les systèmes que je construis et, par conséquent, je suis devenu un ingénieur logiciel plus fort. Je ne veux pas que nous flattions la minorité bruyante, ou les étrangers, dans l'intérêt d'obtenir plus de développeurs Go. Je pense que nous devrions adopter des lignes dures sur certaines choses, la façon dont nous choisissons de gérer les erreurs en fait partie, car cela nous rend meilleurs en éliminant la brièveté du code.

C'est une opportunité pour l'équipe au sein de Google de renforcer la confiance et la confiance avec la communauté, ou de poursuivre la trajectoire sur laquelle nous sommes actuellement et qui n'est pas bonne pour la langue, l'écosystème ou ses utilisateurs.

Je demande à l'équipe Go d'accepter cette proposition telle quelle, tout en continuant à rechercher d'autres itérations linguistiques sans rapport qui sont une valeur ajoutée plus claire.

Ils ne peuvent rien faire de sérieux avec le système de type actuel des années 60. Ils doivent enfin emprunter des idées des années 80 dans leur Go 2.0

De quoi parles-tu? Go s'améliore constamment. C'est incroyable d'avoir accès gratuitement à un langage, à des outils et à une documentation aussi formidables (comme dans la liberté d'expression).

@ngrilly cette dernière partie est probablement pour une discussion plus large. Sans faire dérailler cette proposition, mais en mettant un terme à ce commentaire, il y a un sentiment croissant de désalignement entre les utilisateurs et le leadership dans la communauté/l'écosystème.

Pour le reste de la discussion, je ne pense pas qu'ajouter plus de surcharge cognitive à la syntaxe soit une victoire. Je suis content qu'ils aient trouvé quelque chose qui a fonctionné pour eux, nous ne sommes pas eux.

Je viens d'ouvrir une proposition pour la déclaration if en ligne : https://github.com/golang/go/issues/32860

Référence : https://github.com/golang/go/issues/32825#issuecomment -506707539

À quel point ce monde deviendrait-il plus agréable si tous ceux qui soumettaient leur proposition sur la nouvelle fonctionnalité de golang 2.0 qu'ils aimeraient tellement avoir, fournissaient également une branche de leur fork de https://github.com/golang/go (et tout autre référentiels nécessaires) qui met en œuvre cette proposition.

Vous n'êtes pas d'accord ?

@ av86743 semble au-delà de la portée de cette proposition. Veuillez déposer une proposition suggérant ce plan d'action.

Je vois un certain défi avec cela, comme le risque de beaucoup d'efforts gaspillés avant que quelqu'un ne le refuse en se basant sur quelque chose dans le document de proposition lui-même. Ensuite, vous avez passé tout ce temps sur une fourchette qui ne sera même pas revue.

que diriez-vous de cette syntaxe :

# call error handler
try callFunction(), errorHandler()

# error handler with anonymous function
variable := try callSomething(), func(err *Error) { # error handling }

@theckman Je m'excuse s'il semble que ma suggestion de déplacer cette discussion ailleurs donne l'impression que ce n'est pas important. J'ai expliqué mes raisons dans ma demande, et je crois qu'elles tiennent toujours. L'équipe Go prend en compte les discussions sur les listes de diffusion ainsi que les propositions.

Puisque vous mentionnez "les auteurs originaux de Go", je pense qu'il convient de souligner que la proposition "essayer" a été faite par @griesemer qui est l'un des trois auteurs originaux de Go.

Je suis tout à fait d'accord avec cette proposition, je pense que la seule chose à changer est simplement go fmt, make go fmt pour autoriser une ligne if.

Je veux vraiment une ligne de

if err != nil { return wrappedErr{err} }

au lieu de trois lignes de

if err != nil {
    return wrappedErr{err}
}

@ av86743 semble au-delà de la portée de cette proposition. Veuillez déposer une proposition suggérant ce plan d'action.

@theckman Vous me dites quoi faire, et ce n'est pas seulement pas poli, c'est grossier en apparence. Vous pouvez essayer de vous positionner de la manière que vous choisissez, mais ni moi-même ni, vraisemblablement, personne d'autre ici n'est votre singe "aller chercher" pour sauter lorsque vous le dites.

Je vois un certain défi avec cela, comme le risque de beaucoup d'efforts gaspillés avant que quelqu'un ne le refuse en se basant sur quelque chose dans le document de proposition lui-même. Ensuite, vous avez passé tout ce temps sur une fourchette qui ne sera même pas revue.

Ce ne serait qu'un « effort inutile » pour [... _description dans un langage tout à fait approprié omis par souci de concision_ ...].

Pour un codeur, cependant, ce serait un exercice trivial mais utile et, en même temps, un service à la communauté Go.

@ av86743 Je pense que ce que vous avez suggéré est une idée intéressante et je ne voulais pas qu'elle se perde en tant que commentaire dans un problème sans rapport. Si vous n'avez aucun intérêt à le poursuivre à titre officiel, je m'excuse d'avoir préconisé que vous souleviez une question distincte.

Même si cette proposition spécifique vient de @griesemer , j'ai du mal à croire qu'il bouillonne de rage intérieure depuis dix ans à propos de la verbosité des retours d'erreur non emballés dans Go. C'est cependant un excellent ingénieur, et les ingénieurs trouvent des solutions aux problèmes (perçus); il est très difficile de les arrêter. Nous _aime_ résoudre des problèmes. Il suffit de renifler un problème pour commencer à proposer toutes sortes d'idées. Une fois que ce processus de réflexion est assez avancé, il est difficile pour chacun d'entre nous d'abandonner émotionnellement ses solutions putatives et de considérer que _ce n'est peut-être pas vraiment un problème après tout_. Après tout, nous avons eu un bébé, intellectuellement parlant, et ce n'est pas facile de l'abandonner.

Pour ce que ça vaut, mon soupçon personnel est que le processus de raisonnement de l'équipe Go à ce sujet s'est déroulé comme suit :

  1. Go est extrêmement populaire et largement utilisé, donc des milliers de personnes ont, naturellement, des commentaires, des suggestions et des plaintes à ce sujet.
  2. Vous ne pouvez faire du stonewall que si longtemps. L'équipe Go se sent sous une pression énorme pour faire _quelque chose_ contre tout le bruit de la communauté.
  3. C'est quelque chose.

que diriez-vous d'ajouter une fonction catch () dans defer to catch si l'essai a été trouvé une erreur comme recover ().
Exemple:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

sur de nombreuses fonctions

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
    file1 := open("file1")
    defer file1.Close()
    file2 := open("file2")
    defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

que diriez-vous d'ajouter une fonction catch () dans defer to catch si l'essai a été trouvé une erreur comme recover ().
Exemple:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

sur de nombreuses fonctions

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
  file1 := open("file1")
  defer file1.Close()
  file2 := open("file2")
  defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

Comment cela aide-t-il à gérer chaque erreur d'une manière distincte ?

Quelques précisions :

  1. La proposition try n'introduit ni une nouvelle syntaxe ni un nouveau mot-clé, contrairement à ce que certaines personnes ont affirmé sur ce fil. Il introduit simplement une nouvelle fonctionnalité intégrée, à propos du changement le plus minime que l'on puisse faire pour ajouter de nouvelles fonctionnalités. Soyez précis lorsque vous en discutez, car c'est important. Il y a une énorme différence entre l'ajout d'une nouvelle syntaxe et d'un nouveau mot-clé, et un fichier intégré. Le premier est un changement majeur, ce dernier un ajout relativement mineur. Ce que la proposition try suggère est un ajout relativement mineur.

  2. Je suis d'accord avec @ianlancetaylor que cette discussion est mieux tenue ailleurs (golang-nuts). Il n'y a pas de proposition ici.

  3. En effet, @bitfield , je n'ai aucune "rage intérieure à propos de la verbosité des retours d'erreur non emballés dans Go" , merci :-) Mais je pense que la vérification des erreurs est plus détaillée que peut-être nécessaire; et le fait que ce même sentiment ait été évoqué par la communauté à plusieurs reprises est un indicateur clair que nous (l'équipe Go) ne sommes pas seuls avec cette croyance. Je n'irais pas jusqu'à dire qu'il y a beaucoup de pression pour faire "quelque chose" - nous y travaillons depuis longtemps maintenant, et nous nous contentons d'attendre la "bonne" approche .

La proposition try concerne la solution la plus minimale que nous ayons trouvée (avec une aide significative des contributions de la communauté) qui résout le problème de vérification des erreurs. La proposition try est très explicite sur le fait qu'elle _n'aidera pas du tout_ si chaque test d'erreur nécessite de traiter l'erreur d'une manière spécifique. try n'est utile que lorsque toutes les erreurs d'une fonction sont testées et gérées de la même manière (nous vous recommandons ensuite d'utiliser defer ), ou lorsqu'elles sont simplement renvoyées. Il est difficile d'être plus explicite ici, mais répétons ce que dit la proposition : try n'aidera pas dans tous les scénarios d'erreur. Il aide un dans un nombre important de cas. Pour tout le reste, utilisez les instructions if .

@griesemer try est trop sujet aux erreurs : il n'y a pas de RAII dans Go, nous ne pouvons donc pas simplement quitter la fonction dans de nombreux cas.

@sirkon , je ne sais pas en quoi RAII est pertinent pour cette discussion. try remplace les modèles existants de if ..., err := f(); err != nil { return ..., err } par un ... := try(f()) . S'il y avait un bogue de libération de ressources en utilisant try , alors il existait certainement aussi bien avant. L'introduction de try n'améliore ni n'empêche le bogue de libération de ressources.

@sirkon , je ne sais pas en quoi RAII est pertinent pour cette discussion. try remplace les modèles existants de if ..., err := f(); err != nil { return ..., err } par un ... := try(f()) . S'il y avait un bogue de libération de ressources en utilisant try , alors il existait certainement aussi bien avant. L'introduction de try n'améliore ni n'empêche le bogue de libération de ressources.

Lisez le fil, il y avait un exemple:

info := try(try(os.Open(fileName)).Stat())

@sirkon J'ai vu cet exemple plusieurs fois maintenant. Je suis d'accord que c'est intéressant. Mais réfléchissons-y un peu plus. Le try intégré proposé est essentiellement du sucre syntaxique pour un certain type de passe-partout trouvé dans le code Go. Nous pouvons donc convertir cet exemple dans le code d'origine.

    f, err := os.Open(fileName)
    if err != nil {
        return err
    }
    info, err := f.Stat()
    if err != nil {
        return err
    }

Ce code a le même bug. J'ai certainement vu du code comme ça. Il n'est pas évident pour moi que la fonction try intégrée rend ce bogue plus facile à écrire ou plus difficile à voir.

[On dirait que @ianlancetaylor vient de me battre.]

@sirkon Ce bug est déjà possible, try ou pas - Go ne vous empêche pas d'écrire du mauvais code. Ou inverser la tendance, utiliser un mauvais code comme raison pour laquelle try ne devrait pas être autorisé n'est pas un argument convaincant. Au lieu de cela, go vet devrait signaler les cas problématiques.

defer est l'idiome Go à nettoyer lorsqu'une fonction revient, et cela fonctionne bien. L'approche correcte ici serait bien sûr:

f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

comparez ceci à :

f, err := os.Open(filename)
if err != nil {
   return ..., err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
   return ..., err
}

En utilisant try la source se concentre sur la principale préoccupation : obtenir les informations sur le fichier d'un fichier. En utilisant l'approche traditionnelle, la plupart du code source est préoccupé par les erreurs possibles ; et c'est tout pareil. Même si nous voulons décorer l'erreur, l'approche try fonctionne à merveille :

defer errd.Wrap(&err, "failed to do X for %s", filename)
f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

en utilisant quelque chose comme un package errd (voir le numéro #32676).

@griesemer
Ma future révision de code en auto-réalisation continue de crier que le mécanisme de flux de contrôle devrait être sur sa propre ligne. Cette approche peut-elle être valide (pas d'application) dans le cadre de la proposition actuelle ? Outre la lisibilité, la refactorisation vers une logique de gestion des erreurs plus détaillée est facilitée.

defer errd.Wrap(&err, "failed to do X for %s", filename)
f, err:= os.Open(filename)
try(err) // check is so much a better term
defer f.Close()
info, err := f.Stat()
try(err)

De plus, cette approche de handle fière allure ici, mais plusieurs reports ne feront-ils pas un gâchis ? Ou y aura-t-il une sorte de sync.Once à portée de fonction (je n'ai pas vu de clarification dans le problème errd) ? Si oui, les fonctions anonymes auraient-elles leur propre portée ? Et l'observation... eesh - qui est le premier, qu'est-ce qui est le deuxième ?

Tout cela donne l'impression qu'il y aura finalement deux "modes" d'écriture de code Go. Dis que ce n'est pas le cas.

@griesemer Bien que vous ayez raison, le bogue est également possible aujourd'hui, je pense fortement qu'il deviendra plus répandu à l'avenir avec l'implémentation actuelle de try. Quelqu'un venant d'à peu près n'importe quel langage populaire que je peux ().think.Of(Has)Chaining().Of(Methods) enraciné dans eux pour le meilleur ou pour le pire. Ces langues fournissent toutes leurs propres idiomes qui rendent les modèles naturels et sûrs. Ils rencontrent un obstacle immédiat avec Go car le système de types les oblige à attribuer à chaque valeur une condition d'échec associée, il n'y a tout simplement aucun modèle raisonnable pour éviter cela (ou la proposition d'essai n'existerait pas).

Si cette proposition est acceptée, ils seront un moyen d'éviter le if err , leur permettant d'écrire du code d'une manière qui leur est familière. Sauf qu'ils auront près d'une décennie de code Go dans les réponses stackoverflow, les articles de blog, etc. qui ont été écrits avant la création de try. Ils apprendront rapidement à simplement laisser tomber l'instruction err et if avec try. Ils veulent une taille de fichier dans laquelle ils peuvent coller du code enveloppé dans try jusqu'à ce qu'ils puissent accéder au champ souhaité, tout comme le Stat() dans la proposition d'essai. C'est un modèle auquel ils sont habitués, il est donc naturel qu'ils l'appliquent en écrivant Go. Étant donné que le système d'exploitation Go le plus ciblé traite tout comme un fichier, il est juste de supposer que davantage de fuites de ressources se produiront.

Ce qui m'amène à la raison pour laquelle je suis en profond désaccord avec l'affirmation « vous pouvez déjà le faire aujourd'hui » - parce que vous ne pouvez tout simplement pas. Bien sûr, vous pouvez divulguer un descripteur de fichier. Mais Go ne donne pas au programmeur la possibilité de ne pas avoir l'identifiant dans la portée et donc de divulguer le fichier. Chaque identifiant f ignoré est un descripteur de fichier divulgué. L'utilisation de la fonctionnalité _exige_ que certains idiomes importants de l'écosystème Go soient rompus. Ainsi, l'introduction de la fonctionnalité telle qu'elle est conçue aujourd'hui augmente manifestement le risque de fuite de ressources dans Go.

Cela dit, comme je l'ai mentionné dans https://github.com/golang/go/issues/32825#issuecomment -506882164, je soutiendrais en fait try si quelques petits ajustements étaient apportés, je pense que le changement serait un ajout bienvenu à la langue. Tout ce dont je pense que try a besoin, c'est de le rendre valide uniquement sur le RHS d'une affectation et de ne pas permettre à la valeur de retour d'être adressable. Faites en sorte que les "bons" exemples d'utilisation de try (il s'agisse généralement d'un essai par ligne) soient la "seule" façon d'utiliser try, c'est-à-dire :

info := try(try(os.Open(filename)).Stat()) // compiler error
f := try(os.Open(filename)) // valid
// we were forced to assign f, so we still have an identifier to Close (serve linters and users alike)
defer f.Close()
info := try(f.Stat())
a, b := try(strconv.Atoi("1")), try(strconv.Atoi("2")) // also valid 
a, b := try(strconv.Atoi("1"), strconv.Atoi("2")) // maybe?
a, b := try strconv.Atoi("1"), strconv.Atoi("2")

Je pense que cela s'intégrera naturellement mieux dans le langage, conservera tous les avantages actuels de try (autres que leur imbrication, si vous considérez cela comme un avantage) sans aucun des inconvénients. Je ne pense pas que l'imbrication de try rende service à qui que ce soit, cela permet d'économiser très peu, mais offre des possibilités illimitées d'abus. Je ne me sens pas particulièrement mal aujourd'hui, c'est donc le mieux que je puisse faire :

total := try(try(os.Open(filename)).Stat()).Size() + try(strconv.Atoi(try(ioutil.ReadAll(os.Stdin))))

Mais _nous_ penserons au pire, si vous nous le permettez.

@daved Mettre try(err) sur une deuxième ligne est entièrement pris en charge avec la proposition try existante : try veut simplement un argument qui évalue à une ou plusieurs valeurs où la dernière valeur est de tapez error , ce qui est naturellement satisfait lorsque vous écrivez try(err) .

Je ne suis pas sûr de suivre votre préoccupation concernant defer - s'il y a différents gestionnaires requis, defer pourrait ne pas être le bon choix ; à la place, le if traditionnel peut être nécessaire (comme indiqué dans la proposition).

@cstockton Je suis d'accord que les try imbriqués peuvent être très problématiques; mais je crois aussi que si nous avions try , la plupart du code ressemblerait aux exemples (valides) que

Pour une question de style, nous n'avons pas mis de restrictions telles que celle que vous privilégiez dans la langue - nous avons utilisé go vet pour cela. Au final, pour le logiciel écrit, l'effet est le même. Mais en ne l'ayant pas dans la langue, nous ne nous attachons pas. Il est difficile d'obtenir ces restrictions correctement, et elles rendent la spécification inutilement compliquée. Il est tout simplement beaucoup plus facile d'ajuster go vet et de le rendre plus intelligent à mesure que nous apprenons plus que d'ajuster la langue.

@griesemer Merci pour la clarification. Dans l'exemple de code, si la première ligne était var err error , l'encapsulation affecterait-elle potentiellement les deux erreurs vérifiées ? J'ai vu parler de l'observation comme étant une préoccupation qui pourrait être traitée à l'avenir. Comment est-ce/pourrait-il être lié à cela ?

que diriez-vous d'ajouter une fonction catch () dans defer to catch si l'essai a été trouvé une erreur comme recover ().
Exemple:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

sur de nombreuses fonctions

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
    file1 := open("file1")
    defer file1.Close()
    file2 := open("file2")
    defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

Comment cela aide-t-il à gérer chaque erreur d'une manière distincte ?

comme d'autres utilisateurs engagés

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    file1 :=try open("file1")
    defer file1.Close()
    file2 :=try open("file2")
    defer file2.Close()

        //without try
       file3,err := open("file3")
       defer file3.Close()
 }

@daved Dans ces exemples, l'hypothèse était que err est le nom du résultat error . try définira toujours cette variable d'erreur de résultat, quel que soit le nom (ou l'absence de nom). Si vous avez une variable locale appelée err alors c'est une variable différente. Si vous voulez faire référence à l'erreur de résultat, elle devra avoir un nom différent. Notez que c'est déjà le cas que ce n'est pas valide :

func f(...) (..., err error) {
   var err error // << err already declared
   ...

En revanche, si vous écrivez :

func f(...) (..., err error) {
   a, b, ... err := g() // redeclaration of err
   ...

le err dans l'affectation est simplement le même que celui nommé dans la liste des paramètres de résultat. Il n'y a rien de différent ici de ce qui était déjà le cas depuis très longtemps.

PS : Nous devrions probablement arrêter de détourner ce problème pour des discussions de try et revenir à la proposition d'origine - elle sera à nouveau déverrouillée et ouverte à la discussion demain (1er juillet).

@godcong Une fonction catch() (ou similaire) vous permettra uniquement d'obtenir l'erreur, pas de la définir (et généralement on veut définir l'erreur de la fonction englobante dans une fonction différée fonctionnant comme un gestionnaire d'erreur) . Cela pourrait fonctionner en faisant en sorte que catch() renvoie un *error qui est l'adresse de la valeur de retour d'erreur de la fonction englobante. Mais pourquoi ne pas simplement utiliser le nom du résultat de l'erreur au lieu d'ajouter un nouveau mécanisme au langage ? Voir aussi la proposition try où cela est discuté.

Voir aussi le PS ci-dessus .

@griesemer

Il est difficile d'être plus explicite ici, mais répétons ce que dit la proposition : essayez n'aidera pas dans tous les scénarios d'erreur. Il aide un dans un nombre important de cas. Pour tout le reste, utilisez les instructions if.

Je pense que c'est exactement la faille fatale de la proposition try() : alors qu'avant il n'y avait qu'une et une seule façon de vérifier les erreurs, il y en aura maintenant deux, entremêlées tout au long de la base de code. De plus, au moins pour la base de code sur laquelle je travaille, moins de 20 % de if err != nil peuvent être remplacés par try() , ce qui, bien que non négligeable, ne semble pas valoir la peine de créer un divisé en styles de gestion des erreurs.

Personnellement, j'aurais préféré une construction de gestion des erreurs suffisamment puissante pour remplacer 95% de tous les cas if err != nil . Je pense que c'est aussi ce que beaucoup de gens auraient aimé.

@griesemer Je suis d'accord que les gens apprendront et que l'outillage sera indispensable car les guides de style, les bonnes pratiques, les exemples, la documentation et ainsi de suite

Je vais changer d'angle ici, quel est le cas d'utilisation des instructions d'essai d'imbrication suffisamment fortes pour les effets secondaires potentiels que j'ai décrits ? Comment le code Go bénéficie-t-il aujourd'hui en permettant à une partie de parenthèses séparées par essai pouvant être imbriquées en chaîne d'apparaître n'importe où ? Je suppose que ce n'est pas le cas et je ne pense pas que quiconque ait demandé d'essayer l'imbrication, cela est venu avec la proposition car elle est implémentée en tant que fonction. Vous ne voulez pas ajouter de contraintes telles que la suppression de l'imbrication / l'adressage pour limiter les abus d'imbrication ou les erreurs subtiles, car cela rendrait la spécification du langage plus complexe. Le thème est-il ici pour éviter d'introduire de la complexité dans le langage ou pour ajouter une meilleure façon de gérer les erreurs ?

Parce que si le but ici est de ne pas rendre la spécification du langage plus complexe, le choix est clair : ne pas ajouter de nouvelle fonction avec des retours et paramètres génériques, est arbitrairement emboîtable, fournit un flux de contrôle et modifie l'arité des valeurs qui lui sont données ( mais seulement s'ils satisfont à une interface intégrée spécifique) et probablement plus que j'oublie, par exemple une fonction d'une complexité sans précédent. Si l'objectif est d'améliorer la gestion des erreurs, je pense qu'il devrait le faire sans introduire de nouvelles façons de produire des erreurs.

@sirkon J'ai vu cet exemple plusieurs fois maintenant. Je suis d'accord que c'est intéressant. Mais réfléchissons-y un peu plus. Le try intégré proposé est essentiellement du sucre syntaxique pour un certain type de passe-partout trouvé dans le code Go. Nous pouvons donc convertir cet exemple dans le code d'origine.

    f, err := os.Open(fileName)
    if err != nil {
        return err
    }
    info, err := f.Stat()
    if err != nil {
        return err
    }

Ce code a le même bug. J'ai certainement vu du code comme ça. Il n'est pas évident pour moi que la fonction try intégrée rend ce bogue plus facile à écrire ou plus difficile à voir.

Il est évident pour moi que le flux traditionnel "plus lent" laisse plus de place pour remarquer que le fichier doit être fermé et ce try provoque ce genre de fuites car les gens ont tendance à préférer les raccourcis aux longs chemins.

@godcong Une fonction catch() (ou similaire) vous permettra uniquement d'obtenir l'erreur, pas de la définir (et généralement on veut définir l'erreur de la fonction englobante dans une fonction différée fonctionnant comme un gestionnaire d'erreur) . Cela pourrait fonctionner en faisant en sorte que catch() renvoie un *error qui est l'adresse de la valeur de retour d'erreur de la fonction englobante. Mais pourquoi ne pas simplement utiliser le nom du résultat de l'erreur au lieu d'ajouter un nouveau mécanisme au langage ? Voir aussi la proposition try où cela est discuté.

Voir aussi le PS ci-dessus .

Le système de types de Go est resté bloqué dans les années 60 et est donc naturellement incapable de bien gérer les cas de bord. Si vous étiez assez clairvoyant pour emprunter des idées des années 80, vous auriez des méthodes pour contrôler les flux subtils sujets aux erreurs. Vous essayez maintenant de construire un bâtiment en verre et en métal dans un village médiéval : la mauvaise chose que ces villages médiévaux n'ont pas d'électricité et de conduites d'eau donc vous n'en aurez pas non plus.

Il sera intéressant de voir dans quelle mesure les installations d'erreur nouvelles et améliorées seront utilisées dans golang/go lui-même. Si pas du tout.

Il sera également intéressant de voir si go2 fmt aura une option pour afficher en clair go1.x .

D'après ma propre expérience, depuis que j'ai ajouté du contexte dans mon erreur renvoyée par :

import "github.com/pkg/errors"
func caller(arg string) error {
  val, err := callee(arg)
  if err != nil {
    return errors.Warpf(err, "failed to do something with %s", arg)
  }

  err = anotherCallee(val)
  if err != nil {
    return errors.Warpf(err, "failed to do something with %s", val)
  }

  return nil
}

l'équipe de support a rarement besoin de ma contribution pour les problèmes survenant en production.

À mon humble avis, je pense que l'amélioration de la gestion des erreurs ne consiste pas à réduire le code passe-partout, mais à fournir un moyen plus pratique d'ajouter du contexte aux erreurs. Je ne peux toujours pas trouver un bon moyen sensé d'utiliser try().

Peut-être ajouter un contexte en différé :

func caller(arg string) (err error) {
  defer func() {
    switch t := err.(type) {
      case CalleeErr:
        err = errors.Wrapf(err, "failed to do something with %s", arg)
      case AnotherCalleeErr:
        err = errors.Wrapf(err, "failed to do something with %s", val)
    }
  }()

  val := try(callee(arg))
  try(anotherCallee(val)
  return nil
}

Cela ne semble pas économiser beaucoup de frappe, mais nous sacrifions la lisibilité et les performances.

Pourrait finir par utiliser try() de cette manière :

func caller(arg string) error {
    val, err := callee(arg)
    try(errors.Warpf(err, "failed to do something with %s", arg))

    err = anotherCallee(val)
    try(errors.Warpf(err, "failed to do something with %s", val))

    return nil
  }

Il réduit de quelques lignes, c'est tout.

pour moi, la plupart des solutions à ce problème semblent briser la seule chose que je pensais séparée des autres langages qui utilisent des exceptions : le mécanisme de gestion des erreurs n'est pas utilisé comme flux de contrôle. La plupart de ces solutions ajoutent une forme de flux de contrôle (vérifier/gérer, essayer, attraper, attendre) auquel cas je pense que l'équipe Go pourrait aussi bien ajouter des exceptions et l'appeler un jour.

Bien que j'adorerais si nous pouvions avoir un cas spécial de if et return qui ressemble un peu au rubis

return err if err != nil

PS Pardonnez mon inexpérience en conception de langage, si j'ai dit quelque chose de stupide, n'hésitez pas à le signaler et à m'éduquer.

que diriez-vous d'ajouter une fonction catch () dans defer to catch si l'essai a été trouvé une erreur comme recover ().
Exemple:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

sur de nombreuses fonctions

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
  file1 := open("file1")
  defer file1.Close()
  file2 := open("file2")
  defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

Comment cela aide-t-il à gérer chaque erreur d'une manière distincte ?

comme d'autres utilisateurs engagés

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

  file1 :=try open("file1")
  defer file1.Close()
  file2 :=try open("file2")
  defer file2.Close()

        //without try
       file3,err := open("file3")
       defer file3.Close()
 }

Dans votre exemple, vous gérez toutes les erreurs par le même deffer. Que se passe-t-il si vous souhaitez ajouter un message personnalisé et des informations personnalisées à l'erreur ?

@ianlancetaylor Quelqu'un a-t-il suggéré d'augmenter l'opérateur ":=" pour prendre en charge les "retours inférés" - Se comporter exactement comme try sans appel de fonction. Cela résoudrait beaucoup de problèmes que j'ai vu des deux côtés:

  • try tant que nom de la fonctionnalité a été controversé, tant que nous l'implémentons en tant que fonction, nous sommes bloqués en lui donnant un nom qui, je ne suis pas sûr, que quiconque se sente 100% bien.
  • try fait un nombre sans précédent de choses, il a une entrée comme append() et affecte le flux de contrôle comme panic() tout en prenant la place d'un modèle omniprésent ( if err != nil ) .
  • try imbrication de
  • try est implémenté en tant que fonction pour maintenir la compatibilité descendante

Je pense que si nous devions simplement déduire des retours comme nous le faisons avec les types, les choses semblent concises et semblent plus "Go" comme :

f, err := os.Open(filename)
if err != nil {
   return ..., err
}
defer f.Close()

info, err := f.Stat()
if err != nil {
   return ..., err
}

_Insérez ici une description informatique correcte de ce comportement_, jusque-là, contentez-vous des retours inférés via des déclarations de variables courtes :

f := os.Open(filename)
defer f.Close()
info := f.Stat()

Ce qui a l'air beaucoup plus ordonné que :

f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

Cela résout toutes les préoccupations que j'ai énumérées ci-dessus tout en se sentant (à mon avis) un peu plus « Go like » et en maintenant la rétrocompatibilité. Cela semble aussi un peu plus facile à expliquer, le retour "implicite" de l'appel de fonction pour try() semble vraiment déplacé étant donné la signification omniprésente de try dans toutes les autres langues. Je ne peux pas parler à 100% pour la mise en œuvre, mais il semble que cela pourrait potentiellement être fait avec à peu près le même effort ? Le AssignStmt pourrait avoir un champ ajouté qui spécifie quelles expressions sur le LHS ont omis leurs valeurs d'erreur pour informer la même compilation backend qu'une fonction intégrée ?

J'aime la vérification des erreurs en l'état, mais s'il s'agit vraiment d'un problème qui doit être résolu, je pense qu'un nouveau mot-clé est probablement une meilleure solution. Toute solution impliquera probablement des changements de flux de contrôle, et il est préférable de rendre cela aussi évident que possible.

Dans cet exemple, la condition on est évaluée à chaque fois que err est défini.

func example() (foo int, err error) {
    on err != nil {
        return foo, err
    }

    foo, err = calcThis()
    foo, err = calcThat(foo)

    return
}

Cela fonctionne également sans déclarer les noms des valeurs de retour dans la signature de la fonction.

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()
    foo, err = calcThat(&foo)

    return &foo, nil
}

Cela peut également être réglé plusieurs fois. Voici un exemple artificiel :

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()

    on err != nil {
        return &foo, err
    }

    foo, err = calcThat(&foo)

    return
}

À mes yeux, cela conserve le style et la lisibilité de Go, tout en précisant ce qui se passe à chaque étape (une fois que vous avez compris le paradigme), car il insère effectivement cette condition après chaque occurrence de err définie.

Vous pouvez même effectuer les opérations suivantes :

func example() (foo int, err error) {
    var message string

    on err != nil {
        return foo, errors.Wrap(err, message)
    }

    message = "failed to calc this"
    foo, err = calcThis()

    message = "failed to calc that"
    foo, err = calcThat(foo)

    return
}

Enfin, cela a une applicabilité au-delà de la gestion des erreurs. Voulez-vous revenir si foo == 0 ? Autre exemple artificiel :

func example(i int) bool {
    on x == 0 {
        return false
    }

    x = calcSomeInt(i)
    return true
}

@cstockton

J'étais sur le point de protester qu'il serait un peu trop facile d'éliminer les erreurs de cette façon, mais :

  • C'est déjà un piège avec des fonctions qui renvoient _seulement_ des erreurs, telles que json.Unmarshal . Les bons réviseurs de code IME apprennent à surveiller cela assez rapidement.
  • Ce serait toujours une erreur de syntaxe dans les fonctions qui ne renvoient pas de valeurs d'erreur, telles que les méthodes http.Handler .
  • Si vous saviez comment gérer l'erreur, vous y penseriez, vous l'anticiperiez et vous saisiriez probablement la valeur de l'erreur de toute façon _pour que_ vous puissiez la gérer.
  • Si votre intention était de simplement renvoyer toutes les erreurs rencontrées pour en faire le problème de quelqu'un d'autre (l'appelant), cela accomplit cela, avec le petit inconvénient que vous manquez une occasion d'ajouter explicitement plus d'annotations pour le contexte.

Donc, après avoir pesé tous les avantages auxquels je peux penser et n'avoir remarqué aucun inconvénient évident, je suis... sur la clôture. Je soupçonne qu'il y a des inconvénients non évidents que je n'ai pas pris en compte. Mais je commence à aimer où cela va, pour peu que cela ait de valeur pour personne.

J'espère que c'est la bonne façon de répondre et je ne m'engage pas
grave faux pas.

Le 01/07/19, Chris Stockton [email protected] a écrit :

@ianlancetaylor Quelqu'un a-t-il suggéré d'augmenter l'opérateur ":=" pour prendre en charge
"retours inférés" - Se comporte essentiellement comme try sans fonction
appel. [ ... ]

Ce que je trouve intrigant dans ce cas, c'est que nous avons quelque chose
analogue au paradigme "virgule OK", où l'on omet maintenant le "err"
cessionnaire est admissible dans certaines circonstances bien définies. Valeur
notant, mais clairement pas en soi suffisant pour en faire un
proposition.

Lucio.

Ce bug est déjà possible, essayez ou non - Go ne vous empêche pas d'écrire du mauvais code. Ou inverser la tendance, utiliser un mauvais code comme raison pour laquelle try ne devrait pas être autorisé n'est pas un argument convaincant. Au lieu de cela, aller chez le vétérinaire devrait signaler les cas problématiques.

@griesemer je ne suis pas d'accord. Bien qu'elle puisse économiser des frappes, l'arithmétique du pointeur a été exclue de Go en partant du principe qu'elle facilite l'écriture de code erroné et défectueux. Je pense que c'est le même type de fonctionnalité qui rendra les bugs plus difficiles à détecter.

data := try(ioutil.ReadAll(try(os.Open("foo.txt"))))

L'exemple typique avec try utilise des exemples comme celui-ci, ce qui pour moi laisse évidemment échapper un descripteur de fichier. (Le fait que Go traque et ferme de tels descripteurs dans l'environnement d'exécution à l'insu des utilisateurs dans l'implémentation actuelle est quelque chose qui pourrait nous rassurer, mais il y a quand même un meilleur exemple).

data := try(ioutil.ReadAll(try(http.Get("http://example.com/")).Body))

Ce qui précède se lit comme un code correct, mais ignore l'exigence selon laquelle le corps de la réponse doit être lu dans son intégralité, puis fermé par la suite sur le chemin heureux. Si vous le regardez assez longtemps, l'élégance dégoûtante de l'exemple incorrect devrait montrer que nous verrons ce type de bogues à l'avenir.

En tant que personne qui révise plus qu'écrit du code à ce stade, je préférerais que Go n'ajoute pas de fonctionnalités qui rendent l'inexactitude si tentante.

@jesse-amano L'utilisation de l'opérateur d'affectation empêche en fait ce cas d'être possible, sans déclaration d'affectation explicite, les éléments ci-dessous se comportent exactement comme ils le font aujourd'hui, c'est-à-dire :

json.Unmarshal(...)
(http.Handler)(h).ServeHTTP(w, r)

Quant aux valeurs qui renvoient simplement une erreur, elles peuvent être renvoyées comme return json.Unmarshal(...) et peuvent également être représentées sous une forme plus compacte car il n'est pas nécessaire qu'une valeur existe en dehors du bloc if.

if err := json.Unmarshal(...); err != nIl {
    return err
} 

Ce que je trouve intrigant dans ce cas, c'est que nous avons quelque chose d'analogue au paradigme « virgule OK », où l'omission du cessionnaire « erreur » est désormais autorisée dans certaines circonstances bien définies. À noter, mais clairement pas suffisant en soi pour en faire une proposition valable.

Le comportement serait identique à essayer sans parenthèses ni imbrication et chaînage arbitraires. Je pense qu'il sera difficile de trouver quelque chose qui semble naturel à la majorité des gens sans casser la langue. J'admets qu'il semble que les auteurs de Go soient assez déterminés à ajouter ce type de fonctionnalité à Go. Je cherche vraiment des alternatives car je suis absolument convaincu que try dans sa forme actuelle n'est pas un bon choix pour Go. C'est la chose la plus proche à laquelle je peux penser qui ne brisera pas la Colombie-Britannique, mais peut-être que cela ne semble toujours pas juste pour suffisamment de gens. Quoi qu'il en soit, j'espère que la proposition d'essai sera refusée ou que quelqu'un proposera quelque chose sur lequel beaucoup plus de gens pourront s'entendre.

edit : @jesse-amano j'ai complètement raté ton point désolé ! Je suppose qu'être à l'intérieur d'une fonction qui ne renvoie pas d'erreur afficherait une erreur de compilation typique de non-concordance du nombre d'affectations? J'imagine que try introduirait probablement un nouveau type de message d'erreur du compilateur pour cela.

@beoran Concernant https://github.com/golang/go/issues/32825#issuecomment -507126700 : La gestion des erreurs est déjà différente d'une situation à l'autre : Parfois nous retournons l'erreur inchangée, parfois nous retournons une erreur décorée, parfois nous agissons en cas d'erreur, et parfois nous (pouvons correctement) ignorer une erreur. La seule chose qu'ils partagent tous (sauf lorsque nous ignorons l'erreur) est le test err != nil (que nous pouvons déjà faire de plusieurs manières). Autant il serait bien qu'une nouvelle fonctionnalité de langage capture tous ces cas (ou 95% d'entre eux), une telle construction est très susceptible d'interférer de manière non orthogonale avec d'autres constructions de contrôle que nous avons déjà. C'est-à-dire que le mieux que nous puissions espérer est d'améliorer certains de ces cas (peut-être 20 %, peut-être 50 % d'entre eux).

@cstockton Concernant https://github.com/golang/go/issues/32825#issuecomment -507136111: Si les try imbriqués sont le seul barrage routier restant et go vet n'est pas assez bon , je pense que nous pouvons envisager d'interdire les try imbriqués - ce serait assez facile. Mais pour le moment, je pense que nous sommes encore dans la phase FUD (peur, incertitude et doute), avec pratiquement personne n'ayant réellement expérimenté avec try sérieusement. Je note que les personnes qui l'ont fait, ont rapporté positivement.

PS : Le problème try est à nouveau ouvert pour les commentaires. Continuons là-bas.

@cstockton Oh, absolument. Pour clarifier, le point que je recherchais était qu'il est _déjà_ une mauvaise pratique d'appeler des fonctions comme json.Unmarshal sans capturer la valeur d'erreur dans la majorité des cas, mais généralement pas considéré comme une mauvaise pratique à defer file.Close() place de defer func() { err = file.Close(); if err != nil { ... }; }() , et nous avons appris à faire la différence assez facilement. Il en sera (probablement) avec le changement que vous proposez. J'ai d'abord hésité à l'idée que quelqu'un utilise innocemment foo := strconv.ParseFloat(bar, 64) alors qu'il avait l'intention de gérer l'erreur, mais après une brève réflexion, je ne pense pas vraiment que ce soit un problème après tout.

@sirkon Concernant https://github.com/golang/go/issues/32825#issuecomment -507167388 : Veuillez laisser ces déclarations clairement sans réserve en dehors de la discussion - elles n'ont pas leur place ici. Pour info, beaucoup d'idées dans Go datent en fait des années 80 (packages, compilation séparée, etc.) plutôt que des années 60 et beaucoup sont beaucoup plus récentes (goroutines, interfaces). Ces idées peuvent encore sembler anciennes, mais elles ont résisté à l'épreuve du temps (du moins depuis le peu de temps que CS existe).

@turtleDev Concernant https://github.com/golang/go/issues/32825#issuecomment -507231353 : Go gère les exceptions, et c'est fait avec panic et defer et recover - nous ne l'appelons tout simplement pas "gestion des exceptions" car ce terme a une connotation trompeuse pour Go. Mais juste pour être clair, Go peut faire tout ce que raise avec try-catch peut faire, et plus (parce que contrairement à try-catch , defer peut être utilisé conditionnellement). Merci.

@sorenvonsarvort Concernant https://github.com/golang/go/issues/32825#issuecomment -507268293 : Si vous voulez une décoration d'erreur différente pour chaque cas, utilisez une instruction if . Veuillez consulter le document de conception. Cette question a déjà reçu de nombreuses réponses. Merci.

@cstockton Concernant https://github.com/golang/go/issues/32825#issuecomment -507306652 : Oui, nous avons pensé à un tel mécanisme. Plus précisément, nous avons pensé à « inverser la situation » et au lieu de fournir try , fournissez simplement handle , qui déclare un gestionnaire d'erreurs. Si un gestionnaire est présent (et seulement alors), on laissera simplement de côté le err dans le LHS d'une affectation, et il sera vérifié implicitement (comme avec un try invisible). C'est joli, mais c'est aussi complètement invisible, ce qui est un gros drapeau rouge. Nous voulons que la gestion des exceptions soit explicitement visible dans le code, à chaque endroit. Sans cela, il serait presque impossible de lire le code et de voir où se produit la vérification des erreurs.

@griesemer merci pour la clarification. mais la panique et la récupération ont des cas d'utilisation différents et sont pour la plupart très difficiles à trouver dans n'importe quelle base de code de production. cela ne vous laisse qu'un contrôle limité des constructions de flux. l'ajout d'une nouvelle construction briserait cette cohérence car vous avez maintenant un nouveau contrôle de la construction de flux qui fait quelque chose comme return.

@matthew-noken Concernant https://github.com/golang/go/issues/32825#issuecomment -507323973 : Vous proposez une idée intéressante ; cela ressemble beaucoup au mécanisme de point de surveillance d'un débogueur. Il y a quelques questions auxquelles il faudrait répondre : Le bloc on doit-il revenir (je suppose que oui, car sinon vous entrez dans le code spaghetti) ? Peut-on avoir plus d'une telle déclaration on ? À quel point la condition on peut-elle être compliquée (elle devra être évaluée à chaque affectation) ? Notez que nous ne pouvons pas avoir d'expressions arbitraires car nous devons identifier la variable de manière unique avec l'instruction on . Aussi, un peu un anathème dans Go : la construction on implique un code invisible à exécuter ailleurs.

Si vous voulez explorer cela plus, je suggère d'en discuter ailleurs (golang-nuts, ou une nouvelle proposition différente). Merci.

@as Concernant https://github.com/golang/go/issues/32825#issuecomment -507345821 :

l'arithmétique du pointeur a été exclue de Go en partant du principe qu'elle facilite l'écriture de code défectueux et défectueux

En fait, cela a été exclu car cela aurait rendu le ramasse-miettes difficile ou impossible (et oui, on peut aussi écrire du code dangereux). Mais le point le plus important ici est qu'il y avait des preuves concrètes et une expérience qui ont soutenu cette décision.

Il n'y a aucune expérience et aucune preuve encore que les try imbriqués vont être répandus ou communs. Mais voir https://github.com/golang/go/issues/32825#issuecomment -507358397.

@turtleDev Concernant https://github.com/golang/go/issues/32825#issuecomment -507368167 : Un panic est _exactement_ une exception, et recover dans une fonction différée est essentiellement un catch . Ils peuvent être plus difficiles à trouver dans le code de production car en Go, nous vous déconseillons d'écrire votre code en utilisant des exceptions ; ils ne doivent être utilisés que dans des circonstances exceptionnelles.

Concernant le nombre de structures de flux de contrôle : La proposition est très claire que try est simplement du sucre syntaxique, rien d'autre.

J'ai essayé de répondre à certains des commentaires récents dans ce fil sur ce fil. Mais continuons les nouveaux commentaires sur la proposition try dans le vrai try numéro 32437 (maintenant à nouveau déverrouillé à partir d'aujourd'hui) ; et laissez ce problème réservé à la discussion leave err != nil alone . Merci.

@cstockton Un autre commentaire sur https://github.com/golang/go/issues/32825#issuecomment -507306652 : Si nous avons implémenté cela, alors en commençant par

    func f() int { ... }
    ...
    x := f()

et changer pour

    func f() (int, error) { ... }

signifierait que le comportement de x := f() serait soudainement et silencieusement très différent.

J'ai mené des expériences similaires à ce que @lpar a fait sur tous nos référentiels go. Les résultats sont dans cet aperçu : https://gist.github.com/freeformz/55abbe5da61a28ab94dbb662bfc7f763

@ianlancetaylor Je pense en fait que cela fonctionnerait très bien dans la plupart des cas et rendrait l'introduction d'un meilleur rapport d'erreurs beaucoup moins impactante. Considérez des exemples complets pour les deux cas principaux, d'abord l'appelant renvoie une erreur :

func f() int { ... }
func a() error {
    x := f() // if f() is updated to return an error, we get automatic error propagation by default
    ...
}

func b() {
    x := f() // if f() is updated to return an error, we get the same compiler error 
    // assignment mismatch: 1 variable but pkg.f returns 2 values
}

Je pense que dans le cas courant, c'est un bel avantage pour cette approche, les cas de coin où cela crée un problème, je pense, sont déjà fragiles. Le seul auquel je puisse penser qui pourrait être vraiment méchant est le blocage d'un mutex :

func (t *T) a() error {
   t.mu.Lock()
   x := f() // if f() is updated to return an error, we get automatic error propagation by default
   if x > 15 {
     t.mu.Unlock()
     return errors.New("t > 15")
   }
   ...
}

Mais je pense que le code qui est écrit comme ça est déjà sensible aux blocages s'il s'appuie sur un appel de bibliothèque étrangère pour réussir à avoir un état de programme valide. S'il est stocké dans une portée qui peut vivre au-delà de la panique, il peut alors se bloquer si la même bibliothèque introduit une panique d'exécution via NPE. De plus, l'une des principales motivations pour écrire du code comme celui-ci est le coût historique des reports de vie sur le tas. Avec l'amélioration des performances des single-defers vivant sur la pile, un tel code n'est plus vraiment nécessaire. Je pense que toute dérivation de ce type d'erreur est facilement corrigée.

Enfin, comme les arguments de soutien de l'insuffisance de « essayer » avec l'outillage peuvent également être appliqués ici. Compte tenu de l'adoption croissante des go-modules, nous avons une belle opportunité d'injecter une étape de "mise à niveau de bibliothèque" pour mettre clairement ces changements devant un utilisateur.

@griesemer

En ce qui concerne le nombre de structures de flux de contrôle : La proposition est très claire : essayer est simplement du sucre syntaxique, rien d'autre.

Excusez-moi, mais try ne sera pas une macro (comme C) donc en fait, pour l'utilisateur final, c'est juste un autre contrôle de l'instruction de flux.

Je crois que je n'ai pas d'arguments objectifs à ce stade, donc je reconnais que nous avons peut-être besoin d'une meilleure gestion des erreurs, mais j'ai l'impression que try n'est peut-être pas la meilleure solution.

Encore une fois, ce sont mes opinions et je ne fais que les représenter. Je suis sûr que l'équipe de Go a beaucoup plus réfléchi à cela que je ne le ferai jamais.

Côté : Il me semble étrange que ce numéro ait 1335 votes positifs alors que la proposition try (#32437) n'a que 279 votes négatifs. Je m'attendrais à ce que les personnes qui votent pour cela votent contre la proposition try afin que les sentiments de la communauté à ce sujet soient plus apparents, car ces deux propositions s'excluent mutuellement.

@griesemer

La gestion des erreurs est déjà différente d'une situation à l'autre : parfois nous retournons l'erreur inchangée, parfois nous retournons une erreur décorée, parfois nous agissons sur une erreur, et parfois nous (pouvons correctement) ignorer une erreur.

D'accord là, c'est évident.

La seule chose qu'ils partagent tous (sauf lorsque nous ignorons l'erreur) est le test err != nil (que nous pouvons déjà faire de plusieurs manières). Autant il serait bien qu'une nouvelle fonctionnalité de langage capture tous ces cas (ou 95% d'entre eux), une telle construction est très susceptible d'interférer de manière non orthogonale avec d'autres constructions de contrôle que nous avons déjà. C'est-à-dire que le mieux que nous puissions espérer est d'améliorer certains de ces cas (peut-être 20 %, peut-être 50 % d'entre eux).

L'instruction try() proposée "interfère" également avec if et return de manière non orthogonale, donc je dirais que cet argument n'est pas correct. Certaines personnes ici n'aiment pas try() pour cette raison, mais je ne suis pas d'accord. Go n'est pas Oberon, c'est simple mais pas minimaliste, Go est plus pratique.

Là où nous ne sommes pas d'accord, c'est qu'il vaut même la peine de s'embêter avec une construction de langage qui, comme vous l'avez vous-même admis, n'a qu'une utilisation et une applicabilité limitées, et qui peut déjà être fait correctement avec if et return . Je pense que beaucoup de gens, comme moi, qui ont donné leur feu vert à ce fil sont tellement déçus par try() qu'ils préfèrent ne pas l'avoir du tout. Même s'il n'est pas orthogonal au retour, un try() plus puissant et plus généralement utile est probablement ce que la plupart des programmeurs Go aimeraient voir.

@beoran ,

Vous avez écrit que vous voudriez un try() "plus puissant", "plus généralement utile".

@griesemer a mentionné 4 situations :

  1. Renvoyer l'erreur inchangée
  2. Retourner une erreur décorée
  3. Agir sur une erreur
  4. Ignorer une erreur

try() résout 1 par conception : c'est littéralement un raccourci pour if err != nil { return ..., err } .

Les constructions de langage existantes résolvent 3 et 4. Nous pouvons déjà agir sur une erreur avec if err != nil { ... } et il sera difficile de trouver une structure plus concise dans ce cas. On peut déjà ignorer une erreur avec _ .

Cela nous laisse avec 2 (retourner une erreur décorée). La proposition try() suggère d'utiliser une instruction defer pour décorer l'erreur, ou si chaque erreur doit être décorée différemment, utilisez une construction standard if err != nil { ... } .

Le raisonnement est bien expliqué dans cette partie de la proposition :

Notre première itération de cette proposition a été inspirée par deux idées de Key Parts of Error Handling , qui consiste à utiliser un opérateur intégré plutôt qu'un opérateur, et une fonction Go ordinaire pour gérer une erreur plutôt qu'une nouvelle construction de langage de gestionnaire d'erreurs. Contrairement à ce message, notre gestionnaire d'erreurs avait la signature de fonction fixe func(error) error pour simplifier les choses. Le gestionnaire d'erreurs serait appelé par try en présence d'une erreur, juste avant le retour de try de la fonction englobante. Voici un exemple:

handler := func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
}

f := try(os.Open(filename), handler)              // handler will be called in error case

Bien que cette approche ait permis la spécification de gestionnaires d'erreurs définis par l'utilisateur efficaces, elle a également ouvert de nombreuses questions qui n'avaient manifestement pas de réponses correctes : que devrait-il se passer si le gestionnaire est fourni mais qu'il est nul ? try paniquer ou le traiter comme un gestionnaire d'erreurs absent ? Que se passe-t-il si le gestionnaire est invoqué avec une erreur non nulle et renvoie ensuite un résultat nul ? Cela signifie-t-il que l'erreur est « annulée » ? Ou la fonction englobante devrait-elle retourner avec une erreur nulle ? Il n'était pas non plus clair si l'autorisation d'un gestionnaire d'erreurs facultatif conduirait les programmeurs à ignorer complètement la gestion appropriée des erreurs. Il serait également facile de gérer correctement les erreurs partout, mais de rater une seule occurrence d'un try . Et ainsi de suite.

L'itération suivante a supprimé la possibilité de fournir un gestionnaire d'erreurs défini par l'utilisateur au profit de l'utilisation de defer pour l'encapsulation des erreurs. Cela semblait être une meilleure approche car cela rendait les gestionnaires d'erreurs beaucoup plus visibles dans le code. Cette étape a éliminé toutes les questions concernant les fonctions facultatives en tant que gestionnaires d'erreurs, mais a exigé que les résultats d'erreur soient nommés si l'accès à celles-ci était nécessaire (nous avons décidé que cela était ok).

[...]

Si nous déterminons plus tard qu'avoir une forme de fonction de gestionnaire d'erreurs explicitement fournie, ou tout autre paramètre supplémentaire d'ailleurs, est une bonne idée, il est trivialement possible de passer cet argument supplémentaire à un appel try.

Vous n'êtes pas d'accord avec ce raisonnement ?

Je connais cette partie de la proposition et je ne suis pas d'accord avec elle, car le fait que des questions aient été ouvertes en raison de l'idée de passer un gestionnaire d'erreurs ne signifie pas que nous n'avons pas besoin d'une telle fonctionnalité. Cela signifie simplement que nous devons bien réfléchir pour répondre à ces questions de manière raisonnable.

De plus, maintenant, nous gérons les 4 cas d'utilisation d'erreur avec une instruction if err != nil , qui est un idiome Go cohérent et largement compris. En utilisant uniquement try() pour le cas 1, et éventuellement pour le cas 2, si cela ne nous dérange pas de faire un emballage d'erreur dans une instruction defer, cela signifie que le code pour la gestion des erreurs sera divisé en if et try manière incohérente, et si la gestion des erreurs change, nous devrons basculer entre l'un et l'autre.

Une version plus puissante de try() , qui pourrait être utilisée dans tous les cas, nous permettrait d'utiliser presque toujours try() , et cela deviendrait alors le nouvel idiome cohérent de gestion des erreurs pour Go.

Cependant, avec try() tel qu'il est maintenant, il n'est pas assez largement applicable, et il n'y a tout simplement pas assez de commodité pour introduire l'incohérence susmentionnée dans la gestion des erreurs dans nos bases de code. C'est pourquoi je me sens dépassé par la proposition actuelle de try() , et je pense que ne rien faire est mieux.

@beoran Je pense que les cas 1 et 2 ont en commun de renvoyer une erreur de la fonction (respectivement sans et avec décoration), alors que les cas 3 et 4 ne le font pas (respectivement agit sur une erreur et ignore une erreur). Je pense que 'try()' se concentre sur les cas 1 et 2.

Et si la proposition try() pouvait être améliorée pour gérer les cas 1 et 2, en acceptant une fonction de gestionnaire facultative ? Bien sûr, cela nécessite de trouver des réponses raisonnables aux questions énumérées dans la proposition, mais cela semble traitable. Souhaitez-vous soutenir quelque chose comme ça?

Me voici dehors. Si c'est le cas, nous voulons que les utilisateurs vérifient les erreurs, nous devrions probablement ajouter les erreurs vérifiées (un peu comme les exceptions vérifiées). De cette façon, nous sommes aussi explicites que possible et l'utilisateur sait qu'il vérifie toutes les erreurs.

Plus sérieusement, si j'étais celui qui a pris ces décisions, j'aimerais vraiment réutiliser l'opérateur Kotlin ? ou quelque chose comme le comportement de rouille unwrap() .

L'un ou l'autre de ceux-ci, je pense, serait une amélioration:

getFile()?.getPath()?.toString()

où vous obtenez un nil retour s'il y a eu une erreur en cours de route ou

get_file().unwrap().get_path().unwrap().lossy_to_string()

où il explose à mi-parcours s'il y a une erreur. Rust traite le second en ayant une fonction expressive match qui vous permet de faire une recherche exhaustive des erreurs et de les gérer séparément, le tout renvoyant une valeur quelconque en amont de la chaîne.

Le 02/07/19, Nicolas Grilly [email protected] a écrit :

@beoran Je pense que les cas 1 et 2 ont en commun de renvoyer une erreur du
fonction (respectivement sans et avec décoration), tandis que les cas 3 et 4
ne le faites pas (respectivement agir sur une erreur et ignorer une erreur). Je pense 'essayer ()'
l'accent est mis sur les cas 1 et 2.

Je suis un peu déçu de ne pas voir plus de discussion sur ma suggestion
que c'est la partie "retour" de "retour d'erreur" qui doit être
adressé, pas la partie "erreur".

Là encore, j'aurais peut-être dû suivre une approche plus officielle. je suis
tout simplement pas doué pour formuler des propositions, j'ai tendance à parcourir
endroit.

Les cas 1 et 2 sont, à mon avis, mieux servis par une commande "fail"
mot-clé qui indique clairement (après s'y être habitué) un
changement dans le déroulement du programme et qui ne fait l'objet d'aucune
tradition gênante en ce qui concerne sa syntaxe complète.

Ce qui s'ensuit, cependant, que cela nous plaise ou non, c'est que
le positionnement de "err" comme dernier paramètre de retour est bientôt à
devenir loi plutôt que convention. C'est l'une des nombreuses « non intentionnelles » ou
conséquences « collatérales » qui doivent être prises en considération.

Il y aura beaucoup d'autres conséquences de ce genre, de minimes à
désastreux, certains qui ne se greffent que de manière opportuniste sur le
proposition. Personnellement, je pécherais par excès de prudence, je ne
comprendre pourquoi la Go Team et Robert en particulier recherchent
soutien et sans aucun doute sont blessés que la résistance s'est avérée si
super.

C'est un choc d'idéologies politiques, peut-être faut-il creuser beaucoup
plus profond pour chercher les vraies racines qui nécessitent une attention de jardinage.

@lootch Êtes-vous particulièrement préoccupé par le fait que l'erreur doit être le dernier paramètre de retour pour que try fonctionne ? N'est-ce pas déjà une convention de facto ?

(Soit dit en passant, nous ne sommes pas "blessés" par la résistance - surtout sidérés par la réaction démesurée.)

@griesemer désolé pour l'analogie avec les voitures anciennes, mais les propositions ressemblent à "Nous avons décidé d'ajouter soit un système de carburant diesel à toutes les voitures à essence, soit une batterie et un moteur électrique à toutes les voitures à essence." La gestion des erreurs de Go est assez bonne. Je crains que la valeur ajoutée soit inférieure aux coûts, y compris les nouvelles armes à pied et les frais généraux mentaux.

Je veux juste écrire un commentaire rapide disant qu'ayant beaucoup écrit sur Go (j'utilise Go depuis sa première version publique en 2009 - voir mon github), je serais ravi d'améliorer l'ergonomie autour de la gestion des erreurs. Bien que l'explicitation de la gestion des erreurs de Go soit agréable, elle est également pénible dans le code séquentiel. Le manque d'introspection et de frappe autour des valeurs réelles elles-mêmes (qui est abordé dans une proposition différente) n'améliore pas réellement la résilience du programme, selon mon expérience.

Il y a quelques années, je me suis assez ennuyé à ce sujet, j'ai en fait écrit du sucre autour de la panique et de la récupération pour leur permettre de fonctionner (principalement) comme des exceptions non vérifiées : https://hackthology.com/exceptions-for-go-as-a-library. html . En fait, utiliser ces wrappers est une mauvaise idée à cause de l'endroit où se trouve la communauté. Cependant, je continue de croire que l'amélioration de l'ergonomie autour de la gestion des erreurs est une victoire.

Il semble fou que les gens plaident ardemment pour conserver une manière extrêmement verbeuse mais inutile de propager les conditions d'erreur. J'ai écrit et trouvé de vrais bogues dans mon code (et le code d'autres) où les gens ont foiré la condition if err != nil et créé des bogues. Il n'y a vraiment aucune raison pour que ces bogues soient écrits. La vérification statique peut aider mais pas éliminer ces bogues.

Je soutiens les propositions d'amélioration de l'ergonomie autour de la gestion des erreurs.

Le processus de proposition est pour, pour citer https://golang.org/s/proposal , "Proposer des modifications à emporter".
Ce numéro ne propose pas de changement, il s'agit donc vraiment d'une erreur de catégorie.
Si quelqu'un déposait une proposition intitulée "laisser sélectionner seul", nous la fermerions simplement comme n'étant pas une proposition.

Mais en réalité, ce problème n'est qu'une extension de la discussion d'autres problèmes comme le n° 32437, donc je le laisserai ouvert comme un bon endroit pour discuter de « ne rien faire ».

Je l'ai marqué en attente de proposition pour indiquer qu'il n'y a pas de décision spécifique ici, plutôt une méta-décision.

Côté : Il me semble étrange que ce numéro ait 1335 votes positifs alors que la proposition try (#32437) a _seulement_ 279 votes négatifs.

La proposition d'essai était verrouillée au moment où j'en ai pris connaissance, je ne pouvais donc pas la rejeter. Je suppose que c'est aussi la raison pour laquelle cette proposition a été introduite pour commencer.

Le 02/07/19, Robert Griesemer [email protected] a écrit :

@lootch Avez-vous un souci particulier avec l'erreur devant être la dernière
paramètre de retour pour que try fonctionne ? N'est-ce pas déjà de facto
convention?

Je le fais dans le sens où si cela devenait « de facto » gravé dans le marbre, alors
cette porte doit s'ouvrir et faire de "l'erreur" une option spéciale
type d'argument et acceptez cela, car pour chaque résultat correct, il
sont le plus souvent beaucoup de mauvais et ils peuvent aussi bien être
abordé d'une manière particulière.

Une fois que l'idée que la déclaration de « retour » doit être examinée
plus profondément (et la proposition d'essai semble suggérer que
direction), alors la condition d'erreur n'est plus "simplement une valeur, et
L'approche de Go commence à ressembler au meilleur château de cartes conçu pour
date, mais un château de cartes, néanmoins.

Je suis un utilisateur réticent de quelques-unes des astuces les plus intelligentes de Go (j'ai
mentionné ailleurs que x++ et x-- ne figurent pas - la plupart du temps, je glisse
parfois - dans mon code) donc "essayer" restera l'un de ceux-là pour moi,
mais je ne m'oppose pas par principe à son introduction, je pense simplement
que tant de choses ont été dites et que tous les inconvénients possibles ne sont peut-être pas
ont été révélées (les conséquences involontaires de ce que je vois devenir
une éventuelle décision politique). Mettre ça à l'épreuve peut être bien
ou mauvais.

Ce que je vois, c'est que la panique/récupération a mauvaise réputation et l'injustice de celle-ci
revient nous hanter. Encore une fois, je n'ai pas encore utilisé l'un ou l'autre et je
redoute le jour où je dois le faire parce que je ne suis pas le cookie le plus pointu du bocal
et je trouverai cela très difficile, mais si la panique/récupération avait été encouragée
pour les rares occasions où cela convient (aucun d'entre eux n'est traité par
Rob Pike dans sa merveilleuse présentation sur les erreurs qui ne font que revenir
valeurs, si je me souviens bien), ce serait beaucoup plus clair pour tout cela
Go gère déjà les erreurs aussi bien que l'on peut s'y attendre sans avoir besoin
explorer le marais des options possibles disponibles au-delà de son
limites.

Cela dit, il est tout aussi logique de mettre l'essai à l'épreuve, c'est, après
tout, une fonctionnalité optionnelle. Mais le seul effet secondaire est ce que cela
l'échange porte sur : quelles seront les conséquences de « de facto »
obliger les fonctions de gestion des erreurs à avoir un argument de type "error"
à la fin de leur liste de paramètres de retour ? Comment cela changera-t-il le
perception que « les erreurs ne sont que des valeurs » ? Comment s'articulera-t-il avec
le paradigme désormais beaucoup plus analogue « virgule-OK » ? Quoi d'autre cela
nouveau principe donner lieu à?

Surtout, je pense que la montagne faite de cette taupinière est
indiquant que Go a atteint un jalon qui change la vie. Presque
assurément, les hypothèses qui étaient vraies en 2013 pourraient bien ne plus
prise. Ce n'est pas une nouvelle pour qui que ce soit, mais cela suggère que Go2 pourrait
ne pas être aussi merveilleux d'être rétrocompatible avec Go1 comme cela a été
trop fermement proposé (à mon avis).

@lootch Merci pour votre réponse détaillée. Il est en effet très difficile d'étendre une langue existante d'une manière rétrocompatible, et je suis donc d'accord avec vous pour dire que si cette rétrocompatibilité n'était pas requise, on pourrait peut-être faire les choses différemment. Nous serons bientôt (une fois que les modules seront devenus monnaie courante) en mesure de faire des changements de langue qui ne nécessitent pas une compatibilité descendante stricte.

Comme vous le dites, try est un mécanisme facultatif, et - je pense que cela vaut la peine de le répéter - malgré tout le battage qui l'entoure, un mécanisme très simple à cela, facilement expliqué en utilisant la fonctionnalité Go existante - c'est juste que nous pouvons ' t écrivons nous-mêmes try tant que fonction dans Go (et les génériques n'aideraient pas non plus, d'ailleurs). Si nous le pouvions, je suis certain qu'il serait assez courant de voir des fonctions d'assistance telles que try pour la gestion des erreurs dans le code existant. Après tout, c'est exactement ce dont parle Rob Pike sur le fait que les erreurs sont des valeurs : c'est de la programmation avec des erreurs.

Il est toujours très surprenant pour moi qu'il y ait un tel tollé concernant l'ajout d'une fonction d'assistance assez mineure que tout le monde est libre d'ignorer, mais les gens envisagent sérieusement des changements de langage importants tels que on error qui suggèrent vraiment fortement un style spécifique de gestion des erreurs dans la langue.

Encore merci pour votre contribution. Tout cela est très intéressant.

Je ne sais pas si cette proposition est le bon endroit pour en discuter, mais étant donné qu'il y a déjà une discussion animée sur le mot-clé try dans ce fil, je vais juste le considérer comme un sujet pour l'instant :)

Je me demande si les gens de Google seraient prêts à implémenter le mot-clé try dans leur référentiel interne Golang, et par la suite à convertir les bases de code Google existantes pour utiliser ce mot-clé. En le gardant uniquement en interne, cela permettrait de l'inverser facilement (c'est-à-dire que la charge incombe à une seule entreprise plutôt qu'à l'ensemble de la communauté).

Cela permettrait de faire une petite étude de cas sur cette fonctionnalité dans une base de code réelle à haut sloc. Facebook a fait quelque chose de similaire avec les fonctionnalités PHP dans le passé, et de cette façon, ils ont pu affiner certaines fonctionnalités avant de les proposer à la communauté dans son ensemble.

Si vous deviez rédiger une étude de cas sur la façon dont le mot-clé try a été utilisé _en pratique_ et sa valeur ajoutée dans la vie réelle, cela pourrait fournir un cas convaincant. Si cela (hypothétiquement) ne fournissait aucune valeur réelle, ce serait également précieux à savoir. Parfois, quelque chose a l'air vraiment bien sur le papier mais ne fonctionne pas dans la pratique - ou vice versa.

@Freeaqingme Nous avons déjà produit un CL provisoire qui montre à quoi pourrait ressembler try dans la bibliothèque std : https://go-review.googlesource.com/c/go/+/182717 (il peut y avoir des faux positifs, mais s'il y en a, ils sont très rares). Nous envisageons peut-être de développer un outil permettant la conversion des bases de code dans les deux sens.

Nous vous encourageons également à utiliser tryhard dans votre base de code et à faire rapport.

Merci.

@griesemer, je ne me suis peut-être pas fait comprendre. Je suppose que Google utilise sa propre version de Go en interne. Ma suggestion serait d'appliquer le correctif que vous avez lié ci-dessus à la version interne de Google, puis de convertir (des parties de) la base de code Google - pas seulement stdlib, mais surtout les projets internes écrits en Go. Si les ingénieurs de Google l'utilisaient pendant quelques mois dans des situations réelles, cela donnerait un bon aperçu de la façon dont cela fonctionnerait dans la pratique.

Évidemment, je peux aussi l'appliquer moi-même et l'utiliser sur mes propres bases de code (et je pourrais aussi bien). Mais je ne suis qu'un seul développeur avec une base de code relativement petite, donc ce ne serait pas aussi représentatif si de nombreux employés de Google l'utilisaient.

Personnellement, je suis toujours sur la clôture à propos de cette fonctionnalité. D'une part, j'apprécie le fait que cela puisse me faire économiser quelques frappes à chaque fois. Mais parfois, je peux être paresseux (je suis humain après tout) et imbriquer autant d'instructions try que possible. Maintenant, je vais juste être un peu plus discipliné, si cette fonctionnalité était présente. Mais même si je l'étais, il y a toujours une chance que les bibliothèques externes abusent/abusent de ce mot-clé alors que ma base de code en souffre (en termes de débogage, d'extensibilité de ces bibliothèques).

@Freeaqingme Compris . Nous pourrions certainement exécuter try sur des dépôts internes. Je ne suis pas sûr que nous puissions convertir et reconvertir - il y a un coût important ici. De plus, nous ne pouvions rendre compte que de manière globale (statistiques) de cette expérience, car nous ne serions pas en mesure de rendre compte des détails internes. C'est que la communauté n'aurait aucun moyen facile de vérifier nos affirmations. Enfin, la base de code de Google peut ne pas être représentative.

Mais merci, point pris.

@griesemer Je

Je comprends que vous ne seriez pas en mesure de créer des rapports sur des projets Google individuels ou sur l'infrastructure interne. Cependant, quelques anecdotes pourraient être partagées, peut-être. Mais ce que je trouverais personnellement beaucoup plus précieux, ce sont des Googleurs qui racontent comment cela a fonctionné pour eux. Sans entrer dans les détails, des déclarations telles que "Je m'attendais à X mais lorsque j'ai rencontré des cas comme A et BI ont trouvé que Y" je trouverais très utile. Je ne trouverais aucun besoin de vérifier ce genre de rapports.

Enfin, la base de code de Google peut ne pas être représentative.

C'est peut-être, ce n'est peut-être pas. Mais il y a beaucoup de gens qui travaillent chez Google, donc je pense que la majeure partie de la base de code n'est pas basée sur la façon dont un seul individu a décidé de l'écrire, mais plutôt sur l'aboutissement des contributions de nombreux contributeurs différents (employés). À cet égard, je m'attends à ce que les choses soient aussi représentatives que possible. Il n'y a probablement rien de tel qu'une base de code 100 % représentative.

Gardez-le tel quel, jusqu'à ce que nous trouvions une meilleure solution, le try n'est pas celui que nous recherchions. Pas besoin de prendre des mesures de parti pris à ce sujet, prenez votre temps Go Authors. Je crois fermement, pour autant que je parle avec des gophers du monde entier tous les jours, que la majorité de la communauté Go n'accepte pas la proposition try guichet automatique.

L'introduction d'une nouvelle syntaxe signifie que tout le monde doit mettre à niveau sa version Go. J'utilise toujours Go 1.10 car mon workflow est basé sur le fait que je peux go get choses puis les utiliser à partir de la ligne de commande (mon GOPATH est dans mon PATH ) . J'ai récemment eu un problème en essayant de go get la bibliothèque de quelqu'un d'autre qui utilise des modules. J'ai reçu une erreur indiquant que .../v2 n'était pas disponible. Cela signifie qu'il y a déjà une division dans le code (pensez à Python 2 et 3). Pour moi, il y a un monde avant Go 1.11 et après Go 1.11. C'est très ennuyeux et l'introduction d'une nouvelle syntaxe pour quelque chose qui fonctionne ainsi que la gestion des erreurs dans Go n'est pas du tout un bon compromis. Il introduit plus de fragmentation.

Le 04/07/19, gonutz [email protected] a écrit :

L'introduction d'une nouvelle syntaxe signifie que tout le monde doit mettre à niveau son Go
version. J'utilise toujours Go 1.10 car mon flux de travail est basé sur le fait
que je peux go get choses, puis les utiliser à partir de la ligne de commande (mon
GOPATH est dans mon PATH ). J'ai récemment eu un problème en essayant de go get
la bibliothèque de quelqu'un d'autre qui utilise des modules. J'ai eu une erreur que .../v2 était
pas disponible. Cela signifie qu'il y a déjà une division dans le code (pensez
Python 2 et 3). Pour moi, il y a un monde avant Go 1.11 et après Go 1.11.
C'est très ennuyeux et introduit une nouvelle syntaxe pour quelque chose qui fonctionne
ainsi que la gestion des erreurs dans Go n'est pas du tout un bon compromis. Ce
introduit plus de fragmentation.

Si ça peut te consoler, je suis exactement dans la même position vis-à-vis
Allez les modules. Je n'ai pas trouvé le temps et l'opportunité de me familiariser
avec eux, donc je m'en tiens également à Go1.10. Peut-être que cela devrait être
un sondage qui en vaut la peine.

Lucio.

--
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub :
https://github.com/golang/go/issues/32825#issuecomment -508372318

--
Lucio De Ré
2 rue Piet Retief
Kestell (État libre de l'Est)
9860 Afrique du Sud

Tél. : +27 58 653 1433
Cellulaire : +27 83 251 5824
TÉLÉCOPIE : +27 58 653 1435

Je suis un nouveau développeur de golang (encore en train d'apprendre le go). Je pense que la gestion actuelle des erreurs est bonne car elle nous permet de gérer les erreurs facilement. En tant que développeur Android, je pense que try-catch est plus difficile à gérer notre erreur que if err != nil{ } en golang. Et je pense que la gestion des erreurs explicites est toujours meilleure que la gestion des erreurs implicites.

PS. Désolé pour ma langue.

leave it alone

Il n'est pas cassé....

J'adore le mème, @Daniel1984 :-)

Incidemment, la proposition try laisse if err != nil seul ; cela vous donne juste une option supplémentaire là où cela a du sens.

Je suis d'avis que try ne devrait pas être inclus. Sur l'inclusion de try :

Pro

  • Les programmeurs diminuent le nombre de frappes qu'ils effectuent.
  • Les programmeurs peuvent avoir un raccourci pour revenir de la fonction actuelle à la macro.
  • Ce n'est pas obligatoire.
  • Il serait couramment utilisé.
  • Il est clair où la magie se produit (contrairement à throws Java).
  • Les yeux ne sont plus vitreux en regardant la mer de chèques de nil .
  • Fonctionne mieux avec des implémentations simples.

Con

  • try ajoute une méthode en double pour une opération existante.
  • Pour que try revienne de la fonction actuelle est inattendu, AKA plus magique.
  • Il ajoute des incohérences à la vérification des erreurs.
  • Les programmeurs sans expérience de Go ne comprendront pas.
  • Ne modifie pas la gestion des erreurs.
  • Moins de clarté (incompatibilité entre le retour de la fonction et la valeur de l'expression).
  • Difficile de décrire ce qui se passe à try avec des mots.

@griesemer c'est exactement ce que je n'aime pas. Les choses devraient être simples, je ne voudrais pas ajouter de la complexité au langage juste pour avoir 2 façons d'atteindre la même chose. Il existe des modèles pour éviter cette verbosité if err != nil . https://www.ardanlabs.com/blog/2019/07/an-open-letter-to-the-go-team-about-try.html

La proposition Go2 #32437 ajoute une nouvelle syntaxe au langage pour rendre le passe-partout if err != nil { return ... } moins encombrant.

Il existe diverses propositions alternatives : #32804 et #32811 car l'original n'est pas universellement apprécié.

Pour jeter une autre alternative dans le mix : Pourquoi ne pas le garder tel quel ?

J'en suis venu à aimer la nature explicite de la construction if err != nil et en tant que telle, je ne comprends pas pourquoi nous avons besoin d'une nouvelle syntaxe pour cela. Est-ce vraiment si mauvais que ça ?

Très bien ça. Le code explicite est le code correct. Les horreurs que j'ai vues avec les gestionnaires d'exceptions sont suffisantes pour m'éloigner à jamais de cette terrible construction illisible.

Il semble y avoir un décalage énorme, les gens commençant à commenter uniquement
maintenant et on ne pouvait s'empêcher d'avoir l'impression qu'il est frais
nouvelles pour eux.

Cela doit aussi être pris en compte. La vigne n'est clairement pas aussi
rapide comme on peut le penser.

Lucio.

Et changez gofmt pour autoriser les déclarations if sur une seule ligne, ce simple hand-ball et erreur jusqu'au
fonction appelante. Cela enlèverait beaucoup de désordre.

À la place de:

erreur = maFonction()
si erreur != nil {
retour erreur
}

Permettre:

erreur = maFonction()
if err != nil { return err}

Incidemment, la proposition try laisse if err != nil seul ; cela vous donne juste une option supplémentaire là où cela a du sens.

Cette justification précise est comment Go devient encore un autre C++, C#, Scala, Kotlin, etc.

Edit - cela peut avoir été mal interprété. Je ne dis pas que try va transformer Go en un langage pléthorique. Je dis que la justification ici est un peu erronée

@deanveloper Vous avez clairement un exemple de comportement d'erreur difficile à comprendre avec "try":
https://github.com/golang/go/issues/32437#issuecomment -498932961

Même si c'est un peu répétitif, je suis d'accord avec OP.
De plus, imo toutes les alternatives proposées introduisent une complexité inutile.

Ce sera juste cool d'omettre les accolades lorsque vous n'avez qu'une seule ligne à l'intérieur de la portée

@devenir fou

Cela signifie qu'il y a déjà une division dans le code (pensez à Python 2 et 3). Pour moi, il y a un monde avant Go 1.11 et après Go 1.11.

Je suis un programmeur Python de longue date et le prétendu "split" que vous mentionnez concernant les modules Go n'est rien comparé au désastre de la migration de Python 2 vers Python 3.

C'est très ennuyeux et l'introduction d'une nouvelle syntaxe pour quelque chose qui fonctionne ainsi que la gestion des erreurs dans Go n'est pas du tout un bon compromis. Il introduit plus de fragmentation.

try est une fonction intégrée dans la proposition. Il est entièrement rétrocompatible. Si votre code utilise déjà try comme identifiant, alors votre identifiant masquera le try intégré.

@pongsatornw

Je pense que la gestion actuelle des erreurs est bonne car elle nous permet de gérer les erreurs facilement. En tant que développeur Android, je pense que try-catch est plus difficile à gérer notre erreur que si err != nil{ } dans golang. Et je pense que la gestion des erreurs explicites est toujours meilleure que la gestion des erreurs implicites.

Avez-vous lu la proposition en entier ? try est juste une fonction intégrée qui aide à factoriser la répétition de if err != nil { return ..., err } . La logique générale de gestion des erreurs dans Go reste la même. C'est toujours explicite, les erreurs sont toujours des valeurs et il n'y a pas de try-catch (aka exceptions).

@kroppt

  • try ajoute une méthode en double pour une opération existante.

try fait que factoriser du code répétitif. C'est la même chose avec append . Nous pouvons l'écrire nous-mêmes chaque fois que nous ajoutons des éléments à une tranche, mais il est plus facile d'appeler append .

  • Il ajoute des incohérences à la vérification des erreurs.

Vous pouvez manipuler une tranche "manuellement" en utilisant [...:...] ou vous pouvez utiliser append , selon ce que vous faites. Il n'y a pas d'incohérence. Ce ne sont que des outils différents pour différentes tâches. Idem pour les erreurs, avec un simple if err != nil { ... } , ou avec try , selon la tâche à accomplir.

  • Pour que try revienne de la fonction actuelle est inattendu, AKA plus magique.

C'est inattendu parce que c'est nouveau. On s'y habitue au fur et à mesure que l'on s'en sert. Et je ne pense pas que ce soit magique ; la spécification de try est très simple.

  • Les programmeurs sans expérience de Go ne comprendront pas.
  • Difficile de décrire ce qui se passe à try avec des mots.

Les programmeurs sans expérience de Go ne comprendront pas chan , defer , 'go , iota , panic , recover , <- , type assertions, and many other things either without reading the documentation. try` est facile comparé à la plupart des autres.

  • Ne modifie pas la gestion des erreurs.

C'est peut-être une bonne chose, selon les gaufres demandant de laisser if err != nil tranquille ;-)

@marcopeerboom

Le code explicite est le code correct. Les horreurs que j'ai vues avec les gestionnaires d'exceptions sont suffisantes pour m'éloigner à jamais de cette terrible construction illisible.

try n'a absolument rien à voir avec la gestion des exceptions. Avez-vous lu la proposition complète? Il n'y a rien de comparable à la gestion des exceptions comme en Java ou Python par exemple. try est explicite. Les erreurs doivent être mentionnées dans les signatures de fonction. Les erreurs doivent être traitées sur le site d'appel. Il n'y a pas de déroulement de pile. Etc.

@gale93

Ce sera juste cool d'omettre les accolades lorsque vous n'avez qu'une seule ligne à l'intérieur de la portée

Je pense que la plupart des gaufres avaient la même pensée, et j'ai lu des propositions similaires plusieurs fois dans le suivi des problèmes. Mais c'est un changement beaucoup plus important que try . Il ne serait pas raisonnable de le faire uniquement pour l'instruction if . Vous devrez donc changer cela partout où un bloc est accepté. Sans le { marquant le début du bloc, vous devez spécifier un moyen de délimiter la fin de l'expression conditionnelle. Vous devez mettre à jour la grammaire, l'analyseur, gofmt, etc. Cela changerait complètement la surface du langage.

@ngrilly
La modération et la simplicité du langage sont importantes.

Certains des arguments que vous avez utilisés pourraient justifier de nombreux changements dans les spécifications. Il n'y a pas que des points positifs ici.

J'évalue la décision en fonction de si cela aiderait ou blesserait, sans nécessairement adopter pleinement une certaine philosophie. Vous avez raison de dire que certaines choses dans la spécification violent certains principes sur lesquels go été fondé, mais tout est question de modération. Ce changement n'a pas un impact assez positif sur moi pour tolérer les négatifs.

Salut @kroppt ,

garder le langage simple est important

Je suis d'accord et je pense que nous nous battons tous pour cela.

J'évalue la décision en fonction de si cela aiderait ou blesserait, sans nécessairement adopter pleinement une certaine philosophie.

Je pense que nous évaluons tous try fonction de ses avantages et de ses coûts. La discussion porte sur la définition et la recherche d'un consensus factuel sur ces avantages et ces coûts, ce que j'ai essayé de faire dans mon commentaire précédent.

Vous avez raison de dire que certaines choses dans la spécification violent certains principes sur lesquels go a été fondé

Au cours des dernières années, j'ai lu presque tout ce que l'équipe de Go a publié sur Go et sa conception, et je ne comprends pas ce que vous voulez dire. Je ne pense pas que la proposition try viole les principes fondamentaux du Go.

@ngrilly
https://talks.golang.org/2012/splash.article décrit certains des concepts derrière ce qui rend go différent - clarté et simplicité, entre autres. Je pense que c'est le conflit que certains d'entre nous voient avec ce nouveau changement. C'est plus simple, mais c'est moins clair. Il me semble que le gain de simplicité est moindre que la perte de clarté. Peut-être que je me trompe et que je suis juste prudent.

@kroppt J'ai lu cet article des dizaines de fois ;-) Je ne suis pas d'accord avec l'idée qu'essayer brouille le code. try est juste une fonction intégrée utilisée pour factoriser du code répétitif. Nous le faisons tout le temps en tant que programmeurs. Lorsque nous identifions un modèle répétitif, nous le factorisons dans une nouvelle fonction. Si nous ne le faisions pas, nous aurions une longue fonction main() avec tout notre code en ligne.

@ngrilly
Ce que vous décrivez est dans ma section "pro":

  • Les programmeurs diminuent le nombre de frappes qu'ils effectuent.
  • Les programmeurs peuvent avoir un raccourci pour revenir de la fonction actuelle à la macro.
  • Les yeux ne sont plus vitreux en regardant la mer de chèques nil .

Encore une fois, je ne vois pas l'intérêt d'évoquer l'application universelle d'un principe alors qu'on ne l'applique pas universellement ici.

Je ne suis pas d'accord avec l'idée qu'essayer brouille le code

Le but du changement est d'obscurcir/masquer/simplifier/représenter le code - sinon nous verrions le bloc de contrôle d'erreur d'origine. La question est de savoir si cela rend moins clair le sens.

Je pense que go atteint à l'origine un bon équilibre de simplicité, au point de contribuer à la clarté plutôt que de l'enlever. Je ne peux pas expliquer comment ils l'ont fait, mais try à mon avis ne le fait pas.

Je ne pense pas que nous devrions considérer la verbosité comme un problème. Le code doit être lu et compris par les humains — dont le temps est plus cher que celui des ordinateurs — et la _compréhension_ a tendance à être la partie la plus difficile et la plus longue.

Je trouve que la structure d'indentation de la gestion des erreurs go m'aide à suivre ce qui se passe. Chaque contrôle d'erreur est explicite. La plupart des erreurs non gérées sont également explicites. Cela rend le code rapide à comprendre, pour moi.

Je pense également que même si les vérifications de if err != nil peuvent sembler fastidieuses, je n'ai pas besoin de les _type_. J'ai juste mon éditeur qui le fait.

@kroppt

Le but du changement est d'obscurcir/masquer/simplifier/représenter le code - sinon nous verrions le bloc de contrôle d'erreur d'origine.

Mais vous pouvez utiliser cet argument pour n'importe quel appel de fonction ! Si j'appelle strings.HasPrefix("foobar", "foo") , cela brouille-t-il le code ? Préféreriez-vous écrire et lire l := len("foo"); len("foobar") >= l && s[0:l] == "foo" ?

@rossmcf

Chaque contrôle d'erreur est explicite.

try vérifie toujours explicitement l'erreur. C'est la raison d'être d'essayer.

Je pense également que si err != nil, les vérifications peuvent sembler fastidieuses, je n'ai pas besoin de les taper.

Ce n'est pas fastidieux à taper. C'est fastidieux à lire, quand on a les mêmes 3 lignes partout. C'est une répétition, et nous, en tant que programmeurs, avons généralement tendance à en tenir compte. Peut-être que try a d'autres inconvénients, mais pas celui-ci je pense.

try vérifie toujours explicitement l'erreur
La différence entre l'abstraction try et strings.HasPrefix est que try renvoie implicitement.
Lors de la lecture du code go, je sais que le flux d'exécution reste dans ma fonction jusqu'à ce que je :

  • lire la parenthèse fermante d'une fonction sans types de retour
  • lire les mots-clés return , panic
  • lire syscall.Exit(code)
    Avec try , je ne pouvais pas lire le code de la même manière. Parcourir visuellement les lignes et voir aucune instruction "retour" ne signifierait plus "soit ces lignes s'exécutent toutes, soit une seule bloque, soit le programme se termine".

@ngrilly Vous pouvez répondre à plus d'une personne dans un message Pour info, 10 réponses en quelques heures dont 5 consécutives à un moment donné rendent difficile le suivi de la discussion. Après avoir lu vos messages, à part quelques erreurs, je n'ai vu aucun nouvel argument concret décrivant les avantages d'essayer. J'ai vu un seul avantage : empêcher la saisie de if err != nil . Cela a le prix d'introduire de nouvelles façons de faire fuir les ressources , la possibilité d'écrire du code moins concis et, pire encore, de permettre l'imbrication de try .

Je pense que les spécifications et les arguments formés par les partisans sont trompeurs, ils ne fournissent actuellement que les meilleurs exemples sans montrer les pires exemples. Il n'évalue ni ne mentionne les inconvénients négatifs ci-dessus ou les facteurs atténuants potentiels. Il ne justifie pas pourquoi il ne limite pas la mise en œuvre de try à l'usage proposé et démontré. L'outil tryhard est utilisé pour afficher un code plus compact qui offre subjectivement une meilleure esthétique à certaines personnes, sans un outil trytoohard qui montre toutes les capacités de ce qu'il peut faire, par exemple essayer profondément imbriqué déclarations. Enfin, le nom lui-même est associé de manière omniprésente dans le monde de la programmation à des exceptions, lui permet d'être imbriqué et lié aux erreurs et de le placer de manière adjacente en tant que composant intégré à une récupération et à une panique sans rapport, laissant les choses simplement hors de propos. Je crois et j'ai confiance en la capacité des auteurs de Go à proposer quelque chose de mieux.

Il y a trop de coûts ici pour justifier avec une seule valeur ajoutée que je vois régurgiter dans les réponses des partisans : "Je n'ai plus à taper if err != nil " - la chose qui a été ardemment défendue et enracinée avec les erreurs sont valeurs par l'ensemble de la communauté Go. Nous arrivons à une décennie de code écrit à l'aide de if err != nil - que certaines des avancées technologiques les plus notables (docker, k8s, ...) utilisent toutes en même temps avec un grand succès.

En conclusion, if err != nil n'est pas un fardeau à cacher avec une fonction intégrée, mais quelque chose que nous devrions tous reconnaître comme un ingrédient essentiel au succès des langues. Même si nous acceptons collectivement qu'il s'agit d'un fardeau, la barre pour le supprimer devrait être haute et sans compromis. À l'heure actuelle, trop d'aspects de l'essai sont un compromis.

J'ai des opinions sur la méthode la plus facile, mais ce sont des opinions. L'essai donné est simple et les vérifications explicites actuelles sont simples. Grand les deux manières sont simples. Le problème pour moi est que cela augmente la charge cognitive du lecteur et de l'auteur d'un code donné. Maintenant, les deux doivent interpréter plusieurs façons de faire les choses. Et l'écrivain doit choisir la manière de faire les choses et/ou risquer de faire les choses différemment du reste du package ou du projet sur lequel ils travaillent. Si try remplaçait la vérification explicite, cela augmenterait encore la charge cognitive en raison des retours implicites comme autre chose à analyser.

_Laissant tout cela de côté un instant et considérant que nous avons maintenant deux manières tout aussi simples de gérer les erreurs, nous avons toujours un problème :_ Simple n'est plus facile . Et cela ouvre la porte à toutes les choses que Go a été conçues pour éviter.

La barre pour ajouter quelque chose comme ça devrait être beaucoup plus haute, devrait être expérimentale pendant longtemps pour prouver que c'est mieux avec les données du terrain.

@cstockton

Vous pouvez répondre à plus d'une personne dans un message Pour info, 10 réponses en quelques heures avec 5 d'affilée à un moment donné, il est difficile de suivre la discussion.

Ian a suggéré il y a 7 jours de déplacer cette discussion vers golang-nuts exactement pour cette raison (pas de moyen de répondre à un commentaire spécifique, pas de discussion), suggestion qui a été rejetée pour s'assurer que la discussion serait "officielle". Nous avons ce que nous avons demandé.

@therealplato

La différence entre l'abstraction try et strings.HasPrefix est que try renvoie implicitement.

C'est vrai. Lors de la lecture d'une fonction et de la recherche de points de "sortie", nous devrons rechercher return, panic, log.Panic, os.Exit, log.Fatal, etc... et essayer. Est-ce un tel problème? Le nombre de points de sortie dans une fonction restera le même et sera toujours explicitement marqué, avec ou sans try.

lire les mots-clés retour, panique

la panique n'est pas un mot-clé ; c'est une fonction intégrée. Si nous allons critiquer la proposition de l'équipe Go, qui est probablement plus compétente que n'importe lequel d'entre nous en matière de conception de langage, alors au moins nous devrions leur faire la faveur de bien définir les choses. ??

Lors de la lecture d'une fonction et de la recherche de points de "sortie", nous devrons rechercher return, panic, log.Panic, os.Exit, log.Fatal, etc... et essayer. Est-ce un tel problème?

C'est un problème, car try peut apparaître littéralement partout où une expression peut apparaître. Chaque point de sortie dans Go peut _uniquement_ apparaître comme une seule instruction, try est la seule chose qui peut apparaître comme une expression.

@ngrilly

Ian a suggéré il y a 7 jours de déplacer cette discussion vers golang-nuts exactement pour cette raison (pas de moyen de répondre à un commentaire spécifique, pas de discussion), suggestion qui a été rejetée pour s'assurer que la discussion serait "officielle". Nous avons ce que nous avons demandé.

commencer le message

@ utilisateur1
réponse 1

@ utilisateur2
réponse 2

message de fin

C'est ce que l'on voulait dire.

@cstockton

J'ai vu un seul avantage : empêcher la saisie de if err != nil.

try empêche la saisie et la lecture répétitives de if err != nil { return ..., err } (formaté sur 3 lignes), pas seulement if err != nil .

Cela a le prix d'introduire de nouvelles façons de fuir les ressources, la possibilité d'écrire du code moins concis et, pire encore, de permettre l'imbrication de try.

Le risque de fuite de ressources que vous avez mentionné peut être évité avec vet et lint .

À propos du "code moins concis", le but de try est d'écrire du code plus concis, donc je ne comprends pas votre argument.

Le risque d'imbrication excessive d'appels de fonction n'est pas spécifique à try . Tous les appels de fonction peuvent être trop imbriqués. Les revues de code et le linting vous aideront, comme toujours.

Je pense que les spécifications et les arguments formés par les partisans sont trompeurs, ils ne fournissent actuellement que les meilleurs exemples sans montrer les pires exemples.

Peut-être que ce sentiment est réciproque. Nous sommes tous de charmants gaufres ; ne tombons pas dans le jugement de valeur ;-)

Je vois régurgité dans les réponses des partisans : "Je n'ai plus à taper si err != nil"

Encore une fois, je n'ai plus besoin de taper l := len("foo"); len("foobar") >= l && s[0:l] == "foo" .
Je peux utiliser strings.HasPrefix("foobar", "foo") place.
En quoi est-ce si différent avec try ?
J'ai lu plus tôt que vous accepteriez un try "restreint" qui serait nommé check et interdirait l'imbrication.

Nous arrivons à une décennie de code écrit en utilisant if err != nil - que certaines des avancées technologiques les plus notables (docker, k8s, ...) utilisent toutes en même temps avec un grand succès.

Nous avons également beaucoup de bons codes écrits en C, C++, Java, etc. Avec ce raisonnement, nous n'aurions pas Go.

En lisant les discussions sur la gestion des erreurs dans Go, je n'avais pas l'impression que tout le monde était sur la même longueur d'onde concernant la proposition try , alors j'ai décidé d'écrire un article de blog qui montre comment try peut être utilisé : https://faiface.github.io/post/how-to-use-try/

Discussion associée sur Reddit : https://www.reddit.com/r/golang/comments/c9eo3g/how_to_use_try_faiface_blog/

Je sais que ce problème est contre try , mais j'espère que mon post pourra apporter de nouvelles perspectives :)

Go est coincé et se débat entre magique , ou logique pour l'idée de simple.

Avantages:

  • Réduire le passe-partout
  • Simple
  • Modèle existant parmi d'autres langues
  • Optionnel

Les inconvénients:

  • Courbe d'apprentissage
  • Magie sous-jacente
  • De nouveaux types de bugs
  • Go est opiniâtre et pragmatique avec if != nil mais vous pouvez utiliser try

Je me sens comme cette communauté esp. ici diffère contre les personnes votées dans Go Survey [1] .
Les électeurs pourraient ne pas choisir cela comme une préoccupation principale plutôt que de partir pour des considérations futures.
Mais il était considéré comme ayant un impact en raison de son emplacement.

IMO, l'ajout de fonctionnalités de langage est ancien et la méthode de programmation moderne ajoute plus de fonctionnalités sur les éditeurs, c'est-à-dire Emmet ou des extraits de langage, le pliage et la coloration du code, la refactorisation et le formatage, le débogage et les tests, suggérant une solution à une erreur et citant à partir de godoc ou de débordement de pile , l'interface utilisateur au-dessus du code source et laissez le code source détaillé
code plier if err != nil dans un try

@ngrilly

try empêche la saisie et la lecture répétitives de if err != nil { return ..., err } (formaté sur 3 lignes), pas seulement de if err != nil.

Croyez-vous que je dis "ça vous empêche de taper si err != nil" signifiait que j'avais complètement oublié que nous lisons aussi le code que nous tapons ?

Le risque de fuite de ressources que vous avez mentionné peut être évité avec un vétérinaire et des peluches.

J'ai lié une discussion sur les raisons pour lesquelles je pense que le vétérinaire et les peluches ne sont pas une option raisonnable ici.

À propos du "code moins concis", le but de l'essai est d'écrire du code plus concis, donc je ne comprends pas votre argument.

Oui, si vous aviez lu le lien que "la capacité d' écrire du code moins concis " vous indiquait réellement, vous auriez peut-être compris mon argument. _Remarque, prendre un peu de temps pour comprendre les arguments des personnes avec lesquelles vous participez à la discussion est la première étape pour présenter des informations susceptibles de les amener à céder à votre point de vue._

Le risque d'imbrication excessive d'appels de fonction n'est pas spécifique à essayer. Tous les appels de fonction peuvent être trop imbriqués. Les revues de code et le linting vous aideront, comme toujours.

Intéressant, décomposons cela :

1) Le risque d'imbrication excessive d'appels de fonction n'est pas spécifique à essayer.

Oui, tout le monde ici comprend le fonctionnement des fonctions.

2) Tous les appels de fonction peuvent être trop imbriqués.

Oui, tout le monde ici comprend le fonctionnement des fonctions.

3) Les revues de code et le linting vous aideront, comme toujours.

Oui, vous avez déjà fait l'argument lint et "l'argument des revues de code" est un autre contrôle de langue extérieur qui a été fait dans les messages que je vous ai liés.

Peut-être que ce sentiment est réciproque. Nous sommes tous de charmants gaufres ; ne tombons pas dans le jugement de valeur ;-)

Je ne comprends pas? La proposition n'a pas d'exemples de la pleine capacité fournie par la mise en œuvre, seulement l'utilisation prévue. L'outil tryhard utilisé pour aider à mesurer les effets de la proposition utilise try dans la forme la plus limitée et la plus raisonnable, c'est un fait simple.

Encore une fois, je n'ai plus besoin de taper l := len("foo"); len("foobar") >= l && s[0:l] == "foo".
Je peux utiliser strings.HasPrefix("foobar", "foo") à la place.
En quoi est-ce si différent avec try ?

Je fais de mon mieux pour extraire une position de chaque point de vue opposé, sinon je ne peux pas former d'argument pour la démanteler. Je ne vois vraiment pas ça ici, je suis désolé. Je vais interpréter cela de la seule façon dont je peux lui donner un sens : littéralement.

En quoi est-ce ( strings.HasPrefix ) si différent avec try ?

strings.HasPrefix

func HasPrefix

func HasPrefix(s, chaîne de préfixe) bool

HasPrefix teste si la chaîne s commence par un préfixe.

essayer

func try est une nouvelle fonction intégrée appelée try with signature (pseudo-code)

func try(expr) (T1, T2, … Tn)

où expr représente une expression d'argument entrant (généralement un appel de fonction) produisant n+1 valeurs de résultat de types T1, T2, ... Tn, et une erreur pour la dernière valeur. Si expr évalue à une valeur unique (n est 0), cette valeur doit être de type error et try ne renvoie pas de résultat. L'appel de try avec une expression qui ne produit pas de dernière valeur de type error entraîne une erreur de compilation.

Le try intégré ne peut être utilisé qu'à l'intérieur d'une fonction avec au moins un paramètre de résultat où le dernier résultat est de type error. L'appel de try dans un contexte différent entraîne une erreur de compilation.

Invoquer try avec un appel de fonction f() comme dans (pseudo-code)

x1, x2, … xn = try(f())

turns into the following (in-lined) code:

t1, … tn, te := f()  // t1, … tn, te are local (invisible) temporaries
if te != nil {
        err = te     // assign te to the error result parameter
        return       // return from enclosing function
}
x1, … xn = t1, … tn  // assignment only if there was no error

En d'autres termes, si la dernière valeur produite par "expr", de type error, est nil, try renvoie simplement les n premières valeurs, avec l'erreur nil finale supprimée. Si la dernière valeur produite par "expr" n'est pas nulle, la variable de résultat d'erreur de la fonction englobante (appelée err dans le pseudo-code ci-dessus, mais elle peut avoir un autre nom ou être sans nom) est définie sur cette valeur d'erreur non nulle et la fonction englobante retourne. Si la fonction englobante déclare d'autres paramètres de résultat nommés, ces paramètres de résultat conservent la valeur qu'ils ont. Si la fonction déclare d'autres paramètres de résultat sans nom, ils assument leurs valeurs zéro correspondantes (ce qui revient au même que de conserver la valeur qu'ils ont déjà).

Si try est utilisé dans une affectation multiple comme dans cette illustration et qu'une erreur non nulle est détectée, l'affectation (aux variables définies par l'utilisateur) n'est pas exécutée et aucune des variables sur le côté gauche du l'affectation est modifiée. C'est-à-dire que try se comporte comme un appel de fonction : ses résultats ne sont disponibles que si try retourne au site d'appel réel (par opposition au retour de la fonction englobante). En conséquence, si les variables du côté gauche sont nommées paramètres de résultat, l'utilisation de try conduira à un résultat différent du code typique trouvé aujourd'hui. Par exemple, si a, b et err sont tous des paramètres de résultat nommés de la fonction englobante, ce code

a, b, err = f()
if err != nil {
        return
}

définira toujours a, b et err, indépendamment du fait que f() ait renvoyé une erreur ou non. En revanche

a, b = try(f())

laissera a et b inchangés en cas d'erreur. Bien qu'il s'agisse d'une différence subtile, nous pensons que de tels cas sont rares. Si le comportement actuel est attendu, conservez l'instruction if.

Ils sont différents en ce que le texte entier trouvé dans la description de try n'est pas présent dans strings.HasPrefix. Une meilleure question serait de savoir comment sont les similaires, à laquelle je répondrais qu'ils partagent tous les deux certains aspects des appels et rien d'autre.

J'ai lu plus tôt que vous accepteriez un essai "restreint" qui serait nommé check et interdirait l'imbrication.

Content que vous ayez lu mon argument central contre try : la mise en œuvre n'est pas assez restrictive. Je pense que soit la mise en œuvre doit correspondre à tous les exemples d'utilisation des propositions qui sont concis et faciles à lire.

_Ou_ la proposition doit contenir des exemples qui correspondent à l'implémentation afin que toutes les personnes l'envisageant puissent être exposées à ce qui apparaîtra inévitablement dans le code Go. Avec tous les cas difficiles auxquels nous pouvons être confrontés lors du dépannage de logiciels moins qu'idéalement écrits, qui se produisent dans n'importe quelle langue / environnement. Il devrait répondre à des questions telles que à quoi ressembleront les traces de pile avec plusieurs niveaux d'imbrication, les emplacements des erreurs sont-ils facilement reconnaissables ? Qu'en est-il des valeurs de méthode, des littéraux de fonction anonymes ? Quel type de trace de pile le ci-dessous produit-il si la ligne contenant les appels à fn() échoue ?

fn := func(n int) (int, error) { ... }
return try(func() (int, error) { 
    mu.Lock()
    defer mu.Unlock()
    return try(try(fn(111111)) + try(fn(101010)) + try(func() (int, error) {
       // yea...
    })(2))
}(try(fn(1)))

Je suis bien conscient qu'il y aura beaucoup de code raisonnable écrit, mais nous fournissons maintenant un outil qui n'a jamais existé auparavant : la possibilité d'écrire potentiellement du code sans flux de contrôle clair. Je veux donc justifier pourquoi nous l'autorisons même en premier lieu, je ne veux jamais perdre mon temps à déboguer ce genre de code. Parce que je sais que je le ferai, l'expérience m'a appris que quelqu'un le fera si vous le lui permettez. Ce quelqu'un est souvent un moi mal informé.

Go fournit le moins de moyens possibles aux autres développeurs et à moi-même de nous faire perdre du temps en nous limitant à utiliser les mêmes constructions banales. Je ne veux pas perdre cela sans un avantage écrasant. Je ne pense pas que « parce que try est implémenté en tant que fonction » soit un avantage considérable. Pouvez-vous fournir une raison?

Ne perdez pas de temps sur ce problème de gestion des erreurs, donnez-nous des génériques et nous créerons quelque chose comme le résultat de Rust.

Go est coincé et se débat entre magique , ou logique pour l'idée de simple.

Avantages:

  • Réduire le passe-partout
  • Simple
  • Modèle existant parmi d'autres langues
  • Optionnel

Les inconvénients:

  • Courbe d'apprentissage
  • Magie sous-jacente
  • De nouveaux types de bugs
  • Go est opiniâtre et pragmatique avec if != nil mais vous pouvez utiliser try

Je me sens comme cette communauté esp. ici diffère contre les personnes qui ont voté dans Go Survey [1] .
Les électeurs pourraient ne pas choisir cela comme une préoccupation principale plutôt que de partir pour des considérations futures.
Mais il était considéré comme ayant un impact en raison de son emplacement.

IMO, l'ajout de fonctionnalités de langage est ancien et la méthode de programmation moderne ajoute plus de fonctionnalités sur les éditeurs, c'est-à-dire Emmet ou des extraits de langage, le pliage et la coloration du code, la refactorisation et le formatage, le débogage et les tests, suggérant une solution à une erreur et citant à partir de godoc ou de débordement de pile , l'interface utilisateur au-dessus du code source et laissez le code source détaillé
code plier if err != nil dans un try

J'ai voté pour une gestion des erreurs plus stricte sans possibilité d'oublier de traiter une erreur. Pas pour essayer.

Nous aurons besoin de bien plus que des génériques pour refaire quoi que ce soit à distance comme le type Result Rust. Même _si_ le type Result pouvait être fait uniquement avec des génériques, les programmeurs débutants auraient alors besoin de connaître les génériques avant même de pouvoir gérer correctement une erreur, ou de renvoyer une erreur à partir d'une fonction "de la manière Result "

@deanveloper , mon propos est le suivant : j'ai beaucoup plus à gagner des génériques que du "changement de syntaxe" et je pense que c'est également vrai pour la communauté.

@txgruppi Je peux convenir que les génériques devraient avoir une priorité plus élevée. J'essayais juste de dire que je ne pense pas que les génériques soient un bon substitut à la gestion des erreurs.

@deanveloper , à mon avis, ce problème de gestion des erreurs n'est que cosmétique, les gens passent du temps à discuter de quelque chose qui est stable et fonctionne bien simplement parce que vous devez taper quelques codes supplémentaires. Apprenez simplement à écrire un meilleur code et à résoudre ce problème avec des modifications de conception.
Avant que quelqu'un ne dise que les génériques sont tout aussi simples à corriger avec un meilleur code : erreurs de compilation...

peut-il être résolu avec un extrait ou une macro de clavier ? alors ce n'est pas un problème.

@txgruppi

Apprenez simplement à écrire un meilleur code et à résoudre ce problème avec des modifications de conception.

70% de tout le code de gestion des erreurs dans la bibliothèque standard est actuellement adapté pour try comme Robert Griesemer l'a découvert en utilisant son outil tryhard . D'autres seraient éligibles avec des modifications du code, telles que l'utilisation de la fonction fmt.HandleErrorf (pas encore existante). J'espère que vous ne voulez pas appeler la bibliothèque standard un mauvais code.

peut-il être résolu avec un extrait ou une macro de clavier ? alors ce n'est pas un problème.

Il s'agit aussi de lire le code. C'est pourquoi nous n'aimons pas thing.Thing thing = new thing.Thing(thing.THING);

@faiface , est-ce que 'if err != nil' est sur la voie du développement de logiciels de qualité ? Je ne pense pas.
Le manque de génériques est-il un obstacle au développement de logiciels de qualité ? Oui c'est le cas.

La façon dont je le vois est la suivante : je n'ai pas assez de connaissances pour implémenter des génériques, j'ai donc besoin de quelqu'un pour l'implémenter, mais cette gestion des erreurs n'est qu'une perte de temps pour les esprits qui peuvent faire des génériques une réalité. Je ne suis pas contre cette gestion des erreurs car c'est une mauvaise chose, je suis contre car il y a des choses plus importantes à résoudre.

@faiface la bibliothèque standard n'est pas une bonne représentation du vrai code Go. C'est parce qu'il est beaucoup plus probable que la bibliothèque standard passe simplement les erreurs sans ajouter de contexte, par exemple io/ioutil n'a jamais vraiment besoin de décorer les erreurs, elle peut simplement passer l'erreur qui s'est produite dans io . Robert Griesemer a également admis que la stdlib n'est pas exactement le meilleur représentant du code Go réel, mais je suis actuellement sur mobile et je ne veux pas chercher le commentaire. Je suis à peu près sûr que c'était relativement proche de son post d'essai d'origine.

@deanveloper @faiface Lorsque vous affrontez le Go Corpus :

--- stats ---
 401679 (100.0% of  401679) func declarations
  97496 ( 24.3% of  401679) func declarations returning an error
 991348 (100.0% of  991348) statements
 217490 ( 21.9% of  991348) if statements
  88891 ( 40.9% of  217490) if <err> != nil statements
    485 (  0.5% of   88891) <err> name is different from "err" (-l flag lists details)
  59500 ( 66.9% of   88891) return ..., <err> blocks in if <err> != nil statements
  29391 ( 33.1% of   88891) complex error handler in if <err> != nil statements; cannot use try (-l flag lists details)
    596 (  0.7% of   88891) non-empty else blocks in if <err> != nil statements; cannot use try (-l flag lists details)
  52810 ( 59.4% of   88891) try candidates (-l flag lists details)

Ainsi, dans le code réel, 40 % des instructions if sont écrites pour la vérification des erreurs, et try peut éliminer 59 % d'entre elles.

Je suis d'accord. Je suis d'accord avec if err != nil. C'est simple et propre pour les fonctions qui renvoient des valeurs d'erreur uniques. J'aime également le package d'erreurs et ses fonctions de cause / wrap lorsque le contexte de l'erreur est important. L'utilisation d'erreurs personnalisées avec une propriété de code (pour autant que je sache) vous oblige soit à faire une assertion de type, soit à utiliser quelque chose à la place de l'interface d'erreur standard. Quoi qu'il en soit, je ne me suis jamais retrouvé à lire ou à écrire du code Go et à ressentir une sorte d'agacement quant au fonctionnement actuel de la gestion des erreurs. Les désagréments que j'ai rencontrés sont des cas où plusieurs erreurs peuvent se produire à la suite du traitement d'une collection d'éléments. Cependant, il s'agit d'un problème de conception et non d'un problème de langue.

Notez que lorsque je dis "quand le contexte de l'erreur compte", je fais référence à une situation dans laquelle peut-être un problème de réseau s'est produit et je veux donc réessayer, ou peut-être que les résultats d'un appel de type "rechercher" n'ont renvoyé aucun résultat car il y avait en fait aucun, ou mon doigt nerveux a ajouté un « s » aléatoire à ma requête SQL, ce qui la fait exploser (cela m'arrive souvent … je devrais probablement faire vérifier les lésions nerveuses).

Le 5/7/19, Nicolas Grilly [email protected] a écrit :

@kroppt J'ai lu des dizaines de fois ;-) Je ne suis pas d'accord avec l'idée qu'essayer est
brouiller le code. try est juste une fonction intégrée utilisée pour factoriser certains
code répétitif. Nous le faisons tout le temps en tant que programmeurs. Lorsque nous identifions un
motif répétitif, nous le factorisons dans une nouvelle fonction. Si nous ne l'avons pas fait, nous
aurait une longue fonction main() avec tout notre code en ligne.

Je suis tenté de te traiter de malhonnête, mais je respecte celui de Ian Lance Taylor
efforts surhumains pour garder la discussion polie et je ne peux vraiment pas
voyez ce que quelqu'un gagnerait à mentir intentionnellement sur ce forum.

Cela dit, « Lorsque nous identifions un modèle répétitif, nous l'intégrons
une nouvelle fonction." Bien sûr, mais pas en fournissant une construction conflictuelle
qui, à ce stade avancé du développement de Go, s'accompagne de deux
fonctionnalités cachées : la première est de traiter les fonctions dont le « retour
la liste d'arguments se termine par une error " comme spécial (ou tout le reste
en tant qu'erreur sémantique) et la seconde fournit un flux de contrôle caché
détour analogue mais pas tout à fait identique à un "retour"
déclaration.

Peu importe les cerceaux enflammés que l'utilisation de "différer" introduit pour traiter
avec des utilisations plus obscures de "essayer - la pseudo fonction". Quelqu'un
a dit ailleurs, à peu près "Je ne veux pas tomber sur try dans le code
Je lis". Je ressens la même chose et cela ne doit pas être balayé sous le
tapis.

J'ai déclaré que c'est l'aspect "retour" du "retour d'erreur" qui
doit être traité, et la proposition "en erreur" se rapproche le plus de
ce principe, mais contourne aussi quelque peu les règles. Le mien aussi
suggestion "échouer" (elle déplace le dernier argument pour être le premier, c'est
me rend malheureux).

Plus profondément, quel langage à l'exception possible du SNOBOL,
que je connais a franchi le pas que Rob Pike a décrit comme
"les erreurs sont des valeurs" dans la mesure où Go a, mais quelque chose s'est perdu
dans le processus : une "condition" d'erreur n'est "pas" une valeur. À succès
l'achèvement d'une fonction est un cas particulier et chacun est donc possible
échec.

Chacun (et cela s'applique à la réussite, dont il peut y avoir
plus d'un aussi) doit être traité selon son mérite, mais nous
insister pour que la fonction invoquée nous donne son avis sur
la qualité de l'achèvement sous une forme abrégée, quelque chose qui a
été fait pour toujours et Rob s'est avéré être une idée fausse.

Pour illustrer ce point, considérons les valeurs de retour d'un lecteur :
io.EOF est un cas particulier qui est parfois un succès et parfois un
échec, mais selon les normes Go est fièrement une erreur ("io.Err != nil").
Allons-nous avoir un moyen d'abréger cela aussi? Presque certainement
non, car nous sommes assez habitués à « pardonner » son « tort ».

J'ai longtemps attendu qu'une sortie de boucle transmette un "statut" similaire ou
code de condition (une recherche peut se terminer par une valeur trouvée ou un échec, comment
dites-vous lequel, si vous souhaitez faire des choses différentes ? Vous ajoutez un test,
où les connaissances sont déjà en place - même problème, différent
le contexte).

Ce sont de réels enrichissements par rapport aux langages traditionnels : chaudière réductrice
plaque est absurde, par comparaison.

Et, la comparaison avec l'opérateur ternaire est également valable : si ? :
n'est pas autorisé "de peur qu'il ne soit abusé", alors essayer ne doit pas être autorisé,
soit, au moins pour ces motifs.

Franchement, essayez est une alouette. Cela rend Go plus attrayant pour le mal
public. A côté du danger de faire ça - qui veut les mauvaises personnes
rejoindre notre communauté ? - il y a la question du précédent et la question
de conséquences imprévues. Je dirais "supprimer" et acceptons cela
nous ne sommes pas encore arrivés à un point où la rétrocompatibilité peut
être bafoué, la gestion des erreurs doit donc rester verbeuse.

Nous avons la panique/récupération et il doit être promu de troisième ordre
citoyen à l'aide volontaire qu'il peut être, avec toutes les explications
qui rendent les débutants (et moi, j'avoue en avoir peur) plus
confiance en l'utilisant.

La construction "différer" dans la gestion des erreurs (que j'ai adoptée -
sans s'en rendre compte ailleurs - systématiquement dans la finalisation
Transactions SQL : tx.Rollback/tx.Commit) a été une révélation pour moi.
Il peut y avoir plus de principes qui peuvent être appris « dans le cadre » de
ce que Go propose déjà : restons dans cette case pour l'instant.

Une telle chose, à l'improviste, serait de passer à un rapport d'erreur
fonction une liste de "error.Methods" à exécuter sous conventionnel
conditions (io.EOF, sql.ErrNoRows), au lieu de rapporter le résultat
en noir et blanc. Mais je ne suis pas scolarisé dans ces domaines, mes suggestions
sont trop naïfs, laissez les autres (Roger, écoutez-vous ?)
les idées se concrétisent.

Lucio.

"Ce n'est pas fastidieux à taper. C'est fastidieux à lire, quand on a le même
3 lignes partout. C'est une répétition, et nous, en tant que programmeurs, d'habitude
ont tendance à en tenir compte. Peut-être qu'essayer a d'autres inconvénients, mais pas celui-ci
un je pense."

Encore une fois, malhonnête ou au minimum, rationalisant. J'accorde que ceux
trois lignes sont lues beaucoup plus de fois qu'elles ne sont écrites, mais le
la douleur que Griesemer visait à soulager est dans l'écriture et non dans la lecture

  • qu'il perçoit sans doute comme une conséquence bénéfique.

Mais "err != nil" est un marqueur très familier et lors de la "lecture" - comme
par opposition à la "recherche" à l'aide d'un éditeur, ce modèle est à la fois facile à
spot et facile à annuler. L'affacturage n'est pas la même chose
ligue du tout. Et le prix est faux.

« Nous, les programmeurs », avons tendance à factoriser d'abord des modèles plus complexes,
même s'ils surviennent rarement. On sait aussi que "if err != nil { return
err }" est compilé en une séquence d'instructions très simple, si
personne ne le fait, qu'ils lèvent la main ici. Peut-on être également
confiant que cela se produira avec « essayer - la fonction » ?

Lucio.

@lootch Hé mec, c'est vraiment improductif d'appeler les gens malhonnêtes, je suis presque sûr qu'ils ne le sont pas car les déclarations que vous avez marquées comme telles sont assez raisonnables.

Nous éliminons les schémas répétitifs en tant que programmeurs, c'est absolument vrai.

Beaucoup de code répétitif ralentit la lecture, donc je ne vois pas non plus en quoi c'est fallacieux.

Vos contre-arguments à ceux-ci sont essentiellement "allez mec, ce n'est pas si grave". Eh bien, pour beaucoup de gens, c'est le cas.

qui veut que les mauvaises personnes rejoignent notre communauté ?

C'est un gardien de porte super arrogant. De plus, l'outil tryhard a révélé que try est directement applicable dans la plupart des bases de code Go d'aujourd'hui. Il peut être directement appliqué à 70% du code de gestion des erreurs dans la bibliothèque standard. Avec les modifications apportées au code (pour utiliser la décoration d'erreur en utilisant defer , etc.), je pense que cela sera applicable à plus de 80% de la gestion des erreurs dans tout le code Go.

D'accord, je dépasse la marque, ici. Je m'excuse.

Je suppose que certains d'entre nous deviennent chauds sous le col alors que cette discussion
tourne en rond.

Le 7/7/19, Michal Štrba [email protected] a écrit :

@lootch Hé mec, c'est vraiment improductif d'appeler les gens malhonnêtes, je suis
à peu près sûr qu'ils ne le sont pas car les déclarations que vous avez marquées comme telles sont assez
raisonnable.

Nous éliminons les schémas répétitifs en tant que programmeurs, c'est absolument
vrai.

Beaucoup de code répétitif ralentit la lecture, donc je ne vois pas comment c'est
malhonnête non plus.

Vos contre-arguments à ceux-ci sont essentiellement "allez mec, ce n'est pas si
gros problème". Eh bien, pour beaucoup de gens, c'est le cas.

qui veut que les mauvaises personnes rejoignent notre communauté ?

C'est un gardien de porte super arrogant. De plus, l'outil tryhard a révélé que
try est directement applicable dans la plupart des bases de code Go d'aujourd'hui. C'est peut être
directement appliqué à 70 % du code de gestion des erreurs dans la bibliothèque standard. Avec
modifications apportées au code (pour utiliser la décoration d'erreur en utilisant defer , etc.), je crois
il sera applicable à plus de 80% de la gestion des erreurs dans tout le code Go.

--
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub :
https://github.com/golang/go/issues/32825#issuecomment -508971768

--
Lucio De Ré
2 rue Piet Retief
Kestell (État libre de l'Est)
9860 Afrique du Sud

Tél. : +27 58 653 1433
Cellulaire : +27 83 251 5824
TÉLÉCOPIE : +27 58 653 1435

@lootch Props sur la conscience de soi ! Je peux comprendre la frustration de voir la discussion tourner en rond.

Je le vois aussi de la même manière et je suis de l'autre côté.

Peut-être que les deux parties ne parviennent pas à se comprendre. Avez-vous lu mon article de blog intitulé Comment utiliser « essayer » ? où j'essaie de montrer à quoi ressemblerait l'utilisation de « try » dans la pratique, en faisant de mon mieux pour rester impartial ?

Le 7/7/19, Michal Štrba [email protected] a écrit :

[ ... ]
Peut-être que les deux côtés ne parviennent tout simplement pas à comprendre l'autre côté. Avez-vous
lire mon article de blog intitulé Comment utiliser 'essayer'? où j'essaye de
montrer à quoi ressemblerait l'utilisation de « try » dans la pratique, en faisant de mon mieux pour rester
impartial?

J'avoue que non, je souhaite ardemment de ne jamais avoir à le faire :-)

Avez-vous pensé aux aspects que Stockton a soulevés, où seuls
les avantages d'essayer sont affichés et il demande que le ventre mou
être révélé aussi ? Je crains d'être d'accord avec lui et - sans vouloir offenser -
que votre blog peut souffrir des mêmes lacunes.

Si ce n'est pas le cas, alors s'il vous plaît, poussez-moi, la bonne lecture a une place spéciale
dans ma vie :-)

Lucio.

@lootch J'ai fait de mon mieux pour montrer autant d'aspects d'"essayer" que possible (c'est-à-dire à la fois quand cela s'applique et quand cela ne s'applique pas) en peu de code et le rendre aussi impartial et réaliste que possible. Mais bien sûr, ne me croyez pas sur parole :)

C'était le commentaire le plus voté sur la discussion Reddit associée :

Il s'agit d'un exemple hypothétique utile et impartial. Merci d'avoir ajouté quelque chose de constructif à la conversation qui n'est pas seulement "ça craint".

@lootch J'ai fait de mon mieux pour montrer autant d'aspects d'"essayer" que possible (c'est-à-dire à la fois quand cela s'applique et quand cela ne s'applique pas) en peu de code et le rendre aussi impartial et réaliste que possible. Mais bien sûr, ne me croyez pas sur parole :)

C'était le commentaire le plus voté sur la discussion Reddit associée :

Il s'agit d'un exemple hypothétique utile et impartial. Merci d'avoir ajouté quelque chose de constructif à la conversation qui n'est pas seulement "ça craint".

Fonction avec chemin de fichier en argument ? Cela seul serait une raison pour laquelle ce code ne passerait pas mon examen. Que faire si certains champs sont manquants ? Réorganisé ?

@sirkon Pour ne pas être trop long, l'exemple est bien entendu simplifié. Les modifications qui seraient nécessaires pour résoudre les problèmes que vous avez soulevés n'affectent cependant pas les pratiques de gestion des erreurs, ce qui est tout ce qui compte ici.

Les modifications qui seraient nécessaires pour résoudre les problèmes que vous avez soulevés n'affectent pas les pratiques de gestion des erreurs

Parce que tu l'as dit ?

  1. Commencez par le titre de votre blog : il devrait s'appeler "comment ne pas écrire", car, je le répète, utiliser le chemin du fichier comme paramètre est une très mauvaise pratique et, franchement, tout un code en dessous aussi.
  2. Est-ce que tu t'en rends compte
    go resp := Respondent{ Name: name, Gender: try(parseField(s, &line, "gender")), OS: try(parseField(s, &line, "os")), Lang: try(parseField(s, &line, "lang")), }
    produira de mauvais messages d'erreur ? Il devrait y avoir au moins un message d'erreur de champ inattendu et un message d'erreur de champs manquants. Le diagnostic de votre script est de qualité inférieure.

PS J'ai regardé tes dépôts. Vous rendez-vous compte que Go est un mauvais outil pour vos tâches ? Vous devez comprendre que dans la pratique d'application réelle de Go, les premiers qui verront les journaux seront les ingénieurs d'exploitation, pas les développeurs. Un message d'erreur approprié peut les aider à résoudre eux-mêmes un problème.

@sirkon Allez, ne fais pas de

Vous rendez-vous compte que cela produira de mauvais messages d'erreur ?

Ils sont tout à fait adéquats au modèle. Le format devrait contenir tous les champs et dans l'ordre. Le message d'erreur le dit très clairement.

Si vous souhaitez contester la qualité du code, pourquoi ne pas le réécrire selon vos standards de qualité ? Si vous le faites, je vais essayer de faire de mon mieux pour réécrire votre code pour utiliser try .

PS J'ai regardé tes dépôts. Vous rendez-vous compte que Go est un mauvais outil pour vos tâches ?

Proposez-vous un autre pour mes tâches? J'en ai utilisé pas mal. Au fait, c'est assez hors sujet.

@faiface

Proposez-vous un autre pour mes tâches? J'en ai utilisé pas mal. Au fait, c'est assez hors sujet.

Rouiller? C++ ?

@sirkon

Rouiller? C++ ?

On y va. J'ai utilisé les deux avant de m'installer avec Go. Je n'ai jamais regardé en arrière.

@sirkon L'un des gros défauts de try est qu'il décourage les erreurs de décoration. Le programmeur dans ce cas montrait des applications possibles de try , donc bien sûr il n'y aura pas beaucoup de décoration d'erreur en cours.

De plus, discréditer les gens sur la base des projets sur lesquels ils ont travaillé est totalement hors sujet et injustifié. Vous avez été assez impoli avec vos derniers commentaires, et je veux au moins que vous en soyez conscient.

@deanveloper Merci pour le commentaire !

D'ailleurs

Le programmeur dans ce cas montrait des applications possibles de try, donc bien sûr, il n'y aura pas beaucoup de décoration d'erreur en cours.

Au cas où vous vous référez à mon blog, il y a en fait beaucoup de décorations d'erreurs, mais pas exactement de la même manière que @sirkon le ferait. Voici quelques messages d'erreur du programme qui utilise try :

parse respondnts.txt: open respondnts.txt: no such file or directory
parse respondents.txt: line 12: parse field gender: expected "gender:"
parse respondents.txt: line 9: expected empty line
parse respondents.txt: line 4: parse field lang: EOF

@faiface Mon erreur, j'aurais dû être plus précis. try décourage la décoration d'erreur lorsque vous voulez plusieurs messages d'erreur dans la même fonction. Il était possible de le faire avec check/handle draft et avec les contre-propositions "named handler". Il aurait été très utile dans le cas spécifique indiqué (où vous utilisiez try lors de l'initialisation d'une structure) de pouvoir ajouter une décoration autour de chaque message, mais malheureusement, la proposition d'essai rend cela un peu difficile à faire sans écrire votre propre fonction.

Vérifier / gérer n'aurait pas autant aidé dans votre cas particulier. Mais l'idée proposée de catch et d'autres contre-propositions à try auraient pu gérer les erreurs comme ajouter une décoration supplémentaire.

@deanveloper Eh bien, la plupart du temps, vous devez utiliser la même décoration pour toutes les erreurs d'une fonction, car les sous-fonctions doivent fournir leur propre contexte. Cependant, lorsque vous devez décorer les choses différemment dans une seule fonction, il existe toujours une solution simple avec try :

..., err := functionThatCanFail(...)
try(errors.Wrapf(err, ...))

Ou divisez simplement la grande fonction en plusieurs petites.

@faiface à mes yeux à ce stade, il faut simplement utiliser if err != nil , mais je suppose que c'est une question de préférence.

Cependant, parfois (comme dans le cas de l'initialisation de struct), ce n'est pas une bonne idée de diviser en plusieurs fonctions. Je deviens un peu tatillon bien que je suppose.

En fait, je ne suis pas super contre try , mais je ne suis pas non plus un grand partisan. Je pense qu'il existe une autre meilleure solution.

@deanveloper

Cependant, parfois (comme dans le cas de l'initialisation de struct), ce n'est pas une bonne idée de diviser en plusieurs fonctions.

C'est vrai, mais il n'est pas non plus nécessaire de les décorer différemment, car toute la décoration spécifique requise vient de parseField .

Je pense qu'il existe une autre meilleure solution.

C'est bien possible ! Si je vois une meilleure solution, je laisserai tomber try dans une minute :)

la plupart du temps, vous devez utiliser la même décoration pour toutes les erreurs dans une fonction, car les sous-fonctions doivent fournir leur propre contexte

@faiface Je suis fortement en désaccord avec cette déclaration. Chaque fonction est une sous-fonction d'une autre sur la pile des appels. Cela signifie qu'il a les mêmes responsabilités dans le flux de gestion des erreurs (c'est-à-dire fournir un contexte d'erreur à la portée supérieure).

Imaginez une fonction qui ajoute deux morceaux de données à un seul fichier. Comment distingueriez-vous lequel de ces ajouts a échoué si vous renvoyiez à peine une instruction « impossible d'écrire dans le fichier » ?

Nous sommes tous des créatures paresseuses. Moi aussi je préférerais faire quelque chose une fois pour toutes si je le pouvais. Et oui, quand j'ai commencé mon aventure avec Go, je considérais la gestion des erreurs comme un peu lourde. Après quelques années de pratique, ma vue a tourné à 180 degrés. Je pense que la gestion actuelle des erreurs dans Go favorise une programmation responsable et une bonne conception. À mon humble avis, ce serait un énorme échec d'ajouter un autre mécanisme qui sape cette approche.

@mklimuk Un élément clé de mon commentaire est "la plupart du temps". L'exemple que vous avez fourni est probablement mieux géré par if err != nil . Comme indiqué à plusieurs reprises, try n'est pas conçu pour gérer toutes les situations, seulement les plus courantes.

Et les preuves montrent qu'il le fait, car 70% de tout le code de gestion des erreurs dans la bibliothèque standard peut utiliser try prêt à l'emploi et il en va de même pour 59% de tout le code de gestion des erreurs dans le sauvage.

@faiface eh bien, le fait que try puisse remplacer la gestion explicite des erreurs ne signifie pas qu'il le devrait. Dans mon cas, renvoyer une erreur sans y ajouter de contexte n'est pas "la situation la plus courante". C'est le contraire :)

Les personnes qui votent pour ce fil craignent simplement que cette nouvelle déclaration ne gâche tout l'effort derrière la conception originale de Go (simplicité, clarté, etc.) dans le but de rendre le code Go moins verbeux.

Bien sûr, mais j'espère que vous comprenez que try ne sert pas à renvoyer une erreur sans contexte. En fait, le cas le plus courant d'ajout de contexte (un contexte par fonction) est largement simplifié par try :

func doSomething() (err error) {
    defer fmt.HandleErrorf(&err, "doing something")

    x := try(oneThing())
    try(anotherThing(x))
    // ...
}

La chose à réaliser est que la plupart du temps, oneThing() et anotherThing() renverront un contexte suffisant par eux-mêmes, donc l'envelopper dans un simple "doing something: ..." est tout à fait suffisant.

En passant, je pense que nous pourrions utiliser certaines conventions sur _qui_ fait la décoration. Dans la stdlib, certaines fonctions font cela, ex copy: x to y ou similaire, j'ai personnellement laissé la décoration à l'appelant, car il a les arguments.

Par exemple, si j'avais un Copy() je ferais quelque chose comme return errors.Wrap(err, "writing") et l'appelant utilisant Copy() terminerait par errors.Wrapf(err, "copying from %v to %v", src, dst) ou similaire.

Ces deux-là ne se mélangent pas très bien et peuvent parfois se retrouver avec des chaînes en double, est-il préférable de simplement dire que le style stdlib est idiomatique ? Je ne me souviens pas qu'ils se soient tous comportés comme ça. Je pense que c'est la seule façon dont l'exemple de

J'ai personnellement laissé la décoration à l'appelant, car il a les arguments.

Oui. Par exemple, considérons une fonction qui analyse le corps JSON d'une requête HTTP, vérifie les en-têtes, etc. S'il est alimenté en JSON syntaxiquement invalide, sa responsabilité - tout ce qu'il peut faire, vraiment - est de signaler l'erreur. L'_appelant_ sait quelle partie de l'API tentait d'être appelée et doit décorer l'erreur en conséquence avant de la transmettre à son tour dans la chaîne ou d'émettre une erreur HTTP.

Si vos fonctions sont vraiment un code à usage général qui pourrait être utilisé à plusieurs endroits, elles n'auront pas les informations nécessaires pour décorer l'erreur. Au contraire, si elles ont tout le contexte, ce ne sont probablement pas vraiment des fonctions qui ont du sens en tant que fonctions autonomes, vous créez simplement des fonctions pour décomposer le code et le faire paraître mieux organisé qu'il ne l'est en réalité.

@lpar Mind en donne un exemple précis ?

J'ai déjà donné un exemple précis ? Déterminez si votre fonction parseJSON connaissait réellement le contexte et était capable de décorer le point de terminaison d'API et le flux d'activité pour lesquels elle analysait le corps. Cela suggérerait soit qu'il était spécifique à ce point de terminaison, soit que vous transmettiez les informations simplement pour pouvoir les utiliser pour envelopper les erreurs.

@lpar D'accord, c'est donc un autre exemple où if err != nil restera utilisé. Ou vous divisez votre logique en plusieurs fonctions.

Mais comprenez que donner un exemple où try n'est pas approprié n'est pas un argument contre try . try n'est pas destiné à remplacer toute la gestion des erreurs, seulement les cas les plus courants.

Screenshot 2019-07-07 at 6 30 42 PM

@abejide001 la try n'est pas le "try/catch" traditionnel de beaucoup d'autres langages, c'est beaucoup plus similaire à la macro try! dans Rust. Bon meme si lol

Oups - posté sur le mauvais problème. Déplacé vers https://github.com/golang/go/issues/32437#issuecomment-509024693 .

J'ai récemment posté une proposition #32968 qui s'appuie sur mon désaccord avec la dangereuse capacité d'imbrication que possède la macro try . Bien que j'espère qu'il manque de sérieux défauts, en tant qu'auteur, je ne suis pas la bonne personne pour en voir. J'aimerais donc demander à mon camp _do not try_ (vous :) de le voir, de l'évaluer et de le commenter.


Extrait:

  • _La macro check n'est pas une simple ligne : elle aide le plus là où de nombreuses
    les contrôles utilisant la même expression doivent être effectués à proximité._
  • _Sa version implicite se compile déjà sur Playground._

Contraintes de conception (satisfaites)

C'est un élément intégré, il ne s'emboîte pas dans une seule ligne, il permet beaucoup plus de flux que try et n'a aucune attente quant à la forme d'un code à l'intérieur. Il n'encourage pas les retours nus.

exemple d'utilisation

// built-in 'check' macro signature: 
func check(Condition bool) {}

check(err != nil) // explicit catch: label.
{
    ucred, err := getUserCredentials(user)
    remote, err := connectToApi(remoteUri)
    err, session, usertoken := remote.Auth(user, ucred)
    udata, err := session.getCalendar(usertoken)

  catch:               // sad path
    ucred.Clear()      // cleanup passwords
    remote.Close()     // do not leak sockets
    return nil, 0, err // dress before leaving
}
// happy path

// implicit catch: label is above last statement
check(x < 4) 
  {
    x, y = transformA(x, z)
    y, z = transformB(x, y)
    x, y = transformC(y, z)
    break // if x was < 4 after any of above
  }

J'espère que cela vous aidera, profitez-en !

Mais comprenez que donner un exemple où try n'est pas approprié n'est pas un argument contre try . try n'est pas destiné à remplacer toute la gestion des erreurs, seulement les cas les plus courants.

Et selon les statistiques que j'ai publiées plus tôt , ce ne sont pas les cas les plus courants dans mon code. Les cas les plus courants dans mon code sont les erreurs encapsulées avant le retour. Donc, try ne serait approprié que pour un pourcentage à un chiffre de mes retours d'erreur au mieux (*), c'est pourquoi je pense que nous avons besoin de quelque chose de mieux.

(*) Et en fait, je suis enclin à penser que les instances de retour nues err sont susceptibles d'être des erreurs qui devraient être corrigées.

Tout à fait d'accord, laissez "if err != nil" seul.

@abejide001 la try n'est pas le "try/catch" traditionnel de beaucoup d'autres langages, c'est beaucoup plus similaire à la macro try! dans Rust. Bon meme si lol

Cela seul me préoccupe, le Go est déjà un langage étrange pour les nouveaux arrivants, et maintenant nous devons expliquer pourquoi try a une logique sur mesure. FWIW, je ne pense pas que dire "La rouille l'a fait" soit une bonne raison pour justifier l'ajout de quoi que ce soit dans une langue - ce n'est tout simplement pas bien connu.

@comme je ne disais pas cela pour justifier la fonctionnalité, je le disais simplement pour clarifier ce que la fonctionnalité faisait. Je suis assez au milieu avec la proposition try .

Non pas que cela fasse une grande différence, mais c'est aussi une fonctionnalité de Swift, bien qu'avec un mot-clé au lieu d'une macro.

Il semble qu'il y ait une certaine confusion quant à ce que try essaie de réaliser exactement. À mon humble avis, le problème n'est pas d'écrire plusieurs blocs if qui vérifient les erreurs. Vous les écrivez une fois et vous avez terminé. Le problème est de lire le code qui a plusieurs de ces blocs. Nous faisons beaucoup plus de lecture que d'écriture. Et ces blocs obscurcissent le code réel car ils s'entrelacent avec lui. Pire encore, la plupart du temps, ils sont presque exactement les mêmes, avec seulement une différence de chaîne mineure quelque part dans ce bloc if.

Personnellement, je préférais l'ancien contrôle - gérer le brouillon, mais cela fait au moins un bon travail en séparant les erreurs et les chemins commerciaux. Et nous pourrions enfin avoir un contexte de portée de fonction unique par opposition à pour chaque appel, qui a actuellement de bonnes chances de répéter la même chose que l'erreur parent.

@icholy a écrit :

Il y a eu des retours de la communauté écrasants demandant une gestion des erreurs plus rationalisée (à partir de l'enquête annuelle). L'équipe Go s'attaque maintenant à ce problème.

Je viens de consulter le sondage ici : https://blog.golang.org/survey2018-results

Apparemment, la question était : « Quel est le plus grand défi auquel vous êtes personnellement confronté en utilisant Go aujourd'hui ? » avec la réponse possible "Traitement des erreurs".

Je me demande sérieusement comment, sur la base de cette question + réponse, il a été déduit qu'une syntaxe plus brève était nécessaire. J'aurais peut-être aussi répondu "gestion des erreurs", mais en aucun cas j'aurais voulu voir une autre syntaxe. Si j'avais coché cette option dans l'enquête, j'aurais pensé à mieux permettre d'envelopper les erreurs, de leur fournir des traces de pile, etc.

Ma suggestion serait de prendre du recul par rapport à toutes les propositions de gestion des erreurs (en fait, ce que @miekg suggérait). Et d'abord, déterminez ce que veut réellement la communauté, documentez-le. Ensuite, découvrez pourquoi c'est ce qu'ils veulent. Et seulement après, commencez à chercher des moyens d'y parvenir.

Je viens de parcourir la proposition d'essai, mais à moins que je manque quelque chose, il omet de dire _pourquoi_ il est proposé, à part "pour éliminer le passe-partout si les déclarations [...}". Mais il n'y a aucune indication sur la raison pour laquelle l'élimination de ces déclarations passe-partout est nécessaire.

Je suis tout à fait d'accord avec ce qui précède. Voyons si les nouvelles valeurs d'erreur changent pour aider à gérer les erreurs que les gens ont avec Go. Ensuite, nous pouvons voir si une syntaxe plus brève est requise.

Les gens ici argumentent contre try parce qu'ils pensent que toutes les erreurs renvoyées doivent être annotées. La réalité est que, dans le corpus de code actuel (y compris la bibliothèque standard), un pourcentage élevé de vérifications d'erreurs ont des retours d'erreur ~ nus~ non annotés et bénéficieraient de try . Votre croyance sur la façon dont le code DEVRAIT être n'a rien à voir avec la façon dont le code EST . Épargnez-moi votre dogme.

@icholy Ignorer les erreurs indique que le développeur ne se soucie pas de cette erreur. L'erreur est insignifiante ou est considérée par l'appelant comme impossible. Si tel est le cas, alors "essayer" est tout aussi inutile, l'appelant n'envelopperait tout simplement pas la fonction dans un "essayer".

Ma suggestion serait de prendre du recul par rapport à toutes les propositions de gestion des erreurs (en fait, ce que @miekg suggérait). Et d'abord, déterminez ce que veut réellement la communauté, documentez-le. Ensuite, découvrez pourquoi c'est ce qu'ils veulent. Et seulement après, commencez à chercher des moyens d'y parvenir.

Je suis tout à fait d'accord avec cela. Je vois beaucoup de désaccords de base sur les fonctionnalités que toute amélioration de la gestion des erreurs Go devrait même prendre en charge. Chaque morceau différent de fonctionnalité que les gens mentionnent déclenche le bikeshedding sur son nom et sa syntaxe, donc la discussion ne va nulle part.

J'aimerais savoir plus en détail ce que la communauté Go au sens large attend réellement de toute nouvelle fonctionnalité de gestion des erreurs proposée.

J'ai mis en place une enquête répertoriant un tas de fonctionnalités différentes, des fonctionnalités de gestion des erreurs que j'ai vu des gens proposer. J'ai soigneusement _omis_ toute dénomination ou syntaxe proposée, et j'ai bien sûr essayé de rendre l'enquête neutre plutôt que de favoriser mes propres opinions.

Si des personnes souhaitent participer, voici le lien, raccourci pour le partage :

https://forms.gle/gaCBgxKRE4RMCz7c7

Tous ceux qui participent devraient pouvoir voir les résultats résumés. Alors peut-être qu'une fois que nous aurons une meilleure idée de ce que les gens veulent réellement, nous pourrons avoir une discussion intelligente pour savoir si la proposition d'essai fournit ces choses. (Et puis peut-être même continuer à discuter de la syntaxe.)

@lane-c-wagner essayez-vous de dire que renvoyer une erreur non annotée revient à ne pas la renvoyer du tout ? edit : correction du commentaire précédent

@icholy Ah j'ai mal compris. Quand vous avez dit "nu", je pensais que vous vouliez dire "_" erreurs ignorées.

Cette proposition soutient qu'aucune action ne devrait être une action valide. Ce changement affecte tous les utilisateurs de la langue car ils lisent le code. En tant que tel, une enquête identifiant le plus grand obstacle doit encore demander à la communauté si cet obstacle vaut la peine d'être corrigé. Cette proposition est l'évaluation la plus proche d'une telle question.

S'il vous plaît arrêtez de dire "que tout le monde est libre d'ignorer" try . Nous lisons du code écrit par d'autres.

@tv42 Je ne sais pas si vous vous ici , mais je l'ai dit aussi, et vous avez raison. Coupable comme accusé. Je vais essayer d'être plus prudent avec des généralisations comme celle-là. Merci.

@griesemer votre sondage manquait énormément. J'ai voté pour la gestion des erreurs, mais le problème que je voulais dire était la sécurité du type complet, pas la verbosité. Vous feriez mieux d'en faire un autre sur les erreurs seulement.

Et je veux toujours des types de somme.

Ceci est une proposition sur la façon dont gofmt formate actuellement if err != nil

(Ce n'est pas une opinion sur la proposition try().)

Lorsqu'une instruction if renvoie une valeur d'erreur non nulle sur une ligne, telle que :

err := myFunc()
if err != nil {
    return err
}

gofmt pourrait assouplir sa propre règle d'instruction if et la formater sur une ligne comme ceci :

err := myFunc()
if err != nil { return err }

Trois lignes de code de gestion des erreurs deviennent une seule ligne. Moins d'encombrement. Flux de programme plus facile à suivre.

Il devra y avoir un certain jugement sur l'endroit où tracer la ligne (jeu de mots reconnu) avec ce
changement de règle de gofmt. Il peut inclure des décorations, telles que :

err := myFunc()
if err != nil { return fmt.Errorf("myFunc() blew up! %v", err }

Mais la gestion élaborée des erreurs multilignes doit rester telle qu'elle est : multiligne, claire et explicite.

La proposition _try_ a été retirée : https://github.com/golang/go/issues/32437#issuecomment -512035919

Les génériques n'importe qui ?

Ceci est une proposition sur la façon dont gofmt formate actuellement if err != nil

J'ai essayé cela, à mon humble avis, le code est encore plus illisible de cette façon qu'avec le formatage multiligne. essayer est bien mieux que cette solution.

OMI, le problème ici n'est pas de savoir comment la gestion des erreurs est effectuée, mais si elle est ignorée . Ne serait-il pas possible de laisser la syntaxe if err != nil telle quelle, mais de restreindre l'ignorance des retours Error ? Comme en faire un avertissement/erreur du compilateur avec option de gravité pour le code hérité.

OMI, le problème ici n'est pas de savoir comment la gestion des erreurs est effectuée, mais si elle est ignorée . Ne serait-il pas possible de laisser la syntaxe if err != nil telle quelle, mais de restreindre l'ignorance des retours Error ? Comme en faire un avertissement/erreur du compilateur avec option de gravité pour le code hérité.

Beaucoup de gens veulent un linter affichant les erreurs ignorées.

Je préférerais en faire une erreur difficile, mais en regardant les tonnes d'héritage déjà écrit, linter est également juste.

je trouve https://github.com/kisielk/errcheck utile pour me parler d'erreurs non gérées @plyhun @sorenvonsarvort

Comme vu dans la discussion sur #32437, cette proposition a en effet été acceptée pour l'instant. Fermeture. Si le problème se pose à nouveau, une nouvelle proposition peut être ouverte.

Je commence à penser que l'une des raisons pour lesquelles beaucoup de propositions ont l'impression qu'elles ne correspondent pas tout à fait, c'est parce qu'elles essaient en fait de résoudre deux problèmes différents en même temps. D'une part, il est vrai qu'avoir des blocs err != nil après presque chaque appel de fonction peut interrompre le flux du code d'une manière étrange, même si cela a certainement ses avantages, mais je pense que ce n'est que la moitié du problème. L'autre problème est que la gestion de plusieurs retours, qu'il y ait eu des erreurs ou non, peut être assez maladroit.

Les fonctions de retour multiples semblent très, très différentes des fonctions de retour simples, malgré la différence apparemment minime entre les deux. C'est un peu comme s'il y avait des restrictions supplémentaires sur les fonctions d'appel qui prennent plus d'un argument. C'est parfois très étrange à gérer. Lorsque vous appelez une fonction avec plusieurs valeurs de retour, vous devez presque toujours le faire sur sa propre ligne, et elle, combinée avec := , est souvent la principale source des divers problèmes d'ombrage variable qui ont été discutés ailleurs . Vous ne pouvez pas enchaîner les appels de méthode sur eux, vous ne pouvez pas les affecter directement à un champ de structure et à une nouvelle variable sur la même ligne, et ainsi de suite.

Je ne sais pas. C'est peut-être juste moi. Mais j'utilise Go depuis près de 10 ans maintenant et appeler des fonctions avec plusieurs retours me semble parfois un peu gênant.

Merci!

Il y a en fait un problème avec if err != nil , la portée de err peut vivre plus longtemps qu'elle ne le devrait. Lorsque vous inlinez le if cela résout le problème, mais tous les cas ne peuvent pas être inline.

if err := foo(); err != nil {
if _, err := bar(); err != nil {



md5-6a135eb952fe7b24b3389cb16d3244a1



a, err := bar()
if err != nil {



md5-d52f811d3e31bb368bd8045cfb2e93b4



var err error
baz.A, err = bar()
if err != nil {

La variable err ne doit pas exister dans la portée de la fonction après la fin du bloc if err != nil {} . Voici ma proposition qui s'appuie sur la proposition try() pour résoudre le problème https://github.com/golang/go/issues/33161. J'aimerais avoir des retours constructifs.

La variable err ne doit pas exister dans la portée de la fonction après la fin du bloc if err != nil {}.

pourquoi « devrait-il » ne pas exister après la fin du bloc if ? Le compilateur peut l'optimiser (s'il le juge nécessaire), et il n'y a pas de charge mentale lorsque le bloc err := stmt()\nif err != nil {} se termine car ils vont presque toujours ensemble.

Je n'ai pas encore examiné votre proposition en profondeur (bien que je vous félicite d'avoir fait l'effort d'en rédiger une !). Cependant, comme je l'ai également souligné dans mon commentaire ci-dessus, je pense que des recherches supplémentaires sont nécessaires sur tous les problèmes perçus, avant de creuser dans des propositions pour les résoudre.

Les erreurs if err != nil terminé, principalement parce que nous agissons déjà comme si ce n'était pas le cas.

Dans l'exemple CopyFile, il y a r, err := os.Open(src) suivi de w, err := os.Create(dst) . Le second err ombrage le premier. L'ombrage des variables est généralement mal vu.

Il y a aussi d'autres bizarreries. Si j'ai err := foo() et plus tard quelque chose comme bar.V, err = baz() , si le code est refactorisé et que je n'ai plus besoin de foo(), je devrais ajouter var err error avant le baz ligne. . Je ne pense pas que la refactorisation d'un emplacement différent dans une fonction devrait affecter d'autres endroits comme celui-ci.

Techniquement en

    r, err := os.Open(src)
    if err != nil {
        return ...
    }
    w, err := os.Create(dst)

la deuxième instance de err ne masque pas la première instance. Ce sont en fait la même variable. Voir la discussion sur la redéclaration des variables sur https://golang.org/ref/spec#Short_variable_declarations.

func faireQuelqueChose() {
r, err := os.Open(nom de fichier)
panic(fmt.Errorf(err, "échec de l'ouverture du fichier : %s", nom du fichier)) //
C'est la panique ici.

}

Le jeudi 10 octobre 2019 à 11 h 24, clearcode [email protected] a écrit :

Je pense que nous pouvons ajouter une fonction buildin :

affirmer()

Exemple:

func doSomeThing() erreur {

r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error

resp,err := http.Get(someURL)
assert(err, "la demande a échoué")

}

et une autre fonction qui ne renvoie pas d'erreur :

func faireQuelqueChose() {
r, err := os.Open(nom de fichier)
assert(err, "échec de l'ouverture du fichier : %s", nom du fichier) // C'est la panique ici.

}

donc assert(error, args ...interface{}) vaut mieux que : if err != nil ; {
retour erreur }

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/golang/go/issues/32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5KWXHJKTDN5
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A
.

Le point à retenir est que je veux voir l'erreur réelle renvoyée dans le courant
fonction dans la ligne courante.

Le vendredi 11 octobre 2019 à 9 h 55, Aaaa Einai [email protected] a écrit :

func faireQuelqueChose() {
r, err := os.Open(nom de fichier)
panic(fmt.Errorf(err, "échec de l'ouverture du fichier : %s", nom du fichier)) // C'est la panique ici.

}

Le jeu. 10 octobre 2019 à 11h24 clearcode [email protected]
a écrit:

Je pense que nous pouvons ajouter une fonction buildin :

affirmer()

Exemple:

func doSomeThing() erreur {

r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error

resp,err := http.Get(someURL)
assert(err, "la demande a échoué")

}

et une autre fonction qui ne renvoie pas d'erreur :

func faireQuelqueChose() {
r, err := os.Open(nom de fichier)
assert(err, "échec de l'ouverture du fichier : %s", nom du fichier) // C'est la panique ici.

}

donc assert(error, args ...interface{}) vaut mieux que : if err != nil ; {
retour erreur }

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/golang/go/issues/32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5KWXHJKTDN5
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A
.

Franchement, je ne veux pas d'un retour implicite que try fournit. Si nous avions des génériques, je préférerais de loin une solution qui utilise plutôt un comportement monade.

type Result<T> interface {
  Expect(err error) T
  OrElse(defaultValue T) T
}

func From<T>(value T, err error) Result<T> { ... }

Pour moi, c'est beaucoup plus propre que la fonction intégrée actuellement proposée, bien que d'autres modifications soient nécessaires pour ce qui précède car vous auriez une prolifération de méthodes renvoyées (valeur, erreur) et résultat

C'est tellement similaire à Rust's Ok et Err.
Je pense que if err != nil {} peut-être mieux un peu.

@Yanwenjiepy c'est intentionnel, je suis un grand fan du type Result de Rust.

J'ai commencé à apprendre le Go depuis moins de 10 minutes. La toute première chose que j'ai remarquée dans le code que je regardais était que cette copie était collée encore et encore :

someValue, err := someFunction();
if err != nil {
  panic(err)
}

Je ne suis évidemment pas un expert, mais cela pourrait être utile qu'il ne m'ait fallu que mon premier coup d'œil pour me retrouver sur ce fil.

C'est parce que vous regardez des extraits de code pour l'apprentissage. Le vrai code doit gérer les erreurs, pas seulement la panique et les plantages.

C'est vrai, mais les erreurs peuvent (et doivent souvent) être regroupées. C'est pourquoi les blocs try/catch existent dans d'autres langues. Par exemple, ce qui suit sentirait beaucoup moins les dinosaures pour moi :

try {
  foo, throw err := someFunction();
  bar, throw err := foo.get();
  baz, throw err := bar.make();
  qux, throw err := baz.transform();
} catch(err) {
  // "Unable to foo bar baz qux."
  tryHarder();
}

Encore une fois, profane total. Mais le code n'est que des symboles, et s'ils se répètent suffisamment, vous pouvez également créer un symbole pour cela. Cela semble être un symbole qui se répète très fréquemment.

Vous voudrez peut-être jeter un œil à la publication Les erreurs sont des valeurs de Rob Pike pour voir comment vous pouvez utiliser un assistant pour fusionner les erreurs et les traiter toutes en même temps. En pratique, intercepter toutes les exceptions avec une seule clause est considéré comme un mauvais style dans la plupart des langages qui en contiennent, car vous finissez par cacher des informations sur ce qui s'est réellement passé. (Et si vous étendez l'exemple pour décomposer les exceptions individuelles interceptées et ne pas jeter ces informations, le code finit par être aussi long que l'équivalent Go.)

Merci pour le lien. Le errWriter est une solution tout à fait passable.

C'est vrai, mais les erreurs peuvent (et doivent souvent) être regroupées. C'est pourquoi les blocs try/catch existent dans d'autres langues. Par exemple, ce qui suit sentirait beaucoup moins les dinosaures pour moi :

try {
  foo, throw err := someFunction();
  bar, throw err := foo.get();
  baz, throw err := bar.make();
  qux, throw err := baz.transform();
} catch(err) {
  // "Unable to foo bar baz qux."
  tryHarder();
}

Encore une fois, profane total. Mais le code n'est que des symboles, et s'ils se répètent suffisamment, vous pouvez également créer un symbole pour cela. Cela semble être un symbole qui se répète très fréquemment.

Disons que chaque fonction renvoie un type d'erreur qui se chevauche et que vous devez gérer tous les résultats de la fonction avec élégance, comment écrivez-vous tryHarder() ?

try {
  foo, throw err := someFunction();  // err could be TypeA and TypeB
  bar, throw err := foo.get();       // err could be TypeB and TypeC
  baz, throw err := bar.make();      // err could be TypeA and TypeC
  qux, throw err := baz.transform(); // err could be TypeB and TypeD
} catch(err) {
  tryHarder(); // tell me how to handle each error?
}

Cela ne prendra qu'une minute à quelqu'un d'autre pour comprendre le code ci-dessous :

foo, err := someFunction();  // err could be TypeA and TypeB
if err != nil {
 // handle err
}

bar, err := foo.get();       // err could be TypeB and TypeC
if err != nil {
  // handle err
}

baz, err := bar.make();      // err could be TypeA and TypeC
if err != nil {
  // handle err
}

qux, err := baz.transform(); // err could be TypeB and TypeD
if err != nil {
  // handle err
}

Disons que chaque fonction renvoie un type d'erreur qui se chevauche et que vous devez gérer tous les résultats de la fonction avec élégance

Dans cet exemple, vous avez tout à fait raison.

Cette page vous a été utile?
0 / 5 - 0 notes