Terme (ou "motif"?) Pour "faire quelque chose si ce n'est déjà fait" [fermé]

54

Cela paraît assez basique, je le sais, mais un collègue m'a récemment dit qu'une méthode appelée startHttpServerest trop compliquée à comprendre, car elle ne démarre le serveur que si elle n'est pas déjà en cours d'exécution. Je trouve que j'ai des problèmes lorsque je réponds avec "Sérieusement? Je le fais depuis des décennies - c'est un modèle courant en programmation." Plus souvent que je ne tiens à l'admettre, il revient avec des preuves documentées qui montrent que toute la communauté de la programmation est en retard sur son point de vue et que je finis par me sentir penaud.

Question : Existe-t-il un modèle de conception documenté derrière le concept d'une méthode qui est une non-opération si l'action requise est déjà effective? Ou, si ce n'est un motif, a-t-il un nom non plus? Et si non, y a-t-il une raison de penser que c'est trop compliqué d'envisager l'écriture d'une méthode de cette manière?

John Calcote
la source
6
Cela ressemble à de la mise en cache - et cela est souvent considéré comme compliqué (voir aussi Comment puis-je expliquer $ {quelque chose} à $ {quelqu'un}? )
gnat
8
Vous avez 3 réponses différentes, mais la meilleure solution serait de les appliquer toutes: renommez la fonction d'origine, divisez son code en deux fonctions (l'une des deux maintenant désignée par son nom startHttpServer), et oui, le terme "idempotent" s'applique ici bien.
Doc Brown
8
Quelle sorte de preuve contraire votre collègue fournit-il? Pouvez-vous fournir un lien vers celui-ci?
Robert Harvey
5
Je suis curieux de voir d'où votre collègue tire ses citations. Au moins autour de Stack Overflow et de Software Engineering, cette fonction serait tout au plus mal nommée mais n’aurait aucun comportement inhabituel. Gardez à l'esprit que ce n'est pas parce que quelqu'un, quelque part, a mis sur un blog quelque chose qui représente toute la vision de la communauté de programmation. Même les grands noms comme Martin Fowler disent de temps en temps des choses très étranges. Nous sommes tous juste des humains.
T. Sar - Réintégrer Monica
4
Je pense qu'une meilleure façon de penser à "Faire quelque chose si ce n'est pas déjà fait" serait "Mettez le système dans cet état". Bien sûr, si le système est déjà dans cet état, la méthode ne ferait rien - ce qui serait le comportement attendu.
T. Sar - Réintégrer Monica

Réponses:

127

Comme NickWilliams l' a déjà dit : le concept décrit par l'OP s'appelle idempotent (nom Idempotency ). C'est en effet une pratique courante, en particulier dans les API de haut niveau.

MAIS: Renommez la fonction.

Au lieu de l' startHttpServerappeler makeSureHttpServerIsRunningou ensureHttpServerIsRunning.

Lorsqu'une fonction est appelée startHttpServer, les lecteurs s'attendent à ce qu'elle lance un serveur HTTP. Lorsque je serai appelé dix fois de suite, je ferai fonctionner dix serveurs. Votre fonction ne le fait pas la plupart du temps. De plus, le nom avec "start" suggère que si je ne souhaite utiliser qu'un seul serveur, je devrai savoir si la fonction a déjà été appelée ou non.

Quand une fonction est appelée makeSureHttpServerIsRunning, je suppose qu’elle fera les choses nécessaires pour s’assurer qu’un serveur HTTP est en cours d’exécution, le plus souvent en vérifiant s’il est déjà en cours d’exécution et en le démarrant sinon. Je suppose également que la fonction permet de s’assurer que le serveur est en cours d’exécution (le démarrage d’un serveur peut impliquer un certain temps où il n’est pas encore en cours d’exécution).

gnasher729
la source
83
Cette. Personnellement, j'utilise "Ensure" à la place. Le fait est que cela devrait devenir un schéma de nommage compris par votre équipe.
Euphoric
3
ou bien start lance une exception s’il est déjà commencé. comme sqlconnection.open
Ewan
9
J'utilise toujours le nom "assurer" pour la fonction "faire si pas déjà".
Kaz
3
Un autre nom que je vois souvent est getOrCreateSomething, qui crée uniquement au premier appel et qui retourne simplement quelque chose
Fabich
5
@aroth Êtes-vous sûr de ne pas vous attendre à ce qu'il tente de trouver un port disponible et le restitue? Ou juste être mal écrit? ;)
jpmc26
33

Renommez le en EnsureServerRunning.

Complètement sans ambiguïté et il est clair qu’il s’assure qu’il est en cours d’exécution (si ce n’est pas le cas) sans impliquer de redémarrage, le cas échéant.

(Alternative StartServerIfNotRunning:?)

Stilez
la source
1
La plupart des appelants se soucient simplement que le serveur fonctionne après l'appel. Comment cela est réalisé, ils s'en moquent.
gnasher729
29

Ce n'est pas vraiment un motif, mais j'appellerais votre méthode idempotente . Ce terme est généralement utilisé pour désigner les appels distants, mais la description semble correspondre à ce que vous faites.

Méthodes idempotentes. Les méthodes peuvent également avoir la propriété "idempotence" en ce sens que (mis à part les problèmes d'erreur ou d'expiration), les effets secondaires de N> 0 requêtes identiques sont les mêmes que pour une requête unique. ( De W3.org )

L'effet côté serveur étant que le serveur http est démarré une fois la méthode appelée. Je ne vois rien de mal à ce qu'une méthode le fasse.

Si vous avez besoin d’un modèle, j’imagine que vous pouvez exposer votre httpServer en tant que singleton qui est lancé à l’initialisation.

Nick Williams
la source
5
La fonction n'est pas idempotente si l'appeler une fois démarre le serveur http, et l'appeler une seconde fois ne le fait pas. C’est littéralement le contraire de l’idempotence. Il serait idempotent que chaque appel lance un nouveau serveur http.
Polygnome
36
@Polygnome Wikipedia définit idempotence comme étant f (f (x)) = f (x), ce qui correspond généralement à la description de Nick. Ici, l'entrée et la sortie de la fonction correspondraient à l'état implicite du serveur, de sorte que l'état «le serveur est en cours d'exécution» serait un point fixe pour cette fonction. Peut-être que je comprends mal l'article de Wikipedia ici, pourriez-vous créer un lien vers d'autres références?
amon
16
@Polygnome: cette réponse est correcte, idempotency fait référence à des fonctions pour lesquelles il importe peu qu'elles soient appelées une ou plusieurs fois, le résultat reste toujours le même. Ici, le résultat est un service http en cours d'exécution, indépendamment du nombre d'appels de la fonction.
Doc Brown
14
La confusion vient du fait que l’idempotence est un concept mathématique appliqué aux fonctions. La définition est simple et agréable pour ceux-ci et fonctionne également bien pour les langages fonctionnels purs. Dès que vous introduisez des effets secondaires, cela devient compliqué - vous devez considérer l'environnement dans lequel vous exécutez la fonction en tant qu'argument (implicite) et en tant que sortie de la fonction. Si cet état n'est pas modifié par d'autres appels à la fonction, il est idempotent, sinon ce n'est pas le cas. Dans ce cas, l'état pertinent est "le serveur http est-il en cours d'exécution" - la fonction est donc idempotente.
Voo
10
@ Polygnome Désolé, vous avez tort. Idempotent signifie que la demande a le même effet, qu'elle soit exécutée une fois ou plus d'une fois. Démarrer N serveurs http si N copies de la requête sont reçues n’est certainement pas idempotent.
Kaz
7

Comme celui qui mettent en œuvre cet outil , startHttpServervous devriez essayer de faire le plus simple, lisse et transparente à l' utilisation ...

La logique de la fonction

Techniquement, en scindant startHttpServerla logique en 2 fonctions et en les appelant séparément , tout ce que vous faites est de déplacer startHttpServer l’ idempotency dans le code en appelant les deux fonctions à la place ... startHttpServeren premier lieu), cela vous oblige à écrire du code unDRYed, en le dupliquant de manière exponentielle partout où vous auriez à appeler startHttpServer. En bref, startHttpServer doit s'appeler elle-même la isHttpServerRunningfonction.

