En informatique , la programmation orientée aspect ( POA ) est un paradigme de programmation qui vise à accroître la modularité en permettant la séparation des préoccupations transversales . Elle y parvient en ajoutant un comportement au code existant (un conseil ) sans le modifier, en spécifiant séparément le code modifié via une spécification de « point de jonction », telle que « enregistrer tous les appels de fonction lorsque le nom de la fonction commence par 'set ' ». Cela permet d'ajouter à un programme des comportements non essentiels à la logique métier (comme la journalisation) sans surcharger le code des fonctions principales.
La programmation orientée aspect (AOP) comprend des méthodes et des outils de programmation qui prennent en charge la modularisation des préoccupations au niveau du code source, tandis que le développement logiciel orienté aspect désigne une discipline d'ingénierie à part entière.
La programmation orientée aspect consiste à décomposer la logique d'un programme en domaines fonctionnels cohérents (appelés préoccupations ). Presque tous les paradigmes de programmation permettent, à des degrés divers, de regrouper et d'encapsuler les préoccupations en entités distinctes et indépendantes grâce à des abstractions (fonctions, procédures, modules, classes, méthodes, etc.) qui servent à implémenter, abstraire et composer ces préoccupations. Certaines préoccupations sont transversales et échappent à ces formes d'implémentation. On les appelle préoccupations transversales ou horizontales.
La journalisation illustre parfaitement une problématique transversale, car une stratégie de journalisation doit impacter chaque élément journalisé du système. Elle concerne donc l'ensemble des classes et méthodes journalisées.
Toutes les implémentations de la programmation orientée aspect (AOP) possèdent des expressions transversales qui regroupent chaque préoccupation. La différence entre les implémentations réside dans la puissance, la sécurité et la facilité d'utilisation des constructions proposées. Par exemple, les intercepteurs spécifient les méthodes permettant d'exprimer une forme limitée de transversalité, sans prise en charge poussée de la sécurité des types ni du débogage. AspectJ propose plusieurs expressions de ce type et les encapsule dans une classe spéciale, appelée aspect . Par exemple, un aspect peut modifier le comportement du code de base (la partie du programme qui n'est pas un aspect) en appliquant des conseils (comportements supplémentaires) à différents points de jonction (points du programme) spécifiés dans une quantification ou une requête appelée point de coupe (qui vérifie si un point de jonction donné correspond). Un aspect peut également apporter des modifications structurelles compatibles au niveau binaire à d'autres classes, comme l'ajout de membres ou de classes parentes.
Histoire
L'AOP a plusieurs antécédents directs : la réflexion et les protocoles de méta-objets , la programmation orientée sujet , les filtres de composition et la programmation adaptative.
Gregor Kiczales et ses collègues du Xerox PARC ont développé le concept explicite de programmation orientée aspect (AOP) et l'ont complété par l' extension AspectJ pour Java. L'équipe de recherche d'IBM a privilégié une approche par outils plutôt qu'une approche par conception de langage et a proposé en 2001 Hyper/J et l' environnement de manipulation des préoccupations (Commercial Manipulation Environment) , qui n'ont pas connu un grand succès.
Les exemples de cet article utilisent AspectJ.
Le serveur de transactions Microsoft est considéré comme la première application majeure de l'AOP, suivi par Enterprise JavaBeans .
Motivation et concepts de base
En général, un aspect est dispersé ou imbriqué dans le code, ce qui le rend plus difficile à comprendre et à maintenir. Il est dispersé car la fonction (comme la journalisation) est répartie sur plusieurs fonctions indépendantes susceptibles de l' utiliser , parfois dans des systèmes totalement différents ou écrites dans des langages différents. Ainsi, modifier la journalisation peut nécessiter de modifier tous les modules concernés. Les aspects s'imbriquent non seulement avec la fonction principale des systèmes dans lesquels ils sont exprimés, mais aussi entre eux. Modifier un aspect implique donc de comprendre tous les aspects imbriqués ou de disposer d'un moyen d'inférer l'effet des modifications.
Prenons l'exemple d'une application bancaire dotée d'une méthode conceptuellement très simple pour transférer un montant d'un compte à un autre. En Java, un tel exemple ressemble à ceci :
classe scellée BankingException étend Exception autorise InsufficientFundsException , UnauthorisedUserException { // ... }public class Bank { public void transfer ( Account fromAcc , Account toAcc , int amount ) throws BankingException { if ( fromAcc . getBalance () < amount ) { throw new InsufficientFundsException (); }fromAcc.retirer ( montant ) ; toAcc.dépôt ( montant ) ; } }
Cependant, cette méthode de transfert néglige certaines considérations qu'une application déployée exigerait, telles que la vérification que l'utilisateur actuel est autorisé à effectuer cette opération, l'encapsulation des transactions de base de données pour éviter toute perte accidentelle de données et la journalisation de l'opération à des fins de diagnostic.
Une version prenant en compte toutes ces nouvelles problématiques pourrait ressembler à ceci :
import java.util.logging.* ;classe scellée BankingException étend Exception autorise InsufficientFundsException , UnauthorisedUserException { // ... }public class Bank { private static final Logger logger ; private final Database database ; public void transfer ( Account fromAcc , Account toAcc , int amount , User user ) throws BankingException { logger . info ( "Transfert d'argent..." ); if ( ! isUserAuthorised ( user , fromAcc )) { logger . log ( Level . WARNING , "L'utilisateur n'est pas autorisé." ); throw new UnauthorisedUserException (); } if ( fromAcc . getBalance () < amount ) { logger . log ( Level . WARNING , "Fonds insuffisants." ); throw new InsufficientFundsException (); }fromAcc.retirer ( montant ) ; toAcc.dépôt ( montant ) ;base de données.commitChanges ( ) ; // Opération atomique.logger.log ( Level.INFO , " Transaction réussie. " ) ; } }
Dans cet exemple, d'autres intérêts se sont mêlés aux fonctionnalités de base (parfois appelées problématiques liées à la logique métier ). Les transactions, la sécurité et la journalisation illustrent parfaitement ces problématiques transversales .
Imaginons maintenant ce qui se passerait si nous devions soudainement modifier les paramètres de sécurité de l'application. Dans la version actuelle du programme, les opérations liées à la sécurité sont dispersées dans de nombreuses méthodes, et une telle modification exigerait un effort considérable.
La programmation orientée aspect (AOP) tente de résoudre ce problème en permettant au programmeur d'exprimer des préoccupations transversales dans des modules indépendants appelés aspects . Les aspects peuvent contenir des conseils (code joint à des points spécifiques du programme) et des déclarations inter-types (membres structurels ajoutés à d'autres classes). Par exemple, un module de sécurité peut inclure un conseil qui effectue un contrôle de sécurité avant d'accéder à un compte bancaire. Le point de jonction définit les moments ( points d'accès ) où l'accès à un compte bancaire est autorisé, et le code du corps du conseil définit comment le contrôle de sécurité est implémenté. Ainsi, le contrôle et les points d'accès sont gérés au même endroit. De plus, un point de jonction bien conçu peut anticiper les modifications ultérieures du programme ; par conséquent, si un autre développeur crée une nouvelle méthode d'accès au compte bancaire, le conseil s'appliquera à cette nouvelle méthode lors de son exécution.
Donc, pour l'exemple ci-dessus d'implémentation de la journalisation dans un aspect :
aspect Logger { Logger logger ;void Bank.transfer ( Account fromAcc , Account toAcc , int amount , User user ) { logger.info ( " Transfert d' argent ..." ); }void Bank.getMoneyBack ( User user , int transactionId ) { logger.info ( " L' utilisateur a demandé un remboursement. " ) ; }// Autre code transversal. }
On peut considérer la programmation orientée aspect (AOP) comme un outil de débogage ou un outil destiné à l'utilisateur. Les conseils devraient être réservés aux cas où il est impossible de modifier la fonction (niveau utilisateur) ou lorsqu'on ne souhaite pas la modifier dans le code de production (débogage).
Modèles de points de jonction
La composante relative aux conseils d'un langage orienté aspect définit un modèle de point de jonction (JPM). Un JPM définit trois éléments :
- Lorsque le comportement peut être exécuté, on parle de points de jonction . Ce sont des points d'un programme en cours d'exécution où des comportements supplémentaires peuvent être utilement intégrés. Pour être utile, un point de jonction doit être accessible et compréhensible par un programmeur lambda. Il doit également rester stable face à des modifications mineures du programme afin de garantir la stabilité des aspects. De nombreuses implémentations de la programmation orientée aspect (AOP) prennent en charge l'exécution de méthodes et les références de champs comme points de jonction.
- Un moyen de spécifier (ou de quantifier ) les points de jonction, appelés pointcuts . Les pointcuts déterminent si un point de jonction donné correspond. La plupart des langages de pointcuts utiles utilisent une syntaxe similaire à celle du langage de base (par exemple, AspectJ utilise les signatures Java) et permettent la réutilisation grâce à la dénomination et à la combinaison.
- Un moyen de spécifier du code à exécuter à un point de jonction. AspectJ appelle cela un conseil et peut l'exécuter avant, après et autour des points de jonction. Certaines implémentations permettent également de définir une méthode dans un aspect sur une autre classe.
Les modèles de points de jonction peuvent être comparés en fonction des points de jonction exposés, de la manière dont ces points sont spécifiés, des opérations autorisées à ces points et des améliorations structurelles qui peuvent être exprimées.
Modèle de point de jonction d'AspectJ
- Les points de jonction d'AspectJ comprennent l'appel ou l'exécution de méthodes ou de constructeurs, l'initialisation d'une classe ou d'un objet, l'accès en lecture et en écriture aux champs et les gestionnaires d'exceptions. Ils n'incluent pas les boucles, les appels à la superclasse, les clauses throws ni les instructions multiples.
- Les points de coupure sont spécifiés par des combinaisons de désignateurs de points de coupure primitifs (PCD).
Les PCD « apparentés » correspondent à un type particulier de point de jonction (par exemple, l'exécution d'une méthode) et prennent souvent une signature de type Java en entrée. Voici à quoi ressemble un de ces point de jonction :
exécution ( * ensemble * ( * ))
Ce point de jonction correspond à un point d'exécution de méthode si le nom de la méthode commence par "
set" et qu'il y a exactement un argument de n'importe quel type.Les PCD « dynamiques » vérifient les types d'exécution et lient les variables. Par exemple,
ce ( Point )
Ce point de jonction est valide lorsque l'objet en cours d'exécution est une instance de la classe
Point. Notez que le nom non qualifié d'une classe peut être utilisé via la recherche de type standard de Java.Les PCD de type « portée » limitent la portée lexicale du point de jonction. Par exemple :
dans ( com.entreprise . * )
Ce point de jonction correspond à n'importe quel point de jonction, quel que soit son type, dans le
com.companypaquetage. Il*s'agit d'une forme de caractère générique permettant de faire correspondre plusieurs éléments avec une seule signature.Les pointcuts peuvent être composés et nommés pour être réutilisés. Par exemple :
Ce point de jonction correspond à un point d'exécution de méthode si le nom de la méthode commence par «pointcut set () : execution ( * set * ( * ) ) && this ( Point ) && within ( com . company . * );
set» etthisest une instance du typePointducom.companypackage. Il peut être référencé par le nom «set()». - La directive Advice spécifie l'exécution d'un certain code (spécifié comme du code dans une méthode) à un point de jonction (défini par un pointcut). L'environnement d'exécution AOP invoque automatiquement Advice lorsque le pointcut correspond au point de jonction. Par exemple : Cela signifie concrètement : « si le
après () : set () { Display . update (); }
set()point de jonction correspond au point de raccord, exécutez le codeDisplay.update()une fois le point de raccord établi. »
Autres modèles de points de jonction potentiels
Il existe d'autres types de JPM. Tous les langages de conseil peuvent être définis en fonction de leur JPM. Par exemple, un langage d'aspects hypothétique pour UML pourrait avoir le JPM suivant :
- Les points de jonction sont tous des éléments du modèle.
- Les pointcuts sont des expressions booléennes combinant les éléments du modèle.
- Le moyen d'action à ces points est une visualisation de tous les points de jonction correspondants.
Déclarations inter-types
Les déclarations inter-types permettent d'exprimer des problématiques transversales affectant la structure des modules. Également appelées classes ouvertes et méthodes d'extension , elles permettent aux programmeurs de déclarer en un seul endroit les membres ou les classes parentes d'une autre classe, généralement pour regrouper tout le code relatif à une problématique dans un seul aspect. Par exemple, si un programmeur implémente la problématique transversale d'affichage et de mise à jour à l'aide de visiteurs, une déclaration inter-types utilisant le modèle Visiteur pourrait ressembler à ceci en AspectJ :
aspect DisplayUpdate { void Point . acceptVisitor ( Visitor v ) { v . visit ( this ); } // autre code transversal... }
Cet extrait de code ajoute la acceptVisitorméthode à la Pointclasse.
Toute modification structurelle doit être compatible avec la classe d'origine, afin que les clients de la classe existante continuent de fonctionner, sauf si l'implémentation AOP peut s'attendre à contrôler tous les clients en permanence.
Mise en œuvre
Les programmes AOP peuvent affecter d'autres programmes de deux manières différentes, selon les langages et environnements sous-jacents :
- Il en résulte un programme combiné, valide dans le langage d'origine et indiscernable d'un programme ordinaire pour l'interpréteur final.
- L'interpréteur ou l'environnement final est mis à jour pour comprendre et implémenter les fonctionnalités AOP.
La difficulté de modifier les environnements fait que la plupart des implémentations produisent des programmes combinés compatibles grâce à un type de transformation de programme appelé tissage . Un tisseur d'aspects lit le code orienté aspect et génère le code orienté objet approprié intégrant les aspects. Un même langage AOP peut être implémenté par diverses méthodes de tissage ; la sémantique d'un langage ne doit donc jamais être comprise en fonction de son implémentation de tissage. Seules la vitesse d'exécution et la facilité de déploiement sont affectées par la méthode de combinaison utilisée.
Les systèmes peuvent implémenter le tissage au niveau du code source à l'aide de préprocesseurs (comme c'était le cas initialement pour C++ dans CFront ), ce qui nécessite l'accès aux fichiers sources du programme. Cependant, la structure binaire bien définie de Java permet aux outils de tissage de bytecode de fonctionner avec n'importe quel programme Java sous forme de fichier .class. Ces outils peuvent être déployés lors de la compilation ou, si le modèle de tissage est par classe, lors du chargement des classes. AspectJ a commencé avec le tissage au niveau du code source en 2001, a proposé un outil de tissage de bytecode par classe en 2002 et a offert une prise en charge avancée au chargement après l'intégration d' AspectWerkz en 2005.
Toute solution combinant des programmes à l'exécution doit fournir des vues permettant de les séparer correctement afin de préserver le modèle de programmation segmenté. La prise en charge par Java de plusieurs fichiers sources via le bytecode permet à tout débogueur de parcourir pas à pas un fichier .class correctement construit dans un éditeur de code source. Cependant, certains décompilateurs tiers ne peuvent pas traiter le code construit car ils attendent du code produit par Javac et non toutes les formes de bytecode prises en charge (voir également la section « Critiques » ci-dessous).
Le tissage au moment du déploiement offre une autre approche. Il s'agit essentiellement d'un post-traitement, mais au lieu de modifier le code généré, cette approche de tissage crée des sous-classes de classes existantes afin que les modifications soient introduites par redéfinition de méthodes. Les classes existantes restent intactes, même à l'exécution, et tous les outils existants, tels que les débogueurs et les profileurs, peuvent être utilisés pendant le développement. Une approche similaire a déjà fait ses preuves dans la mise en œuvre de nombreux serveurs d'applications Java EE , tels que WebSphere d' IBM .
Terminologie
La terminologie standard utilisée en programmation orientée aspect peut inclure :
- Préoccupations transversales
- Bien que la plupart des classes d'un modèle orienté objet remplissent une fonction unique et spécifique, elles partagent souvent des exigences secondaires communes. Par exemple, il peut être nécessaire d'ajouter des journaux aux classes de la couche d'accès aux données, ainsi qu'à celles de la couche d'interface utilisateur, lorsqu'un thread entre ou sort d'une méthode. D'autres aspects peuvent concerner la sécurité, tels que le contrôle d'accès ou le contrôle du flux d'informations . Malgré une fonctionnalité principale très différente pour chaque classe, le code nécessaire à l'exécution des fonctionnalités secondaires est souvent identique.
- Conseil
- Voici le code supplémentaire que vous souhaitez appliquer à votre modèle existant. Dans notre exemple, il s'agit du code de journalisation que nous souhaitons appliquer chaque fois que le thread entre ou sort d'une méthode.
- Pointcut
- Cela fait référence au point d'exécution de l'application où une approche transversale doit être appliquée. Dans notre exemple, un point de jonction est atteint lorsque le thread entre dans une méthode, et un autre lorsqu'il en sort.
- Aspect
- La combinaison du point de jonction et du conseil est appelée un aspect. Dans l'exemple ci-dessus, nous ajoutons un aspect de journalisation à notre application en définissant un point de jonction et en fournissant le conseil approprié.
Comparaison avec d'autres paradigmes de programmation
Les aspects sont issus de la programmation orientée objet et de la programmation réflexive . Les langages AOP offrent des fonctionnalités similaires aux protocoles de méta-objets , mais plus restreintes . Les aspects sont étroitement liés à des concepts de programmation tels que les sujets , les mixins et la délégation . Parmi les autres applications des paradigmes de programmation orientée aspect, on peut citer les filtres de composition et l' approche par hypertranches . Depuis au moins les années 1970, les développeurs utilisent des techniques d'interception et de dispatch-patching qui rappellent certaines méthodes d'implémentation de l'AOP, mais celles-ci n'ont jamais bénéficié de la sémantique centralisée des spécifications transversales.
Les concepteurs ont envisagé d'autres moyens de séparer le code, tels que les types partiels de C# , mais ces approches manquent d'un mécanisme de quantification permettant d'atteindre plusieurs points de jonction du code avec une seule instruction déclarative.
Bien que cela puisse paraître sans rapport, lors des tests, l'utilisation de mocks ou de stubs requiert le recours à des techniques de programmation orientée aspect (AOP), comme le conseil « around ». Dans ce contexte, les objets collaborant constituent un élément transversal pour les besoins du test. C'est pourquoi les différents frameworks d'objets mock offrent ces fonctionnalités. Par exemple, un processus appelle un service pour obtenir le solde de ses services. Lors du test de ce processus, l'origine du solde importe peu ; l'essentiel est que le processus l'utilise conformément aux exigences.
Problèmes d'adoption
Les programmeurs doivent être capables de lire et de comprendre le code pour éviter les erreurs. Même avec une formation adéquate, la compréhension des problématiques transversales peut s'avérer difficile sans un outil approprié pour visualiser à la fois la structure statique et le flux dynamique d'un programme. Dès 2002, AspectJ a commencé à fournir des extensions pour environnements de développement intégrés (IDE) afin de faciliter la visualisation des problématiques transversales. Ces fonctionnalités, ainsi que l'assistance à la programmation par aspects et la refactorisation , sont désormais courantes.
Compte tenu de la puissance de la programmation orientée aspect (AOP), une erreur logique dans l'expression des aspects transversaux peut entraîner une défaillance généralisée du programme. Inversement, un autre programmeur peut modifier les points de jonction d'un programme, par exemple en renommant ou en déplaçant des méthodes, d'une manière imprévue par le concepteur de l'aspect et avec des conséquences inattendues . L'un des avantages de la modularisation des préoccupations transversales est de permettre à un seul programmeur d'affecter facilement l'ensemble du système. Par conséquent, ces problèmes se manifestent par un conflit de responsabilité entre deux développeurs ou plus pour une défaillance donnée. L'AOP peut accélérer la résolution de ces problèmes, car seul l'aspect doit être modifié. Sans AOP, les problèmes correspondants peuvent être beaucoup plus disséminés.
Critique
La critique la plus fondamentale adressée à l'effet de la programmation orientée aspect (AOP) est l'obscurcissement du flux de contrôle. Non seulement cet effet est-il pire que celui de l' instruction GOTO , pourtant décriée , mais il est aussi très similaire à l' instruction COME FROM , souvent utilisée à tort et à travers. L' application aveugle de l'instruction , élément fondamental de nombreuses définitions de l'AOP (le code en question n'indique pas qu'une instruction sera appliquée ; celle-ci est spécifiée dans le point de jonction), signifie que l'instruction n'est pas visible, contrairement à un appel de méthode explicite. Par exemple, comparez avec le programme COME FROM :
5 ENTRÉE X 10 AFFICHER 'Résultat :' 15 AFFICHER X 20 PROVENANCE DE 10 25 X = X * X 30 RETOUR
avec un fragment AOP à sémantique analogue :
main () { entrée x print ( résultat ( x )) }input result ( int x ) { return x } around ( int x ): call ( result ( int )) && args ( x ) { int temp = proceed ( x ) return temp * temp }
En effet, le point de coupure peut dépendre des conditions d'exécution et ne pas être statiquement déterministe. L'analyse statique et l'aide de l'IDE pour identifier les recommandations potentiellement pertinentes permettent d'atténuer ce problème, mais ne le résolvent pas .
Les critiques générales reprochent à la programmation orientée aspect (AOP) d'améliorer « à la fois la modularité et la structure du code », mais certains rétorquent qu'elle compromet au contraire ces objectifs et entrave « le développement indépendant et la compréhension des programmes » . Plus précisément, la quantification par points de jonction rompt la modularité : « il faut, en général, avoir une connaissance globale du programme pour raisonner sur l'exécution dynamique d'un programme orienté aspect » . De plus, bien que ses objectifs (modulariser les préoccupations transversales) soient bien compris, sa définition précise reste floue et ne se distingue pas clairement d'autres techniques bien établies . Les préoccupations transversales peuvent s'interférer mutuellement, nécessitant un mécanisme de résolution, tel qu'un ordre . En effet, les aspects peuvent s'appliquer à eux-mêmes, ce qui conduit à des problèmes comme le paradoxe du menteur .
Les critiques techniques soulignent que la quantification des points de jonction (définissant l'exécution des instructions) est « extrêmement sensible aux modifications du programme », un problème connu sous le nom de problème de la fragilité des points de jonction . Les problèmes liés aux points de jonction sont considérés comme insolubles. Remplacer la quantification des points de jonction par des annotations explicites conduit à la programmation orientée attributs (POA) , qui se résume à un appel de sous-programme explicite et souffre du même problème de dispersion, que la POA visait justement à résoudre
Mises en œuvre
De nombreux langages de programmation ont implémenté la programmation orientée aspect (AOP), soit au sein du langage, soit sous forme de bibliothèque externe , notamment :
- Langages du framework .NET ( C# , Visual Basic (.NET) (VB.NET))
- PostSharp est une implémentation AOP commerciale avec une édition gratuite mais limitée.
- Unity fournit une API pour faciliter les bonnes pratiques éprouvées dans les domaines fondamentaux de la programmation, notamment l'accès aux données, la sécurité, la journalisation, la gestion des exceptions et autres.
- AspectDN est une implémentation AOP permettant d'intégrer directement les aspects dans les fichiers exécutables .NET.
- ActionScript
- Ada
- AutoHotkey
- C , C++
- COBOL
- Les frameworks Cocoa Objective-C
- ColdFusion
- Common Lisp
- Delphes
- Prisme de Delphes
- e (IEEE 1647)
- Emacs Lisp
- Groovy
- Haskell
- Java
- JavaScript
- Logtalk
- Lua
- faire
- Matlab
- ML
- Nemerle
- Perl
- PHP
- Prologue
- Python
- Raquette
- Rubis
- Squeak Smalltalk
- UML 2.0
- XML