Est-ce une mauvaise pratique de codage de créer quelque chose dans un get s'il n'existe pas?

18

J'ai donc un webservice qui a quelque chose comme un getAccountoù il retournerait un identifiant au compte s'il l'obtenait, sinon lève une exception. Le client voudra toujours créer un compte si une exception est levée avec les mêmes informations que le get est fait.

Je crée une bibliothèque de commodité pour les clients qui traiteront tous les appels de service Web à l'intérieur afin qu'ils n'aient pas besoin de savoir comment faire les appels eux-mêmes.

Ce que je me demande, c'est dans cette bibliothèque si je devais créer un getAccount(accountName)qui obtiendra le compte s'il existe, et s'il ne le crée pas et ne renvoie pas les informations, est-ce une mauvaise chose à faire? Dois-je laisser au client le soin de gérer les exceptions ou simplement le nommer quelque chose comme getOrCreateAccount? Est-ce que ça importe?

Est-ce une mauvaise pratique de créer quelque chose dans une opération get?

Mike
la source
9
À tout le moins, je l'appellerais getOrCreateAccountou similaire.
Telastyn
4
Cela semble valable dans le contexte d'une initialisation paresseuse. Cependant, cela dépend si vous avez besoin d'utiliser ce modèle dans votre bibliothèque.
Chris C
1
J'aime le verbe acquire, comme acquireAccount. Il n'a pas de sens existant dans les principaux protocoles que j'ai rencontrés, et il a un anneau impératif qui lui convient bien. "Faites tout ce que vous avez à faire pour en acquérir un pour moi. Demandez-le, construisez-le, truquez-le, volez-le, je m'en fiche, obtenez-moi un ou mourez en essayant."
Dan Ross
2
"Le client voudra toujours créer un compte" - cela semble très inhabituel. Si je ne parviens pas à obtenir le compte parce que l'utilisateur a mal saisi son nom d'utilisateur, je ne veux certainement pas créer un compte avec le nom mal saisi.
gnasher729
Selon la spécification javabean oracle.com/technetwork/articles/javaee/spec-136004.html getSomething() est pour les getters et setSomething()pour les setters. Imo tout ce qui ne doit être appelé plus intellectuel quelque autre chose, à savoir fetchSomething, obtainSomething, computeSomethingou doSomethingElseetc.
ccpizza

Réponses:

31

Oui, c'est important. À mon avis, c'est généralement une mauvaise pratique de créer quelque chose dans une procédure qui n'est pas documentée comme ayant les pouvoirs de création. Soit le nom , la procédure getOrCreate...ou une distincte create...procédure et si vous vraiment voulez, avoir une getOrCreate...que premières tentatives get..., et si cela échoue, les appels create...et les appels get....

L'utilisateur de la bibliothèque ne s'attendra probablement pas à ce que la get...procédure soit créée si l'opération get échoue. S'ils découvrent soudain que leur test appelle à get...créer une tonne de données, ils seront probablement plutôt surpris. Et comment le nettoient-ils? Et s'ils écrivent du code en pensant qu'ils obtiendront une erreur en cas d' get...échec et qu'ils veulent gérer cela à leur façon?

FrustratedWithFormsDesigner
la source
Merci, le create retourne en fait l'identifiant que le get retournerait aussi donc je n'aurais pas à faire get... create... get...juste les deux premiers. Je vais parler au client pour savoir s'il aura besoin de pouvoir simplement appeler un getsans vouloir créer
Mike
6
@Mike: Je pense toujours que cela devrait avoir createdans le nom, juste pour être clair à 100% sur ce qui se passe.
FrustratedWithFormsDesigner
Oui, je suis d'accord, je prévoyais d'en faire quelque chose dans le sens de getOrCreate parce que ma pensée originale était juste de faire, mais j'ai réalisé qu'il n'est pas immédiatement clair qu'il crée également quelque chose en arrière
Mike
1
C'est super tard mais getOrCreatea la priorité dans un framework web populaire: docs.djangoproject.com/en/1.10/ref/models/querysets/…
tex
18

Non, ce n'est pas une «mauvaise pratique». Tant que vous et les autres développeurs convenez de la façon dont vous voulez que cela fonctionne, c'est très bien. Après tout, ce serait retourner un compte, ce que vous voulez. Le fait que le compte soit créé «sous le capot» n'est pas pertinent pour l'appelant.