Donc, mon point est:

  • Implémente la isHttpServerRunningfonction parce que cela peut être nécessaire indépendamment de toute façon ...
  • Implémentez-le en le startHttpServerfaisant utiliser isHttpServerRunningpour définir sa prochaine action en conséquence ...

Cependant, vous pouvez toujours startHttpServerrenvoyer toute valeur dont l'utilisateur de cette fonction peut avoir besoin, par exemple:

  • 0 => échec de démarrage du serveur
  • 1 => succès de démarrage du serveur
  • 2 => le serveur a déjà été démarré

Le nom de la fonction

Tout d’abord, quel est le but principal de l’utilisateur? Pour démarrer le serveur HTTP , non?

Fondamentalement, il n'y a pas de problème en envisageant de commencer quelque chose qui a déjà été commencé, AKA 1*1=1. Donc, du moins pour moi, l'appeler " ensureHttpServerIsRunning" ne semble pas être absolument nécessaire, je me soucierais davantage de la longueur, du naturel et du mémorable nom de la fonction.

Maintenant, si vous voulez savoir comment fonctionne la fonction sous le capot en détail, il y a la documentation ou le code source pour cela, je veux dire comme pour toute autre fonction de la bibliothèque / framework / API / etc ...

Vous apprenez la fonction une fois pendant que vous l' écrivez plusieurs fois ...

