2010-06-28 9 views
23

Lors de l'écriture d'une API ou d'un objet réutilisable, y a-t-il une raison technique pour laquelle tous les appels de méthode qui renvoient 'void' ne devraient pas simplement retourner 'this' (* ceci en C++)?Pourquoi une API doit-elle renvoyer "void"?

Par exemple, en utilisant la classe de chaîne, nous pouvons faire ce genre de chose:

string input= ...; 
string.Join(input.TrimStart().TrimEnd().Split("|"), "-"); 

mais nous ne pouvons pas le faire:

string.Join(input.TrimStart().TrimEnd().Split("|").Reverse(), "-"); 

..car Array.Reverse() retourne nul.

Il y a beaucoup d'autres exemples où une API a beaucoup d'opérations de vide-retour, de sorte que le code finit par ressembler à:

api.Method1(); 
api.Method2(); 
api.Method3(); 

..mais il serait parfaitement possible d'écrire:

api.Method1().Method2().Method3() 

..si le concepteur de l'API l'avait autorisé.

Y a-t-il une raison technique pour suivre cette voie? Ou est-ce juste une chose de style, pour indiquer la mutabilité/nouvel objet?

(x-ref Stylistic question concerning returning void)


ÉPILOGUE

J'ai accepté la réponse de Luvieere que je pense que cela représente le mieux l'intention/conception, mais il semble qu'il ya populaires exemples de l'API sur là qui dévient de ceci:

En C++ cout << setprecision(..) << number << setwidth(..) << othernumber; semble modifier l'objet cout afin de modifier la donnée suivante insérée.

Dans .NET, Stack.Pop() et Queue.Dequeue(), les deux retournent un élément, mais modifient également la collection.

Propose à ChrisW et à d'autres pour obtenir des détails sur les coûts de performance réels.

+1

