Quand et pourquoi vous devez utiliser void (au lieu par exemple de bool / int)

30

Je rencontre parfois des méthodes où un développeur a choisi de renvoyer quelque chose qui n'est pas critique pour la fonction. Je veux dire, quand on regarde le code, ça marche apparemment aussi bien qu'un voidet après un moment de réflexion, je demande "Pourquoi?" Cela vous semble-t-il familier?

Parfois, je conviens que le plus souvent, il vaut mieux retourner quelque chose comme un boolou intplutôt que de faire un void. Je ne suis pas sûr cependant, dans l'ensemble, des avantages et des inconvénients.

Selon la situation, le renvoi d'un intpeut rendre l'appelant conscient du nombre de lignes ou d'objets affectés par la méthode (par exemple, 5 enregistrements enregistrés dans MSSQL). Si une méthode comme "InsertSomething" renvoie un booléen, je peux avoir la méthode conçue pour retourner en truecas de succès, sinon false. L'appelant peut choisir d'agir ou non sur ces informations.

D'autre part,

  • Peut-il conduire à un objectif moins clair d'un appel de méthode? Un mauvais codage m'oblige souvent à revérifier le contenu de la méthode. S'il retourne quelque chose, il vous indique que la méthode est d'un type que vous devez faire quelque chose avec le résultat renvoyé.
  • Un autre problème serait, si l'implémentation de la méthode vous est inconnue, qu'est-ce que le développeur a décidé de renvoyer qui ne soit pas critique? Bien sûr, vous pouvez le commenter.
  • La valeur de retour doit être traitée, lorsque le traitement pourrait être terminé à la parenthèse fermante de la méthode.
  • Que se passe-t-il sous le capot? La méthode appelée a-t-elle été récupérée à falsecause d'une erreur levée? Ou est-elle retournée fausse en raison du résultat évalué?

Quelles sont vos expériences avec cela? Comment agiriez-vous là-dessus?

Indépendant
la source
1
Une fonction qui renvoie vide n'est techniquement pas du tout une fonction. C'est juste une méthode / procédure. Renvoyer voidau moins permet au développeur de savoir que la valeur de retour de la méthode est sans conséquence; il fait une action, plutôt que de calculer une valeur.
KChaloux

Réponses:

32

Dans le cas d'une valeur de retour bool pour indiquer le succès ou l'échec d'une méthode, je préfère le Tryparadigme -prefix utilisé dans divers dans les méthodes .NET.

Par exemple, une void InsertRow()méthode peut lever une exception s'il existe déjà une ligne avec la même clé. La question est, est-il raisonnable de supposer que l'appelant s'assure que sa ligne est unique avant d'appeler InsertRow? Si la réponse est non, je fournirais également un bool TryInsertRow()qui renvoie faux si la ligne existe déjà. Dans d'autres cas, tels que des erreurs de connectivité db, TryInsertRow peut toujours lever une exception, en supposant que le maintien de la connectivité db est la responsabilité de l'appelant.

Papier bulle
la source
4
+1 pour faire la distinction entre la gestion des défaillances attendues et inattendues - ma propre réponse ne l'a pas suffisamment souligné.
Péter Török
Absolument un +1 pour avoir essayé d'inventer un principe pour les méthodes TryXX qui, apparemment, rend la méthode d'essai et la méthode principale plus faciles à maintenir et à comprendre leur objectif.
Indépendant
+1 Bien dit. La clé de la meilleure réponse à cette question est que, parfois, vous voulez donner à l'appelant l'option d'une méthode affirmative qui lèvera une exception. Dans ces cas, cependant, l'exception ne doit pas être interceptée et manipulée de manière frauduleuse par la méthode affirmative. Le fait est que l'appelant est rendu responsable. D'un autre côté, un paradigme Try (ou Monad) devrait intercepter et gérer les exceptions, puis renvoyer un booléen ou un Enum descriptif si plus de détails sont nécessaires. Notez qu'un appelant s'attend à ce qu'un Try / Monad ne lève JAMAIS d'exception. C'est comme un point d'entrée.
Suamere
Donc, comme une exception ne devrait jamais se produire à partir d'un Try / Monad, un booléen n'est pas suffisamment descriptif pour informer l'appelant qu'une connexion db a échoué ou qu'une demande http a l'une des nombreuses valeurs de retour nécessaires pour agir différemment, vous pouvez utiliser un Enum au lieu d'un bool pour votre Try / Monad.
Suamere
Si vous avez TryInsertRow - renvoyant un booléen - si, disons, il y a plus d'une contrainte unique dans la base de données - comment savez-vous exactement quelle était l'erreur - et communiquez-la à l'utilisateur? Faut-il utiliser un type de retour plus riche contenant le message d'erreur / le code et le succès?
niico
28