De toute façon, je m'en tiendrai à startHttpServerce qui est plus court, plus simple et plus explicite que ensureHttpServerIsRunning.

ClemC
la source
1
D'autant plus que la méthode doit prendre certains arguments de configuration, tels que le répertoire racine, les paramètres de sécurité, éventuellement un numéro de port, je suis à 100% à l'aise avec "démarrer" ici.
user949300
@ user949300: L'appelant de "EnsureHttpServerIsRunning" ne se soucie pas de la configuration, du répertoire racine, etc. C'est l'affaire de la personne qui l'implémente.
gnasher729
2
@ gnasher729 Cela suppose que le serveur http est un Singleton. Les singletons sont diaboliques. Et peut-être inapproprié ici. On pourrait facilement avoir plusieurs serveurs sur plusieurs ports. S'il n'y a qu'un seul serveur http, cette méthode complète est, IMO, mauvaise conception Il est préférable de simplement démarrer le serveur une fois à l'initialisation du programme.
user949300
2

Je suppose que votre collègue voulait dire que cela en startHttpServerfaisait trop:

  • Vérifier si le serveur est déjà en cours d'exécution,
  • Démarrer le serveur si nécessaire.

Ce sont deux parties de code non liées. Par exemple, une situation similaire se présente lorsqu'une application de bureau doit s'assurer qu'elle n'est pas déjà en cours d'exécution lorsqu'elle est lancée. une partie du code gère les instances d'application (par exemple, à l'aide d'un mutex) et le code qui lance la boucle de message d'application.

Cela signifie que vous ne devez pas avoir une, mais au moins une, deux méthodes :

  • isHttpServerRunning: boolean
  • startHttpServer

Le point d’entrée de l’application appelle la première méthode, puis la seconde si la valeur renvoyée est false. Désormais, chaque méthode fait une chose et est facile à comprendre.


¹ Si la logique nécessaire pour savoir si le serveur est déjà en cours d’exécution est trop complexe, une séparation supplémentaire en plusieurs méthodes peut être nécessaire.

Arseni Mourzenko
la source
21
Désormais, un autre appel des deux fonctions consiste à faire deux choses. De plus, exposer les fonctions séparément peut créer des conditions de concurrence. Pas de repas gratuit.
Whatsisname
1
Si c'est trop pour le collègue, bonne chance.
gnasher729
@ gnasher729: cette réponse ne contredit pas la vôtre - bien au contraire, il serait peut-être judicieux de combiner le changement de nom de la méthode avec sa division en deux. Et tant que nous ne voyons pas le code réel, nous ne savons pas à quel point il est complexe.
Doc Brown
10
Cette réponse semble opposer abstraction et DRY. Et si startHttpServeron appelle plus d'une place dans le code? Est-ce que plusieurs lignes similaires doivent être copiées-collées partout? Cela devrait-il être fait avec toutes les fonctions? Bientôt, votre programme aura une taille infinie.
JacquesB
3
Je pense que le premier effet secondaire de cette approche est que le début de la startHttpServerméthode ressemblera à peu près if (isHttpServerRunning()){ return; }. Vous affirmez une règle de gestion selon laquelle "il n'est pas correct de démarrer le serveur http s'il est déjà en cours d'exécution", mais vous obligez alors une autre personne à appliquer cette règle. Ad-hoc et à plusieurs reprises à chaque endroit où ils pourraient appeler startHttpServer.
aroth
2

Puisque vous ne spécifiez pas de langue, de nombreuses bibliothèques en JavaScript ont une fonction "une fois", par exemple, Underscore . Donc, si cela vous est familier, appelez-le un motif "une fois" et renommez éventuellement votre méthode.