Le motif que vous décrivez est également connu sous le nom * method chaining * (http://martinfowler.com/dslwip/MethodChaining.html). –

+0

Méthode de chaînage, ou interfaces fluides (bien que les puristes prétendent que quelque chose de plus est nécessaire pour ce dernier, je crois!). –

+0

Ahhh - ça a un nom! Il semble tout à fait lisible, et populaire dans des choses comme JQuery – JBRWilkinson

Répondre

26

Les méthodes qui retournent l'état vide indiquent plus clairement qu'elles ont des effets secondaires. Ceux qui retournent le résultat modifié sont supposés n'avoir aucun effet secondaire, y compris la modification de l'entrée d'origine. Rendre une méthode return void implique qu'elle change son entrée ou un autre état interne de l'API.

+0

En C++, il existe d'autres façons d'exprimer ces fonctions membres-const, par exemple ..? – JBRWilkinson

+0

@JBRWilkinson La portée de la question est plus large que C++, elle a aussi une balise C#. Ce que j'ai dit était censé être une pratique d'API agnostique. – luvieere

+0

Bon - point pris. – JBRWilkinson

19

Si vous aviez un retour Reverse()string, alors il ne serait pas évident pour un utilisateur de l'API si elle retourne une nouvelle chaîne ou même un, inversé en place.

string my_string = "hello"; 
string your_string = my_string.reverse(); // is my_string reversed or not? 

C'est pourquoi, par exemple, en Python, list.sort() retours None; il distingue le tri sur place de sorted(my_list).

5

Y a-t-il une raison technique pour suivre cette voie?

L'une des directives de conception C++ est «ne payez pas pour les fonctionnalités que vous n'utilisez pas»; retourner this aurait une (légère) pénalité de performance, pour une fonctionnalité que beaucoup de gens (pour ma part) ne seraient pas enclins à utiliser.

+0

Quelle est la pénalité de performance en C++? Le pointeur sur la pile n'est-il pas de toute façon? – JBRWilkinson

+2

JBRWilkinson Partout où ce pointeur est, il doit être le pointeur this du processus appelant au moment où il retourne au processus appelant. En fait, en utilisant MSVC, le pointeur 'this' est dans le registre' ecx', et le code de retour est dans le registre 'eax': donc la pénalité de performance serait une instruction supplémentaire' mov ecx, eax' à la fin du/chaque sous-programme appelé. – ChrisW

+2

@JBRWilkinson L'opcode supplémentaire peut être optimisé lorsqu'il n'est pas utilisé, uniquement lorsque la routine appelée sait d'où elle est appelée, c'est-à-dire lorsqu'elle est inline (mais tout ce qui est en incrustation aurait ses propres problèmes de performances). – ChrisW

0

Outre les raisons de conception, il existe également un léger coût de performance (à la fois en termes de vitesse et d'espace) pour renvoyer cette valeur.

+0

Pouvez-vous donner quelques détails? – JBRWilkinson

+1

ChrisW a fourni d'excellents commentaires dans sa réponse. Chaque langue/plate-forme devrait être assez similaire à cet égard. Par exemple, dans C#/.NET, le compilateur devra ajouter une instruction de retour IL à l'asssembly et le compilateur JIT devrait le convertir dans la plupart des cas en une instruction 'mov eax, ecx' comme le compilateur C++ dans son exemple. –

4

Le principal technique que beaucoup d'autres ont mentionné (que void souligne le fait que la fonction a un effet secondaire) est connu comme Command-Query Separation.

Bien qu'il y ait des avantages et des inconvénients à ce principe, par exemple, l'intention (subjectivement) plus claire par rapport à plus API concise, la partie la plus importante est de être cohérent.

+0

L'article Wiki semble un peu boueux.Il est tout à fait normal que les commandes renvoient une indication indiquant si quelque chose d'inattendu s'est produit. Quelque chose comme la fonction incrementAndReturnValue se comporte comme une commande, mais renvoie une valeur dans le but de permettre à l'appelant de savoir ce qui s'est passé. On pourrait passer une valeur "attendue" et faire en sorte que la fonction retourne un booléen indiquant si les choses étaient "comme prévu", mais il est plus facile pour l'appelant de faire la comparaison lui-même. – supercat

4

J'imagine que l'une des raisons pourrait être la simplicité. Tout simplement, une API devrait généralement être aussi minimale que possible. Il devrait être clair avec tous les aspects, à quoi cela sert.

Si je vois une fonction qui renvoie void, je sais que le type de retour n'est pas important. Quelle que soit la fonction, cela ne me rapporte rien.

Si une fonction renvoie quelque chose non void, je dois arrêter et me demander pourquoi. Quel est cet objet qui pourrait être retourné? Pourquoi est-il retourné? Puis-je supposer que this est toujours retourné, ou sera-t-il parfois nul? Ou un objet entièrement différent? Etc.

Dans une API tierce, je préférerais que ce genre de questions ne se pose jamais.

Si la fonction n'a pas besoin de pour retourner quelque chose, il ne devrait rien retourner.

+3

+1. Il serait assez facile d'écrire un wrapper enchaînant les méthodes si une syntaxe pratique est souhaitée. Je préférerais que pessimiser toutes sortes de fonctions en renvoyant ceci/* ceci quand cela n'est pas nécessaire au profit d'un raccourci stylistique discutable. – stinky472

3

Si vous avez l'intention de votre API pour être appelé de F #, s'il vous plaît faire retour void sauf si vous êtes convaincu que ce particulier appel de méthode va être enchaîné avec un autre presque chaque fois qu'il est utilisé.

Si vous ne vous souciez pas de rendre votre API facile à utiliser à partir de F #, vous pouvez arrêter de lire ici. F # est plus strict que C# dans certains domaines - il veut que vous soyez explicite quant à savoir si vous appelez une méthode pour obtenir une valeur, ou uniquement pour ses effets secondaires. Par conséquent, appeler une méthode pour ses effets secondaires lorsque cette méthode renvoie également une valeur devient gênant, car la valeur renvoyée doit être explicitement ignorée afin d'éviter les erreurs du compilateur. Cela rend les «interfaces fluides» quelque peu difficiles à utiliser à partir de F #, qui a sa propre syntaxe supérieure pour enchaîner une série d'appels ensemble.

Par exemple, supposons que nous avons un système d'enregistrement avec une méthode Log qui retourne this pour permettre une sorte de chainage des méthodes:

let add x y = 
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) // #1 
    x + y             // #2 

En F #, parce que la ligne n ° 1 est un appel de méthode qui retourne une valeur, et nous ne faisons rien avec cette valeur, la fonction add est considérée comme prenant deux valeurs et renvoie cette instance Logger.Cependant, la ligne n ° 2 non seulement renvoie également une valeur, elle apparaît après ce que F # considère comme l'instruction "return" pour la fonction, faisant effectivement deux déclarations "return". Cela provoquera une erreur de compilation, et nous devons ignorer explicitement la valeur de retour de la méthode Log pour éviter cette erreur, de sorte que notre méthode add n'a qu'une seule instruction de retour.

let add x y = 
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) |> ignore 
    x + y 

Comme vous pouvez le deviner, faire beaucoup d'appels « API fluent » qui sont principalement des effets secondaires devient un exercice quelque peu frustrant aspergeant beaucoup de déclarations ignore dans tous les sens. Vous pouvez, bien sûr, avoir le meilleur des deux mondes et satisfaire les développeurs C# et F # en fournissant à la fois une API fluide et un module F # pour travailler avec votre code. Mais si vous n'allez pas faire cela, et vous avez l'intention de votre API pour la consommation publique, s'il vous plaît réfléchir à deux fois avant de retourner this à partir de chaque méthode unique.

+1

Intéressant de voir comment les autres langues dépendent de ce modèle. Merci pour la bonne réponse! – JBRWilkinson

+0

Ceci me rappelle les "Rhinos et Tigres" de Yegge, qui discutent (entre autres choses) si et comment les VM peuvent supporter l'appel entre plusieurs langues. "Et ils sont comme (agitant les mains)" Ooooh, nous allons passer dessus, passer dessus, lisser. " Et la réponse est: «Vous ne pouvez pas, c'est fondamental, ces langues fonctionnent différemment! – Ken

Questions connexes