À mon humble avis, le renvoi d'un "code d'état" provient de l'époque historique, avant que les exceptions ne deviennent monnaie courante dans les langages de niveau intermédiaire à supérieur tels que C #. De nos jours, il est préférable de lever une exception si une erreur inattendue a empêché votre méthode de réussir. De cette façon, il est sûr que l'erreur ne passe pas inaperçue, et l'appelant peut la traiter comme il convient. Donc, dans ces cas, il est parfaitement correct qu'une méthode ne retourne rien (c'est-à-dire void) au lieu d'un indicateur d'état booléen ou d'un code d'état entier. (Bien sûr, il faut documenter les exceptions que la méthode peut lancer et quand.)

D'un autre côté, s'il s'agit d'une fonction au sens strict, c'est-à-dire qu'elle effectue un calcul basé sur des paramètres d'entrée et qu'elle devrait renvoyer le résultat de ce calcul, le type de retour est évident.

Il y a une zone grise entre les deux, quand on peut décider de renvoyer des informations "supplémentaires" de la méthode, si cela est jugé utile pour ses appelants. Comme votre exemple sur le nombre de lignes affectées dans un insert. Ou pour qu'une méthode place un élément dans une collection associative, il peut être utile de renvoyer la valeur précédente associée à cette clé, s'il y en avait. De telles utilisations peuvent être identifiées en analysant soigneusement les scénarios d'utilisation (connus et prévus) d'une API.

Péter Török
la source
4
+1 pour les exceptions par rapport au code d'état. L'exception (mauvais jeu de mots) à cela est le modèle TryXX bien utilisé.
Andy Lowry
éter je suis avec toi là-bas. TcF et ne pas taire les erreurs! Probablement, cependant, la zone grise. Il peut toujours être utile de renvoyer quelque chose. Lignes affectées, dernier ID inséré, un PID de logiciel exécuté, mais je pense quand même qu'il est plus facile de penser "enfer ouais, je retourne ceci" lors du développement. Puis, un an plus tard, lors de l'extension, «quoi?
Indépendant
+1 d'informations supplémentaires expliquent pourquoi je code des méthodes comme celle-ci dans des langages de niveau supérieur comme C #. Cela facilite l'utilisation des informations supplémentaires lorsque vous en avez besoin.
ashes999
2
-1 Wow, vos propres mots sont tellement contradictoires et faux. Vous ne devez jamais utiliser d'exceptions pour le flux de contrôle ou la communication. Ils sont infiniment plus chers qu'un conditionnel. Quand est le temps "avant que les exceptions ne deviennent monnaie courante"? ... vous voulez dire avant que les blocs try / catch soient devenus monnaie courante? Les exceptions sont courantes depuis toujours. "Lancez une exception si une erreur inattendue ..." Comment agissez-vous sur quelque chose d'inattendu? Pourquoi voudriez-vous attraper une exception, puis la relancer? C'est tellement cher. Pourquoi dites-vous "erreur"? Les erreurs et les exceptions sont différentes.
Suamere
1
Et qui peut dire qu'une exception levée ne passera pas inaperçue? Rien n'oblige personne à se connecter à un bloc catch, pourquoi ne pas se connecter à un bloc "then"? Pourquoi "documenter quelles exceptions la méthode peut lancer et quand"? Que diriez-vous si nous écrivons du code auto-documenté et qu'une méthode retourne simplement une énumération avec les états finaux attendus de cette méthode? Si une exception possible est évitable en vérifiant l'état, ils doivent être capturés et manipulés sans lancer.
Suamere
14

La réponse de Peter couvre bien les exceptions. D'autres considérations ici impliquent la séparation commande-requête

Le principe CQS dit qu'une méthode doit être soit une commande, soit une requête et non les deux. Les commandes ne doivent jamais renvoyer une valeur, ne modifient que l'état et les requêtes doivent uniquement renvoyer et non modifier l'état. Cela permet de garder la sémantique très claire et de rendre le code plus lisible et maintenable.