Moi-même, venant davantage de Java, je pense aux termes "mise en cache" ou "évaluation paresseuse". "Idempotent" est techniquement correct et constitue un bon choix, en particulier. si vous avez un fond plus fonctionnel.

utilisateur949300
la source
Ce n'est peut-être pas "une fois", si le serveur peut être arrêté.
gnasher729
@ gnasher729. Je pourrais imaginer une restartHttpServer()méthode rarement utilisée . Mais juste arrêter - quel est le cas d'utilisation ici? Vous aimez les échecs de connexion sporadiques? :-)
user949300
Il n'est pas nécessaire qu'un cas d'utilisation se limite à arrêter le serveur HTTP, car celui-ci a peut-être été arrêté par un élément externe au programme lui-même: le serveur HTTP peut s'être égaré et être mort, un administrateur peut l'avoir arrêté manuellement pour une raison quelconque, etc.
Dave Sherohman
Dave, un crash est "exceptionnel" et doit être traité comme tel. De plus, étant donné qu'un crash peut survenir à tout moment , chaque ligne de votre code ne devrait-elle pas l'être ensureRunning()? :-) En ce qui concerne l’arrêt de l’administrateur, le fait de redémarrer constamment cet autre code pendant que l’administrateur essaie de modifier ou de corriger quelque chose serait incroyablement ennuyant et erroné. Laissez l'administrateur le redémarrer, pas le code.
user949300
-2

Je préférerais startHttpServerIfNotIsRunning.

De cette façon, la condition est clairement mentionnée déjà dans le nom de la méthode. Ensureou makeSureme semble un peu vague, car ce n’est pas une expression technique. On dirait que nous ne savons pas exactement ce qui va se passer.

Herr Derb
la source
Je suppose que vous n'êtes pas un locuteur natif? Parce qu'assurer a la définition exacte de ce que nous allons faire. Néanmoins, il est toujours juste que la documentation et les API ne nécessitent pas que les niveaux de locuteur anglais soient compréhensibles.
Voo
1
Merci je pas exactement ce que Ensuresignifie. Je n'aime toujours pas ce mot pour une expression technique. Ensureest quelque chose d'humain. Un système ne peut rien garantir, il ne fera que ce qu'il est censé faire.
Herr Derb
2
@Voo - Je suis un locuteur natif et je ne suis pas sûr de ce que signifie assurer.
Jonathan Cast
Puisque toutes les personnes postées ici ont accès à Internet, pourquoi ne rechercheraient-elles pas la définition de «Ensure» si elles ne savaient pas exactement ce que cela signifiait? Un peu de trivialité ... beaucoup de gens échangent l'utilisation de 'Ensure' et 'Assure' sans se rendre compte que l'utilisation de l'un de ces mots pourrait les rendre «légalement» responsables, alors que l'autre ne le ferait pas.
Dunk
@jcast: Sérieusement?
gnasher729
-3

Ce que votre collègue aurait dû vous dire, c'est que vous n'avez pas à écrire cette méthode. Il a déjà été écrit maintes fois et mieux que vous ne l'auriez probablement écrit. Par exemple: http://docs.ansible.com/ansible/latest/systemd_module.html https://docs.saltstack.com/en/latest/ref/states/all/salt.states.service.html

D'un point de vue architectural, la gestion d'un serveur Web par un morceau de code arbitraire est un véritable cauchemar. Sauf si la gestion des services est exclusivement ce que fait votre code. Mais je suppose que vous n'avez pas écrit monit (ou kubernetes ou ...).

Andrew
la source
2
Le langage utilisé est Java et la méthode a été conçue pour démarrer un serveur intégré grizzly dans une application Jersey autonome. Je ne peux pas critiquer l'exactitude de votre commentaire, étant donné que je ne vous ai pas donné beaucoup de contexte, mais vous vous êtes bien inspiré de votre déclaration. C'est parfaitement correct d'avoir une méthode qui appelle une jetée ou un grizzly pour démarrer le serveur.
John Calcote
1
Des millions d'applications métier incluent un serveur HTTP auto-hébergé pour fournir une API. Et tous ceux-ci auront besoin d'un code très simple qui configure réellement la bibliothèque qu'ils utilisent et fournit des informations telles que l'interface sur laquelle se connecter, le port à utiliser, les paramètres de sécurité, etc. Avoir une startServerfonction ou similaire est tout à fait inhabituel . Cela ne signifie pas que vous écrirez les détails de bas niveau.
Voo