Signification d’abstraction fuite?

Que signifie le terme “Abstraction Fuite”? (Veuillez expliquer avec des exemples. J’ai souvent du mal à crier une simple théorie.)

Voici un exemple de meatpace :

Les automobiles ont des abstractions pour les conducteurs. Dans sa forme la plus pure, il y a un volant, un accélérateur et un frein. Cette abstraction cache beaucoup de détails sur ce qui se trouve sous le capot: moteur, cames, courroie de dissortingbution, bougies d’allumage, radiateur, etc.

La chose intéressante à propos de cette abstraction est que nous pouvons remplacer des parties de l’implémentation par des pièces améliorées sans avoir à recycler l’utilisateur. Disons que nous remplaçons le dissortingbuteur par allumage électronique et que nous remplaçons la came fixe par une came variable. Ces changements améliorent les performances, mais l’utilisateur continue de naviguer avec la roue et utilise les pédales pour démarrer et s’arrêter.

C’est en fait tout à fait remarquable … un homme de 16 ans ou de 80 ans peut faire fonctionner cette machine compliquée sans trop savoir comment cela fonctionne à l’intérieur!

Mais il y a des fuites. La transmission est une petite fuite. Dans une transmission automatique, vous pouvez sentir la voiture perdre de la puissance pendant un moment, car elle change de vitesse, tandis que dans la transmission CVT, vous ressentez un couple régulier tout en haut.

Il y a aussi des fuites plus importantes. Si vous faites tourner le moteur trop vite, vous risquez de l’endommager. Si le bloc moteur est trop froid, la voiture risque de ne pas démarrer ou sa performance peut être médiocre. Et si vous allumez la radio, les phares et la climatisation en même temps, votre consommation d’essence diminuera.

Cela signifie simplement que votre abstraction expose certains détails de l’implémentation ou que vous devez connaître les détails de l’implémentation lors de l’utilisation de l’abstraction. Le terme est atsortingbué à Joel Spolsky , circa 2002. Voir l’ article de Wikipedia pour plus d’informations.

Un exemple classique est celui des bibliothèques réseau qui vous permettent de traiter les fichiers distants comme des fichiers locaux. Le développeur utilisant cette abstraction doit être conscient que des problèmes de réseau peuvent entraîner un échec, contrairement aux fichiers locaux. Vous devez ensuite développer du code pour gérer spécifiquement les erreurs en dehors de l’abstraction fournie par la bibliothèque réseau.

Wikipedia a une assez bonne définition pour cela

Une abstraction qui fuit fait référence à toute abstraction implémentée, destinée à réduire (ou masquer) la complexité, où les détails sous-jacents ne sont pas complètement masqués.

Ou, en d’autres termes, pour les logiciels, vous pouvez observer les détails de la mise en œuvre d’une fonctionnalité via des limitations ou des effets secondaires dans le programme.

Un exemple rapide serait les fermetures C # / VB.Net et leur incapacité à capturer les parameters ref / out. La raison pour laquelle ils ne peuvent pas être capturés est due à un détail de la mise en œuvre du processus de levage. Cela ne signifie pas pour autant qu’il existe une meilleure façon de procéder.

Voici un exemple familier aux développeurs .NET: La classe Page ASP.NET tente de masquer les détails des opérations HTTP, en particulier la gestion des données de formulaire, afin que les développeurs n’aient pas à gérer les valeurs publiées (car contrôles du serveur).

Mais si vous allez au-delà des scénarios d’utilisation les plus élémentaires, l’abstraction de la Page commence à fuir et il devient difficile de travailler avec des pages à moins que vous ne compreniez les détails d’implémentation de la classe.

Un exemple courant est l’ajout dynamic de contrôles à une page – la valeur des contrôles ajoutés dynamicment ne sera pas mappée pour vous, sauf si vous les ajoutez au bon moment : avant que le moteur sous-jacent mappe les valeurs de formulaire entrantes aux contrôles appropriés. Lorsque vous devez apprendre cela, l’abstraction a fui .

Eh bien, dans un sens, c’est une chose purement théorique, mais pas sans importance.

Nous utilisons des abstractions pour faciliter la compréhension des choses. Je peux opérer sur une classe de chaînes dans une langue donnée pour masquer le fait que je traite un ensemble ordonné de caractères qui sont des éléments individuels. Je traite un ensemble ordonné de caractères pour cacher le fait que je traite des nombres. Je traite des nombres pour cacher le fait que je traite avec des 1 et des 0.

Une abstraction qui fuit est une abstraction qui ne cache pas les détails qu’elle est censée cacher. Si vous appelez ssortingng.Length sur une chaîne de caractères de 5 caractères en Java ou .NET, vous pouvez obtenir une réponse de 5 à 10, en raison des détails d’implémentation où ces caractères appellent réellement des points de données UTF-16 .5 d’un personnage. L’abstraction a fui. Si vous ne le faites pas, cela signifie que trouver la longueur nécessiterait davantage d’espace de stockage (pour stocker la longueur réelle) ou passer de O (1) à O (n) (pour déterminer la longueur réelle). Si je me soucie de la vraie réponse (souvent vous ne le faites pas vraiment), vous devez travailler sur la connaissance de ce qui se passe réellement.