Il y a quelques cas où la violation du principe CQS est une bonne idée. Celles-ci concernent généralement les performances ou la sécurité des threads.

Andy Lowry
la source
1
-1 Faux et faux. Premièrement, tout l'intérêt de CQS réside dans le Q. Un appelant doit pouvoir obtenir des calculs ou des appels externes via un service avec état sans craindre de modifier l'état de ce service. De plus, bien que CQS soit un principe utile à suivre de manière lâche, il n'est strictement appliqué que dans des conceptions spécifiques. Enfin, même en CQS strict, rien ne dit qu'une commande ne peut pas retourner une valeur, elle dit qu'elle ne doit pas calculer ou appeler des calculs externes et renvoyer des données . C ou Q peuvent se sentir libres de retourner leur état final, si cela est utile pour l'appelant.
Suamere
Ah, mais le nombre de lignes affectées par une commande facilite la création de nouvelles commandes à partir des anciennes. Source: années et années d'expérience.
Joshua
@Joshua: De même, "ajouter un nouvel enregistrement et renvoyer un ID (pour certaines structures, un numéro de ligne) qui a été généré lors de l'ajout" peut être utilisé à des fins qui ne pourraient pas autrement être réalisées efficacement.
supercat
Vous ne devriez pas avoir besoin de renvoyer les lignes concernées. La commande doit savoir combien seront affectés. Si c'est autre chose que ce #, il devrait revenir en arrière et lever une exception car quelque chose de bizarre vient de se produire. Ainsi, il s'agit d'informations que l'appelant ne se soucie pas vraiment (sauf si l'utilisateur a besoin de connaître le vrai # et que vous ne pouvez pas le dire à partir des données fournies). Il y a encore des zones grises comme les ID générés par DB, les piles / files d'attente où l'obtention des données change son état, mais j'aime créer une "CommandQuery" dans ces scénarios, donc personne n'essaie de faire quelque chose de "bizarre".
Daniel Lorenz
3

Quel que soit le chemin, vous allez marcher avec ça. Ne contient pas de valeurs de retour pour une utilisation future (par exemple, les fonctions retournent toujours true), c'est un gaspillage d'effort terrible et ne rend pas le code plus clair à lire. Lorsque vous avez une valeur de retour, vous devez toujours l'utiliser, et pour pouvoir l'utiliser, vous devez avoir au moins 2 valeurs de retour possibles.

refro
la source
+1 Cela ne répond pas à la question, mais ce sont certainement des informations correctes. Lors de la prise de décision, la décision doit être basée sur un besoin. Si les appelants ne trouvent pas les données de retour utiles, cela ne devrait pas être fait. Et personne ne trouve utile de renvoyer 100% du temps pour une "épreuve future". Certes, si "l'avenir" est comme ... dans quelques jours et qu'il y a une tâche à accomplir, c'est une autre histoire, lol.
Suamere
1

J'avais l'habitude d'écrire beaucoup de fonctions nulles. Mais depuis que j'ai commencé toute la méthode à enchaîner le crack, j'ai commencé à retourner cela plutôt que le vide - autant laisser quelqu'un profiter du retour parce que vous ne pouvez pas faire de squat avec le vide. Et s'ils ne veulent rien en faire, ils peuvent simplement l'ignorer.

Wyatt Barnett
la source
1
Le chaînage des méthodes peut cependant rendre l'héritage lourd et difficile. Je ne ferais pas de chaînage de méthode à moins que vous n'ayez une bonne raison de le faire autre que de ne pas vouloir "annuler" vos méthodes.
KallDrexx
Vrai. Mais l'héritage peut être lourd et difficile à utiliser.
Wyatt Barnett
En regardant un code inconnu qui retourne quoi que ce soit (mentionné tout l'objet) fait très attention, il suffit de vérifier le flux à l'intérieur et à l'extérieur des méthodes ..
Indépendant
Je suppose que tu ne t'es pas embarqué Rubyà un moment donné? C'est ce qui m'a initié au chaînage de méthodes.
KChaloux
Un des inconvénients que j'ai avec le chaînage de méthode est qu'il indique moins clairement si une déclaration comme celle return Thing.Change1().Change2();-ci s'attend à changer Thinget à renvoyer une référence à l'original, ou à retourner une référence à une nouvelle chose qui diffère de l'original, tout en laissant le d'origine intacte.
supercat