Dois-je écrire une API d'interface avant une implémentation?

14

Je me suis plongé récemment dans une programmation plus "organisée" et j'ai appris que je devrais programmer sur une interface, pas une implémentation. Dans cet esprit, serait-il préférable de "dessiner" un projet dans des interfaces avant d'en écrire l'implémentation dans la mesure du possible?

Et si tel est le cas, dans le cas de l'utilisation de bibliothèques tierces (c'est-à-dire Lidgren), dois-je également les encapsuler dans des interfaces et les résoudre via des conteneurs IOC, ou est-ce correct de les exposer aux interfaces?

Dan Pantry
la source
D'après mon expérience personnelle - il est bon de concevoir l'architecture en premier - la responsabilité de chaque classe. Vous n'avez pas besoin de l'écrire, pensez-y ou dessinez-le sur un papier. Ensuite, il s'agit de préférences personnelles, mais je recommande d'écrire d'abord des commentaires de doc pour chaque méthode que vous commencez à implémenter. L'écriture de documents vous fait vraiment réfléchir à la fonctionnalité avant de commencer à écrire du code.
Sulthan
Oui, et programmez les interfaces (ou les classes abstraites d'ailleurs) avant de les implémenter. Il aide à obtenir le flux de messages du client vers le serveur et vice versa "à droite" avant d'être enlisé (et investi dans) les implémentations. Très bon diaporama sur la question: comment concevoir une bonne API et pourquoi c'est important
Marjan Venema

Réponses:

8

Malheureusement, vous constaterez que cela se résume souvent à vos préférences personnelles.

Ce que vous avez décrit jusqu'à présent, cependant, semble bon. En fait, si vous le souhaitez (et je le recommande), vous pouvez utiliser l'approche suivante:

  1. Écrivez le squelette de votre application sous forme d'interfaces, de classes abstraites (tronquées) et de classes (également tronquées)
  2. Écrivez vos tests par rapport à ces interfaces et stubs (ils échoueront pour l'instant)
  3. Écrivez vos implémentations (vos tests commenceront à passer à la fin de votre implémentation)

Vous vous concentrez sur l'écriture de code plus "organisé". Suivre TDD vous y aidera.

Quelques points supplémentaires:

  • Les conteneurs IoC sont pratiques. Utilisez-les et DI autant que vous le pouvez.
  • N'envelopper bibliothèques 3ème partie. Cela détendra le couplage entre votre code (code que vous contrôlez) et le code tiers (code que vous ne contrôlez pas)
MetaFight
la source
1
C'est ce que j'avais pensé à l'origine, mais on m'a dit que cela violerait le principe YAGNI. Le problème que je trouve avec beaucoup de mes projets qui ne sont jamais finis, c'est qu'ils deviennent rapidement non maintenables avec la quantité de code blob que j'ai écrit, parce que je ne l'organise pas correctement ou ne planifie pas mon plan d'attaque.
Dan Pantry
quelle partie violerait YAGNI?
MetaFight
Emballage de bibliothèques tierces.
Dan Pantry
2
Je suppose que cela se résume à: quelles sont les chances que la bibliothèque tierce change? S'il y a 0% de chance de cela, alors bien sûr YAGNI. Mais c'est rarement le cas. En outre, l'encapsulation de vos bibliothèques tierces peut rendre votre autre code plus facile à tester unitaire (si, par exemple, vous ne pouviez pas
simuler la
1
@DanPantry: Envelopper des bibliothèques tierces n'est pas une violation YAGNI, mais une protection bien nécessaire contre "l'infestation de bibliothèques tierces par votre propre code". Il ne s'agit pas seulement de pouvoir échanger une bibliothèque, mais comme MetaFight le dit également, une défense contre les changements dans les nouvelles versions de la bibliothèque qui, autrement, nécessiteraient des changements dans votre propre code. En enveloppant la bibliothèque (et en particulier ses types spécifiques: classes, énumérations, structures, etc.), vous isolez votre propre code et avez un seul point à changer lorsque la bibliothèque change (pour une raison quelconque).
Marjan Venema
13

Oui, vous devez coder contre des interfaces plutôt que des implémentations connues, et oui, vous devez d'abord construire des interfaces plutôt que de les faire émerger de votre propre code.

Les raisons des deux recommandations sont en grande partie les mêmes: la programmation informatique concerne principalement les facteurs humains. Beaucoup trouvent cela surprenant, mais réfléchissent: il existe un nombre presque infini de façons différentes de résoudre le même problème informatique qui fonctionnent aussi bien. Presque tous sont complètement impossibles à comprendre pour quiconque ne les a pas écrits (ou en fait à l'auteur peu de temps après).

Il s'ensuit qu'une bonne ingénierie logicielle consiste essentiellement à obtenir l'effet souhaité (calcul correct avec une efficacité raisonnable) d'une manière qui permette de travailler avec le code source plus tard. Les interfaces et les API sont une partie cruciale de cette discipline: elles vous permettent de penser à un problème à un niveau de description à la fois. C'est beaucoup plus facile que de penser à des règles de cohérence métier et à des implémentations de listes chaînées en même temps, et donc imposer une telle séparation des préoccupations de force est mieux que de permettre au programmeur client d'utiliser votre code comme il l'entend.

C'est difficile à croire pour de nombreux programmeurs de cow-boys, qui sont convaincus qu'ils comprennent tout ce qu'ils écrivent, sont bien meilleurs que les penseurs moyens, et peuvent gérer toute la complexité qui pose des problèmes aux programmeurs "moindres". Ne pas être conscient de ses propres limites cognitives est un phénomène extrêmement courant - c'est pourquoi les meilleures pratiques dans l'organisation du code sont si importantes (et si souvent ignorées).

Pour répéter, les interfaces et les barrières API sont largement bonnes , même lorsque vous ne coopérez qu'avec vous-même. En ce qui concerne les bibliothèques externes, si elles apportent une API bien pensée avec elles, je ne vois aucun problème à l'utiliser, car tant que vous ne prévoyez pas d'avoir à échanger cette bibliothèque contre une autre. Sinon, un wrapper ou une couche anti-corruption peut être une très bonne idée.

Kilian Foth
la source
J'aime votre point de vue sur SE étant en grande partie sur l'obtention de l'effet souhaité d'une manière qui permet de travailler avec le code source plus tard. J'aurais aimé pouvoir l'exprimer aussi bien lors de mon dernier travail, où je me battais toujours pour du code propre!
MetaFight
Existe-t-il une convention de dénomination pour les API qui ne sont que des interfaces que je vais finir par utiliser partout? Par exemple, si je fais un modèle de commande, est-ce que je l'appelle "commandables"?
Snoop
@StevieV Il en existe plusieurs, par exemple IBlahimplémenté par Blahou Blahimplémenté par BlahImpl. Je n'aime pas les deux, et ont tendance à utiliser Blahmis en œuvre par OralBlah, WrittenBlahou ASLBlah. Mais comme d'habitude, il est plus important de se conformer à votre base de code et à vos attentes existantes qu'à toute norme générale.
Kilian Foth du
4

Plutôt que de simplement servir de programmation aux interfaces, pourquoi ne pas vous intéresser au développement / conception piloté par les tests (TDD)?

Beaucoup de gens pensent que TDD est une pratique de test, mais en fait c'est une approche de conception où vous laissez les tests exposer comment votre code sera utilisé via des tests (initialement via des tests unitaires, mais peut également l'être via des tests d'intégration).

La programmation vers les interfaces est une arme importante dans votre ensemble d'outils, mais comme la plupart des choses, ce n'est pas toujours la solution / technique / pratique appropriée, car elle n'est pas toujours nécessaire. Vous devez programmer les interfaces où vous en avez besoin.

L'utilisation de TDD vous obligera à explorer où ces interfaces sont importantes et où, franchement, cela n'a pas d'importance. Et à la fin, vous devriez avoir un assez bon ensemble de tests unitaires sur votre base de code.

Quant à l'utilisation de bibliothèques tierces, je recommande fortement de les inclure dans vos propres abstractions, le cas échéant; et ne pas en informer les clients de votre API.

Bonne chance!

[modifier: vu la réponse de megaflight - tout à fait d'accord]

rupjones
la source
2
TDD vous a implicitement pensé en termes d'interface plutôt que d'implémentation, bien que ce ne soit pas une déclaration formelle d '"interface".
DougM
1
C'est une excellente réponse. +1 pour avoir suggéré TDD, ce qui, à mon avis, est la solution au vrai problème du PO de savoir par où commencer lorsque vous travaillez sur un nouveau projet, et j'aurais +1 à nouveau si je le pouvais pour "L'utilisation de TDD vous obligera à explorer où de telles interfaces sont importants et où cela, franchement, n'a pas d'importance. "
Benjamin Hodgson
2

Je pense que c'est exagéré. Si l'utilisateur de votre API n'a pas besoin d'être forcé d'implémenter / utiliser quelque chose d'une certaine manière, je le laisserais de côté. Les interfaces sont des contrats, si je n'en ai pas besoin, alors pourquoi m'en donner un?

Je pense que les gens abusent des interfaces. Vous ajoutez une couche de complexité qui n'est pas nécessaire dans la plupart des cas.

Kyle Johnson
la source
Je pense que les gens sous- utilisent les interfaces. Si vous souhaitez créer des pièces réutilisables, l'interface n'est pas seulement un ajout "agréable à avoir", mais la principale chose à prendre en charge. Outre la mise en œuvre réelle bien sûr.
JensG
1

La programmation contre un contrat est presque toujours une bonne idée. Ce contrat n'a pas besoin d'être une interface, il peut être rempli par une classe à la place. À mon avis, les interfaces sont devenues quelque peu surutilisées avec DI en raison de problèmes de tests unitaires et de cadres de simulation.

Personnellement, je préfère n'apporter des interfaces que lorsque je pourrais très probablement avoir ou avoir plus d'une mise en œuvre d'un contrat. Les interfaces sont idéales pour les référentiels où je souhaite supprimer l'accès aux données, mais probablement moins pour ma logique métier standard, qui est susceptible d'être relativement rigide.

Désormais, l'absence d'interface peut entraîner des problèmes avec les tests unitaires, en particulier pour les puristes. Mais je veux me moquer des dépendances externes de mes programmes, pas de ses dépendances internes. Je veux que mes tests effectuent la validation du code, pas l'écho de la structure du code.

Peter Smith
la source