Des cas plus discutables se produisent avec des cas où une méthode ou une propriété vous permet d’entrer dans le fonctionnement interne, qu’il s’agisse de fuites d’abstraction ou de manières bien définies de passer à un niveau d’abstraction inférieur.

Je continuerai à donner des exemples en utilisant RPC.

Dans le monde idéal de RPC, un appel de procédure à distance devrait ressembler à un appel de procédure local (ou du moins à l’histoire). Il devrait être complètement transparent pour le programmeur de telle sorte que quand ils appellent SomeObject.someFunction() ils n’aient aucune idée si SomeObject (ou juste une fonction someFunction ) sont stockés et exécutés localement ou stockés et exécutés à distance. La théorie veut que cela simplifie la programmation.

La réalité est différente car il y a une énorme différence entre faire un appel de fonction local (même si vous utilisez le langage interprété le plus lent du monde) et:

  • appeler via un object proxy
  • sérialiser vos parameters
  • établir une connexion réseau (si elle n’est pas déjà établie)
  • transmettre les données au proxy distant
  • demander au proxy distant de restaurer les données et d’appeler la fonction distante en votre nom
  • sérialiser les valeurs de retour
  • transmettre les valeurs de retour au proxy local
  • remonter les données sérialisées
  • retourner la réponse de la fonction distante

Dans le temps seul, il y a environ trois ordres (ou plus!) De différence de magnitude. Ces trois ordres de grandeur vont faire une énorme différence de performance, ce qui rendra votre abstraction de fuite d’appel de procédure plutôt évidente la première fois que vous traitez par erreur un RPC comme un appel de fonction réel. En outre, un appel de fonction réel, sauf problèmes sérieux dans votre code, aura très peu de points de défaillance en dehors des bogues d’implémentation. Un appel RPC présente tous les problèmes possibles suivants, qui se présenteront sous la forme de cas d’échec en plus de ce que vous attendez d’un appel local normal:

  • vous ne pourrez peut-être pas instancier votre proxy local
  • vous ne pourrez peut-être pas instancier votre proxy distant
  • les mandataires peuvent ne pas être en mesure de se connecter
  • les parameters que vous envoyez peuvent ne pas le rendre intact ou du tout
  • la valeur de retour que l’envoi à distance peut ne pas le rendre intact ou du tout

Donc, maintenant, votre appel RPC, qui est “comme un appel de fonction local”, comporte une foule de conditions d’échec supplémentaires auxquelles vous n’avez pas à faire face lors d’appels de fonctions locaux. L’abstraction a encore fui, encore plus fort.

En fin de compte, RPC est une mauvaise abstraction car elle fuit comme un tamis à tous les niveaux – en cas de succès et en cas d’échec des deux.

Un exemple dans l’exemple de Django ORM plusieurs à plusieurs :

Notez dans l’exemple d’utilisation de l’API que vous devez enregistrer. L’object de base a1 avant de pouvoir append des objects Publication à l’atsortingbut many-to-many. Et notez que la mise à jour de l’atsortingbut plusieurs-à-plusieurs enregistre immédiatement dans la firebase database sous-jacente, alors que la mise à jour d’un atsortingbut singulier n’est pas répercutée dans la firebase database avant l’appel de .save ().

L’abstraction est que nous travaillons avec un graphe d’object, où les atsortingbuts à valeur unique et les atsortingbuts à valeurs multiples ne sont que des atsortingbuts. Mais la mise en œuvre en tant que firebase database relationnelle a sauvegardé le magasin de données, car le système d’intégrité du RDBS apparaît à travers le mince placage d’une interface d’object.

Supposons que nous ayons le code suivant dans une bibliothèque:

 Object[] fetchDeviceColorAndModel(Ssortingng serialNumberOfDevice) { //fetch Device Color and Device Model from DB. //create new Object[] and set 0th field with color and 1st field with model value. } 

Lorsque le consommateur appelle l’API, il reçoit un object []. Le consommateur doit comprendre que le premier champ du tableau d’objects a une valeur de couleur et que le second champ est la valeur du modèle. Ici, l’abstraction a fui de la bibliothèque vers le code consommateur.

L’une des solutions consiste à renvoyer un object qui encapsule le modèle et la couleur du périphérique. Le consommateur peut appeler cet object pour obtenir le modèle et la valeur de couleur.

 DeviceColorAndModel fetchDeviceColorAndModel(Ssortingng serialNumberOfTheDevice) { //fetch Device Color and Device Model from DB. return new DeviceColorAndModel(color, model); } 

Une abstraction qui fuit concerne l’état d’encapsulation. exemple très simple d’abstraction qui fuit:

 $currentTime = new DateTime(); $bankAccount1->setLastRefresh($currentTime); $bankAccount2->setLastRefresh($currentTime); $currentTime->setTimestamp($aTimestamp); class BankAccount { // ... public function setLastRefresh(DateTimeImmutable $lastRefresh) { $this->lastRefresh = $lastRefresh; } } 

et de la bonne façon (pas de l’abstraction qui fuit):

 class BankAccount { // ... public function setLastRefresh(DateTime $lastRefresh) { $this->lastRefresh = clone $lastRefresh; } } 

plus de description ici .