Faut-il toujours savoir ce que fait une API simplement en regardant le code?


20

Récemment, j'ai développé ma propre API et avec cet intérêt investi dans la conception d'API, je me suis vivement intéressé à la façon d'améliorer ma conception d'API.

Un aspect qui a été soulevé à plusieurs reprises est (non pas par les utilisateurs de mon API mais dans ma discussion d'observation sur le sujet): il faut savoir simplement en regardant le code appelant l'API ce qu'il fait .

Par exemple, voyez cette discussion sur GitHub pour le dépôt de discours , ça va quelque chose comme:

foo.update_pinned(true, true);

En regardant simplement le code (sans connaître les noms des paramètres, la documentation, etc.), on ne peut pas deviner ce qu'il va faire - que signifie le deuxième argument? L'amélioration suggérée est d'avoir quelque chose comme:

foo.pin()
foo.unpin()
foo.pin_globally()

Et cela clarifie les choses (le deuxième argument était de savoir s'il fallait épingler foo globalement, je suppose), et je suis d'accord dans ce cas, la dernière serait certainement une amélioration.

Cependant, je pense qu'il peut y avoir des cas où des méthodes pour définir un état différent mais logiquement lié seraient mieux exposées en tant qu'appel de méthode plutôt qu'en tant qu'appels séparés, même si vous ne savez pas ce qu'il fait simplement en regardant le code . (Il faudrait donc recourir à la recherche des noms de paramètres et de la documentation pour le savoir - ce que personnellement je ferais toujours, peu importe si je ne suis pas familier avec une API).

Par exemple, j'expose une méthode SetVisibility(bool, string, bool)sur un FalconPeer et je reconnais simplement en regardant la ligne:

falconPeer.SetVisibility(true, "aerw3", true);

Vous n'auriez aucune idée de ce qu'il fait. Il définit 3 valeurs différentes qui contrôlent la "visibilité" du falconPeerdans le sens logique: accepter les demandes de jointure, uniquement avec mot de passe et répondre aux demandes de découverte. Diviser cela en 3 appels de méthode pourrait conduire un utilisateur de l'API à définir un aspect de la "visibilité" en oubliant d'en définir d'autres auxquels je les oblige à réfléchir en exposant uniquement la seule méthode pour définir tous les aspects de la "visibilité" . De plus, lorsque l'utilisateur veut changer un aspect, il voudra presque toujours changer un autre aspect et peut maintenant le faire en un seul appel.


13
C'est précisément la raison pour laquelle certaines langues ont des paramètres nommés (parfois même des paramètres nommés imposés ). Par exemple , vous groupe pourrait beaucoup de paramètres dans un simple updateméthode: foo.update(pinned=true, globally=true). Ou: foo.update_pinned(true, globally=true). Ainsi, la réponse à votre question doit également prendre en compte les fonctionnalités du langage, car une bonne API pour la langue X peut ne pas être bonne pour la langue Y et vice versa.
Bakuriu

D'accord - cessons d'utiliser des booléens :)
Ven

2
Il est connu comme "piège booléen"
user11153

@Bakuriu Even C a des énumérations, même s'il s'agit d'entiers déguisés. Je ne pense pas qu'il existe un langage réel où les booléens sont une bonne conception d'API.
Doval

1
@Doval Je ne comprends pas ce que vous essayez de dire. J'ai utilisé des booléens dans cette situation simplement parce que l'OP les a utilisés, mais mon point n'a absolument aucun rapport avec la valeur transmise. Par exemple: setSize(10, 20)n'est pas aussi lisible que setSize(width=10, height=20)ou random(distribution='gaussian', mean=0.5, deviation=1). Dans les langues avec des paramètres nommés imposés, les booléens peuvent transmettre exactement la même quantité d'informations que l'utilisation d'énumérations / constantes nommées, de sorte qu'ils peuvent être bons dans les API.
Bakuriu

Réponses:


27

Votre désir de ne pas le diviser en trois appels de méthode est parfaitement compréhensible, mais vous avez d'autres options en plus des paramètres booléens.

Vous pouvez utiliser des énumérations:

falconPeer.SetVisibility(JoinRequestOptions.Accept, "aerw3", DiscoveryRequestOptions.Reply);

Ou même une énumération de drapeaux (si votre langue le prend en charge):

falconPeer.SetVisibility(VisibilityOptions.AcceptJoinRequests | VisibilityOptions.ReplyToDiscoveryRequests, "aerw3");

Ou vous pouvez utiliser un objet paramètre :

var options = new VisibilityOptions();
options.AcceptJoinRequests = true;
options.ReplyToDiscoveryRequest = true;
options.Password="aerw3";
falconPeer.SetVisibility(options);

Le modèle d'objet de paramètre vous offre quelques autres avantages qui peuvent vous être utiles. Il facilite le contournement et la sérialisation d'un ensemble de paramètres, et vous pouvez facilement donner aux paramètres non spécifiés des valeurs "par défaut".

Ou vous pouvez simplement utiliser des paramètres booléens. Microsoft semble le faire tout le temps avec l'API .NET Framework, vous pouvez donc simplement hausser les épaules et dire "si c'est assez bon pour eux, c'est assez bon pour moi".


Le modèle d'objet de paramètre a toujours un problème que l'OP a déclaré: "La division de ceci en 3 appels de méthode pourrait conduire un utilisateur de l'API à définir un aspect de" visibilité "en oubliant d'en définir d'autres ".
Lode

C'est vrai, mais je pense que cela rend la situation meilleure (sinon parfaite). Si l'utilisateur est obligé d'instancier et de passer un objet VisibilityOptions, il peut (avec un peu de chance) lui rappeler qu'il existe d'autres propriétés sur l'objet VisibilityOptions qu'il pourrait vouloir définir. Avec l'approche des trois appels de méthode, ils n'ont qu'à leur rappeler des commentaires sur les méthodes qu'ils appellent.
BenM

6

Évidemment, il y a toujours des exceptions à la règle, mais comme vous l'avez bien expliqué par vous-même, il y a certains avantages à avoir une API lisible. Les arguments booléens sont particulièrement gênants, car 1) ils ne révèlent pas d'intention et 2) ils impliquent que vous appeliez une fonction, alors que vous devriez en avoir deux, car des choses différentes vont se produire en fonction du drapeau booléen.

La question principale est plutôt: combien d'efforts voulez-vous investir pour rendre votre API plus lisible? Plus il est tourné vers l'extérieur, plus l'effort peut facilement être justifié. S'il s'agit d'une API qui n'est utilisée que par une autre unité, ce n'est pas si grave. Si vous parlez d'une API REST dans laquelle vous prévoyez de laisser le monde entier perdre dessus, vous pouvez également investir davantage d'efforts pour la rendre plus compréhensible.

Quant à votre exemple, il existe une solution simple: Apparemment, dans votre cas, la visibilité n'est pas seulement une chose vraie ou fausse. Au lieu de cela, vous avez tout un ensemble de choses que vous considérez comme de la "visibilité". Une solution pourrait être d'introduire quelque chose comme une Visibilityclasse, qui couvre tous ces différents types de visibilité. Si vous appliquez davantage le modèle Builder pour les créer, vous pouvez vous retrouver avec du code comme celui-ci:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.