GrandmasterB
la source
10
La seule exception est s'il s'agit d'une API accessible au public. La communauté plus large a déjà convenu que l'EEG est indempotent et nullipotent; en d'autres termes, la même action est effectuée à chaque fois, et c'est une méthode sûre qui ne change pas l'état du serveur. C'est dans le Wiki REST . En dehors de cela, si l'API existe simplement dans votre propre petit monde, faites tout ce qui est accepté par votre groupe.
jmort253
9
Je ne suis pas d'accord, même pour une API publique, c'est bien - tant que cela a du sens dans le contexte de ce que l'API est censée faire. Il ne sert à rien de créer plusieurs entrées dans l'API alors qu'une seule ferait l'affaire.
GrandmasterB
Pour mémoire, il s'agit d'une bibliothèque entièrement interne et j'ai la possibilité de dire à l'équipe qui l'utilise comment cela fonctionne
Mike
1
@ jmort253: Un service est nullipotent s'il n'a aucun effet secondaire visible pour le client . Le serveur peut faire ce qu'il veut.
kevin cline
1
@kevincline, je suppose que j'y pensais en termes, disons, de débiter ma carte de crédit. Si le serveur charge ma carte sur la base d'une demande GET mais me donne toujours un résultat comme "refusé" à chaque fois que je l'exécute, c'est comme si cela allait à l'encontre de l'esprit de GET. Cela dit, je vois votre point. Le serveur pouvait compter le nombre de demandes GET et me verrouiller après 3 requêtes, limitant ainsi le taux, mais sans modifier aucun détail de mon compte. Je pense que c'est une distinction importante quand on dit que l'état du serveur n'est pas modifié. Je devrais peut-être plutôt dire «état du client sur le serveur»
jmort253
10

Si getAccount()peut toujours retourner un compte, du point de vue de l'appelant, le compte existe et a toujours existé. Il n'est pas nécessaire getAccount()de «créer» quoi que ce soit. Le compte n'a pas besoin d'être stocké n'importe où jusqu'à ce qu'il soit différent du compte par défaut.

Kevin Cline
la source
+1 Généralement, GetOrCreatec'est la mauvaise sémantique, mais obtenir un objet qui peut "logiquement" exister, qu'il existe ou non phyiscalement, c'est bien. Par exemple, un tableau clairsemé d'éléments mutables peut ne pas avoir de stockage alloué pour l'élément 1 841 533, mais permet toujours à cet élément d'être "récupéré" en créant un nouvel objet, en le stockant et en renvoyant une référence.
supercat
4

Il est plus logique de créer 3 méthodes:

getAccount -> Qui obtient simplement le compte.

createAccount -> Crée un compte.

getAccountAndCreateIfNeeded -> Choisissez votre propre nom;)

Pourquoi la séparation: Vous avez une méthode simple pour obtenir et créer. C'est une méthode claire et testable pour les deux. Pour getAccount, ce n'est pas une exception de ne pas trouver le compte. Il suffit donc de retourner false ou quelque chose comme ça, c'est prévu.

Ensuite, vous pouvez utiliser cette valeur de retour dans votre fonction groupée: getAccountAndCreateIfNeeded qui est désormais également testable, elle devrait toujours renvoyer un compte. Tout ce que vous demandez.

Toutes ces 3 méthodes sont claires, il est exactement clair ce qu'elles font et ce qu'elles retournent. Vous pouvez maintenant conclure des accords avec votre équipe, mais ce type d'exceptions est terrible à long terme. Rendez-les très clairs et vous n'aurez aucun problème.

Luc Franken
la source
En outre, à partir de Clean Code: "Si votre méthode ne peut pas faire ce que son nom l'indique, lancez une exception." J'aurais un bloc Try / Catch à l'intérieur de 'GetOrCreateIfneeded' qui lance un GET échoué, puis a le 'createAccount' à l'intérieur du Throw pour tout type d'exception qui revient pour le get échoué.
Graham
Je pourrais suggérer une quatrième méthode:, getAccountIfExistsqui obtiendrait un compte ou indiquerait qu'il n'existe pas sans en créer un nouveau. La getAccountméthode elle-même doit présupposer que le compte existe et lever une exception sinon.
supercat
2

Cela dépend des circonstances.

Par exemple, vous pouvez l'utiliser pour effectuer un chargement / une instanciation paresseux, en différant le chargement des données ou la création d'une instance jusqu'à ce que cela soit réellement nécessaire. Ceci est normalement judicieux car il économise des ressources dont vous pourriez ne pas avoir besoin (si la classe / données n'est jamais nécessaire, elle n'est jamais chargée).

Cependant, dans ce cas particulier, je dirais qu'avoir une méthode appelée getAccount qui créerait un nouveau compte s'il n'existait pas ne serait pas une bonne pratique. Si l'utilisateur a donné des informations d'identification pour identifier un compte particulier et que ce compte est introuvable, cela signifie-t-il que l'utilisateur n'est pas encore un client et devrait avoir un compte créé pour lui, ou cela signifie-t-il que les informations d'identification ont été mal saisies et l'utilisateur doit être invité à vérifier qu'il a saisi ce qu'il voulait saisir?

Si vous avez une méthode getAccount qui crée un nouveau compte si elle n'a pas pu en identifier un, vous n'avez pas le choix. Si vous divisez la création de compte et la sortie de compte dans des méthodes distinctes, vous avez beaucoup plus de flexibilité pour décider quoi faire si une tentative d'obtention d'un compte échoue.

GordonM
la source