Que signifie le terme "Leaky Abstraction"? (Veuillez expliquer avec des exemples. J'ai souvent du mal à comprendre une simple théorie.)
Que signifie le terme "Leaky Abstraction"? (Veuillez expliquer avec des exemples. J'ai souvent du mal à comprendre une simple théorie.)
Réponses:
Voici un exemple d' espace de viande :
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 qu'il y a sous le capot: moteur, cames, courroie de distribution, 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 parties améliorées sans recycler l'utilisateur. Disons que nous remplaçons le capuchon du distributeur par un allumage électronique, et nous remplaçons la came fixe par une came variable. Ces changements améliorent les performances, mais l'utilisateur dirige toujours avec la roue et utilise les pédales pour démarrer et s'arrêter.
C'est en fait assez remarquable ... un jeune de 16 ou 80 ans peut faire fonctionner cette machine compliquée sans vraiment en savoir beaucoup sur son fonctionnement à 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 lorsqu'elle change de vitesse, tandis que dans la CVT, vous ressentez un couple régulier tout en montant.
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 peut ne pas démarrer ou avoir de mauvaises performances. Et si vous activez la radio, les phares et la climatisation en même temps, vous verrez votre consommation d'essence diminuer.
Cela signifie simplement que votre abstraction expose certains des détails d'implémentation, ou que vous devez être conscient des détails d'implémentation lorsque vous utilisez l'abstraction. Le terme est attribué à Joel Spolsky , vers 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 locaux. Le développeur qui utilise cette abstraction doit être conscient que des problèmes de réseau peuvent entraîner l'échec de cette opération 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.
Wikipédia 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 cachés
Ou en d'autres termes, pour les logiciels, c'est lorsque vous pouvez observer les détails de 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 paramètres ref / out. La raison pour laquelle ils ne peuvent pas être capturés est due à un détail de mise en œuvre de la façon dont le processus de levage se déroule. Cela ne veut pas dire cependant qu'il existe une meilleure façon de faire cela.
Voici un exemple familier aux développeurs .NET: la Page
classe d'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 elle mappe automatiquement les valeurs de formulaire au serveur les contrôles).
Mais si vous vous éloignez des scénarios d'utilisation les plus basiques, l' Page
abstraction commence à fuir et il devient difficile de travailler avec des pages à moins de comprendre les détails d'implémentation de la classe.
Un exemple courant est l'ajout dynamique de contrôles à une page - la valeur des contrôles ajoutés dynamiquement 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, d'une certaine manière, c'est une chose purement théorique, mais pas sans importance.
Nous utilisons des abstractions pour rendre les choses plus faciles à comprendre. Je peux opérer sur une classe de chaîne dans une langue pour cacher 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 des 1 et des 0.
Une abstraction qui fuit est celle qui ne cache pas les détails qu'elle est censée cacher. Si appelez string.Length sur une chaîne de 5 caractères en Java ou .NET, je pourrais obtenir n'importe quelle réponse de 5 à 10, en raison des détails d'implémentation où ce que ces langues appellent des caractères sont en réalité des points de données UTF-16 qui peuvent représenter 1 ou .5 d'un caractère. L'abstraction a fui. Cependant, ne pas la fuir signifie que trouver la longueur nécessiterait plus 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 l'avez pas vraiment), vous devez travailler sur la connaissance de ce qui se passe réellement.
Des cas plus discutables se produisent avec des cas comme 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 moyens bien définis de passer à un niveau d'abstraction inférieur, peuvent parfois être une question sur laquelle les gens ne sont pas d'accord.
Je vais continuer dans la veine de 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 locale (du moins c'est ce que dit l'histoire). Il doit être complètement transparent pour le programmeur de telle sorte que lorsqu'ils appellent, SomeObject.someFunction()
ils n'ont aucune idée si SomeObject
(ou juste someFunction
d'ailleurs) sont stockés localement et exécutés 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 locale (même si vous utilisez le langage interprété le plus lent au monde) et:
Dans le temps seul, c'est environ trois ordres (ou plus!) De différence de grandeur. Ces trois ordres de grandeur vont faire une énorme différence dans les performances qui rendra votre abstraction d'un appel de procédure une fuite plutôt évidente la première fois que vous traitez à tort un RPC comme un véritable appel de fonction. De plus, un véritable appel de fonction, à moins de 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 seront considérés comme des cas d'échec au-delà de ce que vous attendez d'un appel local régulier:
Alors maintenant, votre appel RPC qui est "juste comme un appel de fonction locale" a tout un tas de conditions d'échec supplémentaires que vous n'avez pas à affronter lorsque vous faites des appels de fonction locale. L'abstraction a encore fui, encore plus fort.
En fin de compte, RPC est une mauvaise abstraction car il 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 many-to-many :
Notez dans l'exemple d'utilisation d'API que vous devez .save () l'objet Article de base a1 avant de pouvoir ajouter des objets Publication à l'attribut plusieurs-à-plusieurs. Et notez que la mise à jour de l'attribut plusieurs-à-plusieurs enregistre immédiatement dans la base de données sous-jacente, tandis que la mise à jour d'un attribut singulier n'est pas reflétée dans la base de données tant que .save () n'est pas appelé.
L'abstraction est que nous travaillons avec un graphe d'objets, où les attributs à valeur unique et les attributs à valeurs multiples ne sont que des attributs. Mais l'implémentation en tant que magasin de données basé sur une base de données relationnelle fuit ... alors que le système d'intégrité du RDBS apparaît à travers le mince placage d'une interface objet.
Tout d'abord, il vaut mieux comprendre ce qu'est «l'abstraction»?
L'abstraction est une manière de simplifier le monde. Cela signifie que vous n'avez pas à vous soucier de ce qui se passe réellement sous le capot / derrière le rideau. Cela signifie que quelque chose est une preuve idiote. Ok, alors qu'est-ce que ça veut dire? Ceci est mieux illustré par l'exemple.
Exemple d'abstraction: les complexités du pilotage d'un 737/747 sont "abstraites"
Prenons l'exemple d'un avion à réaction Boeing. Ces avions sont des machines très compliquées. Vous avez des moteurs à réaction, des systèmes d'oxygène, des systèmes électriques, des systèmes de train d'atterrissage, etc., mais le pilote n'a pas à se soucier des subtilités du moteur à réaction ... tout cela est "évacué", ce qui signifie: le jour, un pilote ne s'inquiète que de la roue et d'une colonne de commande pour contrôler l'avion. Gauche pour aller à gauche et droite pour aller à droite, tirez vers le haut pour gagner de l'élévation et poussez vers le bas pour descendre. C'est assez simple ... en fait j'ai menti: contrôler le volant est un peu plus compliqué. Dans un monde idéal, c'est la seule chose qu'il devraitêtre inquiet. Mais ce n'est pas le cas dans la vraie vie: si vous pilotez un avion comme un singe, sans aucune compréhension réelle du fonctionnement d'un avion, ni d'aucun des détails de mise en œuvre, vous vous écraserez probablement et tuerez tout le monde à bord.
En réalité, un pilote doit s'inquiéter de BEAUCOUP de choses importantes - tout n'a pas été résumé: les pilotes doivent s'inquiéter de la vitesse du vent, de la poussée, des angles d'attaque, du carburant, de l'altitude, des problèmes météorologiques, des angles de descente, si le pilote va dans la bonne direction, où se trouve l'avion en ce moment et ainsi de suite. Les ordinateurs peuvent aider le pilote dans ces tâches, mais tout n'est pas automatisé / simplifié.
Par exemple, si le pilote tire trop fort sur la colonne - l'avion obéira, mais alors le pilote risquera de caler l'avion, et si vous décrochez l'avion, il est extrêmement difficile de reprendre le contrôle avant qu'il ne s'écrase au sol .
En d'autres termes, il ne suffit pas que le pilote contrôle simplement le volant sans rien savoir d'autre ......... nooooo ....... il doit connaître les risques et les limites sous-jacents de l'avion avant qu'il n'en vole un ....... elle doit savoir comment fonctionne l'avion, et comment l'avion vole; il doit connaître les détails de la mise en œuvre ..... elle doit savoir que tirer trop fort entraînera un décrochage, ou qu'un atterrissage trop raide détruira l'avion.
Ces choses ne sont pas abstraites. Beaucoup de choses sont abstraites, mais pas tout. Le pilote n'a qu'à se soucier de la colonne de direction, et peut-être d'une ou deux autres choses. L'abstraction est "fuyante".
...... c'est la même chose dans votre code. Si vous ne connaissez pas les détails de mise en œuvre sous-jacents, le plus souvent, vous travaillerez dans un coin.
Voici un exemple de codage:
Les ORM résument beaucoup de tracas dans le traitement des requêtes de base de données, mais si vous avez déjà fait quelque chose comme:
User.all.each do |user|
puts user.name # let's print each user's name
end
Ensuite, vous vous rendrez compte que c'est une bonne façon de tuer votre application si vous avez plus de quelques millions d'utilisateurs. Tout n'est pas abstrait. Vous devez savoir que les appels User.all
avec 25 millions d'utilisateurs vont augmenter votre utilisation de la mémoire et poser des problèmes. Vous devez connaître certains détails sous-jacents. L'abstraction est fuyante.
Le fait qu'à un moment donné , qui sera guidé par votre échelle et votre exécution, vous devrez vous familiariser avec les détails d'implémentation de votre cadre d'abstraction afin de comprendre pourquoi il se comporte de cette façon.
Par exemple, considérez cette SQL
requête:
SELECT id, first_name, last_name, age, subject FROM student_details;
Et son alternative:
SELECT * FROM student_details;
Maintenant, elles ressemblent à des solutions logiquement équivalentes, mais les performances de la première sont meilleures en raison de la spécification des noms de colonnes individuels.
C'est un exemple trivial, mais cela revient finalement à la citation de Joel Spolsky:
Toutes les abstractions non triviales, dans une certaine mesure, sont fuites.
À un moment donné, lorsque vous atteindrez une certaine échelle dans votre opération, vous voudrez optimiser le fonctionnement de votre base de données (SQL). Pour ce faire, vous devrez connaître le fonctionnement des bases de données relationnelles. Cela vous a été abstrait au début, mais il fuit. Vous devez l'apprendre à un moment donné.
Supposons que nous ayons le code suivant dans une bibliothèque:
Object[] fetchDeviceColorAndModel(String 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 obtient un Object []. Le consommateur doit comprendre que le premier champ du tableau d'objets a une valeur de couleur et le second champ est la valeur de modèle. Ici, l'abstraction a fui de la bibliothèque vers le code consommateur.
L'une des solutions consiste à renvoyer un objet qui encapsule le modèle et la couleur de l'appareil. Le consommateur peut appeler cet objet pour obtenir le modèle et la valeur de couleur.
DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice)
{
//fetch Device Color and Device Model from DB.
return new DeviceColorAndModel(color, model);
}
L'abstraction qui fuit consiste uniquement à encapsuler l'état. 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 la bonne manière (pas d'abstraction qui fuit):
class BankAccount
{
// ...
public function setLastRefresh(DateTime $lastRefresh)
{
$this->lastRefresh = clone $lastRefresh;
}
}
plus de description ici .