Il y a un bon quart de siècle, lorsque j'apprenais le C ++, on m'a enseigné que les interfaces doivent être indulgentes et, dans la mesure du possible, ne pas se soucier de l'ordre dans lequel les méthodes ont été appelées car le consommateur peut ne pas avoir accès à la source ou à la documentation au lieu de cette.
Cependant, chaque fois que j'ai encadré des programmeurs juniors et des développeurs seniors qui m'ont entendu, ils ont réagi avec étonnement, ce qui m'a amené à me demander si c'était vraiment une chose ou si elle venait juste de sortir de la mode.
Aussi clair que de la boue?
Considérez une interface avec ces méthodes (pour créer des fichiers de données):
OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile
Maintenant, vous pouvez bien sûr les parcourir dans l'ordre, mais dites que vous ne vous souciez pas du nom du fichier (pensez a.out
) ou de l'en-tête et de la chaîne de fin qui étaient inclus, vous pouvez simplement appeler AddDataLine
.
Un exemple moins extrême pourrait être l'omission des en-têtes et des remorques.
Une autre encore pourrait être de définir les chaînes d'en-tête et de fin avant l'ouverture du fichier.
Est-ce un principe de conception d'interface qui est reconnu ou tout simplement la manière POLA avant qu'on lui donne un nom?
NB ne vous embourbez pas dans les détails de cette interface, c'est juste un exemple pour cette question.
la source
Réponses:
Une façon dont vous pouvez vous en tenir au principe du moindre étonnement est de considérer d'autres principes tels que les FAI et les SRP , ou même SEC .
Dans l'exemple spécifique que vous avez donné, la suggestion semble être qu'il existe une certaine dépendance de l'ordre de manipulation du fichier; mais votre API contrôle à la fois l'accès aux fichiers et le format des données, ce qui sent un peu comme une violation de SRP.
Edit / Update: cela suggère également que l'API elle-même demande à l'utilisateur de violer DRY, car il devra répéter les mêmes étapes chaque fois qu'il utilisera l'API .
Envisagez une autre API où les opérations d'E / S sont distinctes des opérations de données. et où l'API elle-même «possède» la commande:
ContentBuilder
FileWriter
Avec la séparation ci-dessus, le
ContentBuilder
n'a pas besoin de réellement "faire" autre chose que de stocker les lignes / en-tête / bande-annonce (Peut-être aussi uneContentBuilder.Serialize()
méthode qui connaît l'ordre). En suivant d'autres principes SOLID, peu importe que vous définissiez l'en-tête ou la fin avant ou après l'ajout de lignes, car rien dans leContentBuilder
n'est réellement écrit dans le fichier jusqu'à ce qu'il soit transmis àFileWriter.Write
.Il présente également l'avantage supplémentaire d'être un peu plus flexible; par exemple, il peut être utile d'écrire le contenu dans un enregistreur de diagnostics ou de le transmettre sur un réseau au lieu de l'écrire directement dans un fichier.
Lors de la conception d'une API, vous devez également prendre en compte le rapport d'erreurs, qu'il s'agisse d'un état, d'une valeur de retour, d'une exception, d'un rappel ou d'autre chose. L'utilisateur de l'API s'attendra probablement à pouvoir détecter par programme toute violation de ses contrats, ou même d'autres erreurs qu'il ne peut pas contrôler, telles que des erreurs d'E / S de fichiers.
la source
SetHeader
ouAddLine
importe. Pour éliminer cette dépendance à l'ordre n'est ni FAI ni SRP, c'est simplement POLA.FileWriter
pourrait alors exiger la valeur de la dernièreContentBuilder
étape de laWrite
méthode pour s'assurer que tout le contenu d'entrée est complet, ce qui rendInvalidContentException
inutile.ContentBuilder
et de permettreFileWriter.Write
d'encapsuler ce peu de connaissances. L'exception serait nécessaire en cas de problème avec le contenu (par exemple, un en-tête manquant). Un retour pourrait également fonctionner, mais je ne suis pas fan de transformer les exceptions en codes retour.Il ne s'agit pas seulement de POLA, mais aussi d'empêcher un état invalide comme source possible de bogues.
Voyons comment nous pouvons fournir quelques contraintes à votre exemple sans fournir une implémentation concrète:
Première étape: ne laissez rien être appelé avant l'ouverture d'un fichier.
Maintenant, il devrait être évident qu'il
CreateDataFileInterface.OpenFile
faut appeler pour récupérer uneDataFileInterface
instance, où les données réelles peuvent être écrites.Deuxième étape: assurez-vous que les en-têtes et les remorques sont toujours définis.
Vous devez maintenant fournir tous les paramètres requis à l'avance pour obtenir un
DataFileInterface
nom de fichier, un en-tête et une bande-annonce. Si la chaîne de fin n'est pas disponible jusqu'à ce que toutes les lignes soient écrites, vous pouvez également déplacer ce paramètre versClose()
(renommer éventuellement la méthode enWriteTrailerAndClose()
) afin que le fichier au moins ne puisse pas être terminé sans chaîne de fin.Pour répondre au commentaire:
Vrai. Je ne voulais pas me concentrer sur l'exemple plus que nécessaire pour faire valoir mon argument, mais c'est une bonne question. Dans ce cas, je pense que je l'appellerais
Finalize(trailer)
et dirais que cela ne fait pas trop. L'écriture de la bande-annonce et la fermeture ne sont que de simples détails de mise en œuvre. Mais si vous n'êtes pas d'accord ou avez une situation similaire où c'est différent, voici une solution possible:Je ne le ferais pas réellement pour cet exemple, mais cela montre comment appliquer la technique en conséquence.
Soit dit en passant, j'ai supposé que les méthodes doivent en fait être appelées dans cet ordre, par exemple pour écrire séquentiellement de nombreuses lignes. Si ce n'est pas obligatoire, je préférerais toujours un constructeur, comme le suggère Ben Cottrel .
la source
WriteTrailerAndClose()
) frôle une violation de la SRP. (C'est une chose avec laquelle j'ai eu du mal à plusieurs reprises, mais votre suggestion semble être un exemple possible.) Comment répondriez-vous?OpenFile
surcharge qui n'en nécessite pas.