Pylint de l'intérieur vers l'extérieur. Comment fait-il

Divers assistants dans l'écriture de code cool nous entourent, linter, typekchera, utilitaire pour trouver des vulnérabilités, tous avec nous. Nous y sommes habitués et nous l'utilisons sans entrer dans les détails comme une «boîte noire». Par exemple, peu de gens comprennent les principes de Pylint, l'un de ces outils indispensables pour optimiser et améliorer le code Python.

Mais Maxim Mazaev sait à quel point il est important de comprendre ses outils, et il nous l'a dit à Moscow Python Conf ++ . À l'aide d'exemples concrets, il a montré comment la connaissance du périphérique interne de Pylint et de ses plug-ins a permis de réduire le temps de révision du code, d'améliorer la qualité du code et généralement d'améliorer l'efficacité du développement. Vous trouverez ci-dessous une instruction de décryptage.



Pourquoi avons-nous besoin de Pylint?


Si vous l'utilisez déjà, la question peut se poser: "Pourquoi savoir ce qu'il y a à l'intérieur de Pylint, comment cette connaissance peut-elle aider?"

En règle générale, les développeurs écrivent du code, lancent le linter, reçoivent des messages sur ce qu'il faut améliorer, comment rendre le code plus beau et apporter les modifications proposées. Maintenant, le code est plus facile à lire et n'a pas honte de montrer à ses collègues.

Pendant longtemps, ils ont travaillé exactement de la même manière avec Pylint au Cyan Institute, avec des ajouts mineurs: ils ont changé les configurations, supprimé les règles inutiles et augmenté la longueur maximale des cordes.

Mais à un moment donné, ils ont rencontré un problème, pour lequel j'ai dû creuser profondément dans Pylint et comprendre comment cela fonctionne. Quel est ce problème et comment le résoudre, lisez la suite.


À propos du conférencier: Maxim Mazaev ( barre oblique inverse ), 5 ans de développement, travaille au CIAN. Apprend en profondeur Python, la programmation asynchrone et fonctionnelle.

À propos du cyan


La plupart croient que CIAN est une agence immobilière avec des agents immobiliers et sont très surpris lorsqu'ils découvrent qu'au lieu d'agents immobiliers, nous avons des programmeurs.

Nous sommes une entreprise technique dans laquelle il n'y a pas d'agents immobiliers, mais il y a beaucoup de programmeurs.

  • 1 million d'utilisateurs uniques par jour.
  • Le plus grand tableau d'affichage pour la vente et la location de biens immobiliers à Moscou et à Saint-Pétersbourg. En 2018, ils sont entrés au niveau fédéral et travaillent dans toute la Russie.
  • Près de 100 personnes dans l'équipe de développement, dont 30 écrivent du code Python quotidiennement.

Chaque jour, des centaines et des milliers de lignes de nouveau code entrent en production. Les exigences pour le code sont assez simples:

  • Code de qualité décente.
  • Homogénéité stylistique. Tous les développeurs doivent écrire du code à peu près similaire, sans "vinaigrette" dans les référentiels.

Pour y parvenir, bien sûr, vous avez besoin d'une révision du code.

Examen du code


L'examen du code dans CIAN se déroule en deux étapes:

  1. La première étape est automatisée . Le robot Jenkins exécute les tests, exécute Pylint et vérifie la cohérence de l'API entre les microservices, puisque nous utilisons des microservices. Si à ce stade les tests échouent ou que le linter montre quelque chose d'étrange, alors c'est une occasion de rejeter la demande d'extraction et d'envoyer le code pour révision.
  2. Si la première étape a réussi, la deuxième vient - l'approbation de deux développeurs . Ils peuvent évaluer la qualité du code en termes de logique métier, approuver une demande d'extraction ou renvoyer le code pour révision.


Problèmes de révision de code


La demande d'extraction peut ne pas réussir l'examen du code en raison de:

  • des erreurs dans la logique métier lorsqu'un développeur a résolu de manière inefficace ou incorrecte un problème;
  • problèmes de style de code.

Quels pourraient être les problèmes de style si le linter vérifie le code?

Tous ceux qui écrivent en Python savent qu'il existe un guide pour écrire du code PEP-8 . Comme toute norme, PEP-8 est assez général et nous, en tant que développeurs, ne sommes pas assez. Je veux spécifier la norme à certains endroits et développer à d'autres.

Par conséquent, nous avons élaboré nos dispositions internes sur la façon dont le code devrait ressembler et fonctionner, et nous les avons appelées «propositions Cian refusées» .



«Decline Cian Proposals» - un ensemble de règles, il y en a maintenant environ 15. Chacune de ces règles est à la base du rejet de la demande et de sa révision.

Qu'est-ce qui empêche une révision productive du code?


Il y a un problème avec nos règles internes - le linter ne les connaît pas, et il serait étrange qu'il le sache - elles sont internes.
Le développeur qui exécute la tâche doit toujours se souvenir et garder les règles à l'esprit. S'il oublie l'une des règles, au cours du processus de révision du code, les réviseurs indiqueront le problème, la tâche sera révisée et le temps de publication de la tâche augmentera. Après l'achèvement et la correction des erreurs, les testeurs doivent se rappeler ce qui était dans la tâche, pour changer de contexte.

Cela crée un problème pour le développeur et les réviseurs. En conséquence, la vitesse de révision du code est considérablement réduite. Au lieu d'analyser la logique du code, les testeurs commencent à analyser le style visuel, c'est-à-dire qu'ils effectuent le travail du linter: ils scannent le code ligne par ligne et recherchent les incohérences dans l'indentation dans le format d'importation.

Nous aimerions nous débarrasser de ce problème.

Mais ne nous écrivez pas votre linter?


Il semble que le problème sera résolu par un outil qui connaîtra tous les accords internes et pourra vérifier le code pour leur mise en œuvre. Nous avons donc besoin de notre propre linter?

Pas vraiment. L'idée est stupide, car nous utilisons déjà Pylint. C'est un linter pratique, apprécié des développeurs et intégré à tous les processus: il s'exécute dans Jenkins, génère de beaux rapports qui sont entièrement satisfaits et sous forme de commentaires viennent tirer la requête. Tout va bien, un deuxième linter n'est pas nécessaire .

Alors, comment résoudre le problème si nous ne voulons pas écrire notre propre linter?

Écrire un plugin Pylint


Vous pouvez écrire des plugins pour Pylint, ils sont appelés checkers. Sous chaque règle interne, vous pouvez écrire votre propre vérificateur, qui le vérifiera.

Prenons deux exemples de ces vérificateurs.

Exemple n ° 1


À un moment donné, il s'est avéré que le code contient de nombreux commentaires de la forme «TODO» - promet de refactoriser, de supprimer le code inutile ou de le réécrire magnifiquement, mais pas maintenant, mais plus tard. Il y a un problème avec de tels commentaires - ils ne vous obligent absolument à rien.

Le problème


Le développeur a écrit une promesse, a exhalé et est allé l'esprit tranquille pour faire la prochaine tâche.


En résumé:

  • les commentaires avec des promesses pendent au fil des ans et ne sont pas suivis;
  • le code est jonché;
  • la dette technique s'accumule depuis des années.

Par exemple, un développeur a promis il y a 3 ans de supprimer quelque chose après une version réussie, mais la version a-t-elle eu lieu dans 3 ans? Peut-être que oui. Dois-je supprimer le code dans ce cas? C'est une grande question, mais probablement pas.

Solution: écrivez votre vérificateur pour Pylint


Vous ne pouvez pas interdire aux développeurs d'écrire de tels commentaires, mais vous pouvez leur faire faire un travail supplémentaire: créez une tâche dans le tracker pour finaliser la promesse. Ensuite, nous ne l'oublierons certainement pas.

Nous devons trouver tous les commentaires du formulaire TODO et nous assurer que chacun d'eux a un lien vers une tâche dans Jira. Écrivons.

Qu'est-ce qu'un vérificateur en termes de Pylint? Il s'agit d'une classe qui hérite de la classe de base du vérificateur et implémente une certaine interface.

class TodoIssueChecker(BaseChecker): _ _implements_ _ = IRawChecker 

Dans notre cas, c'est IRawChecker - le soi-disant vérificateur «brut».

Un vérificateur brut parcourt les lignes d'un fichier et peut effectuer une certaine action sur une ligne. Dans notre cas, sur chaque ligne, le vérificateur recherchera quelque chose de similaire à un commentaire et un lien vers une tâche.

Pour le vérificateur, vous devez déterminer la liste des messages qu'il émettra:

 msgs = { '9999': ('  TODO    ', issue-code-in-todo', ' ')} 

Le message a:

  • la description est courte et longue;
  • code vérificateur et un nom mnémonique court qui détermine de quel type de message il s'agit.

Le code du message a la forme "C1234", dans lequel:

  • La première lettre est clairement standardisée pour différents types de messages: [C] onvention; [W] arning; [E] yog; [F] atal; [R] efactoring. Grâce à la lettre, le rapport montre immédiatement ce qui se passe: un rappel des accords ou des problèmes fatals qui doivent être résolus de toute urgence.
  • 4 nombres aléatoires uniques à Pylint.

Le code est nécessaire pour désactiver la vérification si elle devient inutile. Vous pouvez écrire Pylint: disable et un code alphanumérique court ou un nom mnémonique:

 # Pylint: disable=C9999 # Pylint: disable=issue-code-in-todo 

Les auteurs de Pylint recommandent d'abandonner le code alphanumérique et d'utiliser le mnémonique, c'est plus visuel.

L'étape suivante consiste à définir une méthode appelée process_module .



Le nom est très important. La méthode doit être appelée de cette façon, car Pylint l'appellera ensuite.

Le paramètre de nœud est transmis au module. Dans ce cas, peu importe de quoi il s'agit ou de quel type il s'agit, il est seulement important de se rappeler que le nœud a une méthode de flux qui renvoie un fichier ligne par ligne.

Vous pouvez parcourir le fichier et pour chaque ligne vérifier les commentaires et les liens vers la tâche. S'il y a un commentaire, mais pas de lien, lancez un avertissement du formulaire «issue-code-in-todo» avec le code du vérificateur et le numéro de ligne. L'algorithme est assez simple.

Enregistrez le vérificateur pour que Pylint le sache. Cela se fait par la fonction de registre :

 def register(linter: Pylinter) -> None: linter. register_checker ( TodoIssueChecker(linter) ) 

  • Une instance de Pylint entre dans la fonction.
  • Il appelle la méthode register_checker.
  • Nous passons le vérificateur à la méthode.

Un point important: le module checker doit être en PYTHONPATH pour que Pylint puisse l'importer plus tard.

Un vérificateur enregistré est vérifié par un fichier de test avec des commentaires sans liens vers des tâches.

 $ cat work. # T0D0:   , -! $ pylint work. --load-plugins todo_checker … 

Pour le test, exécutez Pylint, passez-lui le module, utilisez le paramètre load-plugins pour passer le vérificateur et à l'intérieur du linter, exécutez deux phases.

Phase 1. Initialisation du plugin


  • Tous les modules avec plugins sont importés. Pylint a des contrôleurs internes et externes. Ils se réunissent tous et sont importés.
  • Nous nous inscrivons - module.register (self) . Pour chaque vérificateur, la fonction de registre est appelée, où l'instance Pylint est passée.
  • Des contrôles sont effectués: pour la validité des paramètres, pour la présence de messages, d'options et de rapports dans le format correct.

Phase 2. Analyser le pool de vérificateurs


Après la phase 1, il reste une liste complète de différents types de vérificateurs:

  • Vérificateur AST;
  • Vérificateur brut;
  • Vérificateur de jetons.



Dans la liste, nous sélectionnons ceux qui se rapportent à l'interface du vérificateur brut: nous regardons quels vérificateurs implémentent l'interface IRawChecker et les prenons pour nous-mêmes.

Pour chaque vérificateur sélectionné, appelez la méthode checker.process_module (module) et exécutez la vérification.

Résultat


Exécutez à nouveau le vérificateur sur le fichier de test:

 $ cat work. # T0D0:   , -! $ pylint work,  --load-plugins todo_checker : 0,0:   T0D0     (issue-code-in-todo) 

Un message apparaîtra indiquant qu'il y a un commentaire avec TODO et aucun lien vers la tâche.

Le problème est résolu et maintenant dans le processus de révision du code, les développeurs n'ont plus besoin de scanner le code avec leurs yeux, de trouver des commentaires, d'écrire un rappel à l'auteur du code qu'il existe un accord et il est conseillé de laisser un lien. Tout se passe automatiquement et la révision du code est un peu plus rapide.

Exemple n ° 2. arguments-mots-clés


Il existe des fonctions qui prennent des arguments positionnels. S'il y a beaucoup d'arguments, alors quand ils appellent la fonction, il n'est pas très clair où l'argument est et pourquoi il est nécessaire.

Le problème


Par exemple, nous avons une fonction:

 get_offer_by_cian_id( "sale", rue, 859483, ) 

Le code a vente et True, et on ne sait pas ce qu'ils signifient. C'est beaucoup plus pratique lorsque les fonctions dans lesquelles il y a beaucoup d'arguments sont appelées uniquement avec des arguments nommés:

 get_offer_by_cian_id( deal_type="sale", truncate=True, cian_id=859483, ) 

C'est un bon code, dans lequel il est immédiatement clair où se trouve le paramètre et nous ne confondrons pas leur séquence. Essayons d'écrire un vérificateur qui vérifie de tels cas.

Le vérificateur "brut" utilisé dans l'exemple précédent est très difficile à écrire pour un tel cas. Vous pouvez ajouter des expressions régulières super complexes, mais ce code est difficile à lire. Il est bon que Pylint permette d'écrire un autre type de vérificateur basé sur l'arbre de syntaxe abstraite AST , et nous l'utiliserons.

Paroles sur AST


Un arbre de syntaxe AST ou abstraite est une représentation arborescente du code, où le sommet est les opérandes et les feuilles sont des opérateurs.

Par exemple, un appel de fonction, où il y a un argument positionnel et deux arguments nommés, est transformé en un arbre abstrait:


Il existe un sommet de type Appel et il a:

  • attributs de fonction appelés func;
  • une liste d'arguments positionnels args, où il existe un nœud de type Const et une valeur de 112;
  • liste des arguments nommés Mots clés.

La tâche dans ce cas:

  • Trouvez dans le module tous les nœuds de type Appel (appel de fonction).
  • Calculez le nombre total d'arguments pris par la fonction.
  • S'il y a plus de 2 arguments, assurez-vous qu'il n'y a pas d'arguments positionnels dans le nœud.
  • S'il y a des arguments positionnels, affichez un avertissement.


 ll( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ … ]))] 

Du point de vue de Pylint, un vérificateur basé sur AST est une classe qui hérite de la classe de vérificateur de base et implémente l'interface IAstroidChecker :

 class NonKeywordArgsChecker(BaseChecker): -_ _implements_ _ = IAstroidChecker 

Comme dans le premier exemple, la description du vérificateur, le code du message, le nom mnémonique court sont indiqués dans la liste des messages:

 msgs = { '9191': (' ', keyword-only-args', ' ')} 

L'étape suivante consiste à définir la méthode visit_call :

 def visit_call(self, node: Call) 

La méthode n'a pas à être appelée ainsi. La chose la plus importante est le préfixe visit_, puis vient le nom du sommet qui nous intéresse, avec une petite lettre.

  • L'analyseur AST parcourt l'arbre et pour chaque sommet, il cherche à voir si l'interface checkr visit_ <Name> est définie.
  • Si oui, appelez-le.
  • Récursivement passe par tous ses enfants.
  • En quittant un nœud, il appelle la méthode Leave_ <Name>.

Dans cet exemple, la méthode visit_call recevra un nœud de type appel en entrée et verra s'il a plus de deux arguments et si des arguments positionnels sont présents pour lancer un avertissement et transmettre le code au nœud lui-même.

 def visit_call(self, n): if node.args and len(node.args + node.keywords) > 2: self.add_message( 'keyword-only-args', node=node ) 

Nous enregistrons le vérificateur, comme dans l'exemple précédent: nous transférons l'instance Pylint, appelons register_checker, passons le vérificateur lui-même et le démarrons.

 def register(linter: Pylinter) -> None: linter.register_checker( TodoIssueChecker(linter) ) 

Voici un exemple d'appel de fonction de test dans lequel il y a 3 arguments et un seul d'entre eux est nommé:

 $ cat work. get_offers(1, True, deal_type="sale") $ Pylint work.py --load-plugins non_kwargs_checker … 

Il s'agit d'une fonction qui est potentiellement appelée incorrectement de notre point de vue. Lancez Pylint.

La phase d'initialisation du plugin 1 est complètement répétée, comme dans l'exemple précédent.

Phase 2. Analyse du module chez AST


Le code est analysé dans un arbre AST. L'analyse est réalisée par la bibliothèque Astroid .

Pourquoi Astroid, pas AST (stdlib)


Astroid n'utilise pas en interne le module Python AST standard, mais l' analyseur typé AST typed_ast , qui diffère en ce qu'il prend en charge les indications de type PEP 484. Typed_ast est une branche d'AST, une fourchette qui se développe en parallèle. Fait intéressant, il y a les mêmes bogues qui sont dans AST et sont réparés en parallèle.

 from module import Entity def foo(bar): # type: (Entity) -> None return 

Auparavant, Astroid utilisait le module AST standard, dans lequel on pouvait rencontrer le problème de l'utilisation des astuces définies dans les commentaires utilisés dans le second Python. Si vous vérifiez ce code via Pylint, jusqu'à un certain point, il jurerait lors de l'importation inutilisée, car la classe Entity importée n'est présente que dans le commentaire.

À un moment donné sur GitHub, Guido Van Rossum est venu chez Astroid et a déclaré: «Les gars, vous avez Pylint qui jure sur de tels cas, et nous avons un analyseur AST typé qui prend en charge tout cela. Soyons amis! "

Le travail a commencé à bouillir! 2 ans se sont écoulés, ce printemps, Pylint est passé à un analyseur AST typé et a cessé de jurer de telles choses. Les importations de taiphints ne sont plus marquées comme inutilisées.

Astroid utilise un analyseur AST pour analyser le code dans un arbre, puis fait des choses intéressantes lors de sa construction. Par exemple, si vous utilisez import * , il importe tout avec un astérisque et ajoute aux sections locales pour éviter les erreurs avec les importations inutilisées.

Les plugins Transform sont utilisés dans les cas où il existe des modèles complexes basés sur des méta-classes, lorsque tous les attributs sont générés dynamiquement. Dans ce cas, Astroid est très difficile à comprendre. Lors de la vérification, Pylint jure que les modèles n'ont pas un tel attribut lors de son accès, et en utilisant les plugins Transform, vous pouvez résoudre le problème:

  • Aidez Astroid à modifier l'arbre abstrait et à comprendre la nature dynamique de Python.
  • Complétez AST avec des informations utiles.

Un exemple typique est pylint-django . Lorsque vous travaillez avec des modèles django complexes, le linter jure souvent sur des attributs inconnus. Pylint-django résout simplement ce problème.

Phase 3. Analyser le pool de vérificateurs


Nous revenons au vérificateur. Nous avons à nouveau une liste de vérificateurs, à partir de laquelle nous trouvons ceux qui implémentent l'interface du vérificateur AST.

Phase 4. Analyser les vérificateurs par types de nœuds


Ensuite, nous trouvons des méthodes pour chaque vérificateur, elles peuvent être de deux types:

  • visit_ <Nom du nœud>
  • lev_ <nom du nœud>.

Ce serait bien de savoir quels nœuds vous devez appeler pour un nœud en marchant dans un arbre. Par conséquent, ils comprennent le dictionnaire, où la clé est le nom du nœud, la valeur est une liste des vérificateurs intéressés par le fait d'accéder à ce nœud.

 _visit_methods = dict( < > : [checker1, checker2 ... checkerN] ) 

La même chose avec les méthodes de congé: une clé sous la forme d'un nom de nœud, une liste de vérificateurs qui sont intéressés par le fait de sortir de ce nœud.

 _leave_methods = dict( < >: [checker1, checker2 ... checkerN] ) 

Lancez Pylint. Il montre un avertissement que nous avons une fonction où il y a plus de deux arguments et qui contient un argument positionnel:

 $ cat work. get_offers(1, True, deal_type="sale") $ Pylint work.py --load-plugins non_kwargs_checker C: 0, 0:  c >2      (keyword-only-args) 

Le problème est résolu. Désormais, les programmeurs de révision de code n'ont pas besoin de lire les arguments de la fonction; le linter le fera pour eux. Nous avons gagné du temps , du temps pour la révision du code et les tâches vont plus vite en production.

Et pour écrire des tests?


Pylint vous permet d'effectuer des tests unitaires de contrôleurs et c'est très simple. Du point de vue du linter, le test-checker ressemble à une classe qui hérite du CheckerTestCase abstrait. Il est nécessaire d'indiquer le vérificateur qui est en cours de vérification.

 class TestNonKwArgsChecker(CheckerTestCase): CHECKER_CLASS = NonKeywordArgsChecker 

Étape 1. Nous créons un nœud AST de test à partir de la partie du code que nous vérifions.

 node = astroid.extract_node( "get_offers(3, 'magic', 'args')" ) 

Étape 2. Vérifiez que le vérificateur, entrant dans le nœud, lève ou ne jette pas le message correspondant:

 with self.assertAddsMessages(message): self.checker.visit_call(node) 

Tokenchecker


Il existe un autre type de vérificateur appelé TokenChecker . Il fonctionne sur le principe d'un analyseur lexical. Python possède un module tokenize qui fait le travail d'un scanner lexical et divise le code en une liste de jetons. Cela pourrait ressembler à ceci:


Les noms de variable, les noms de fonction et les mots clés deviennent des jetons de type NAME et les délimiteurs, crochets et deux-points deviennent des jetons de type OP. De plus, il existe des jetons distincts pour l'indentation, le saut de ligne et la traduction inverse.

Comment Pylint fonctionne avec TokenChecker:

  • Le module testé est tokenisé.
  • Une énorme liste de jetons est transmise à tous les vérificateurs qui implémentent ITokenChecker et la méthode process_tokens (jetons) est appelée .

Nous n'avons pas trouvé l'utilisation de TokenChecker, mais il y a quelques exemples que Pylint utilise:

  • Vérification orthographique . Par exemple, vous pouvez prendre tous les jetons avec du texte de type et regarder l'alphabétisation lexicale, vérifier les mots des listes de mots vides, etc.
  • Vérifiez les retraits , les espaces.
  • Travaillez avec des cordes . Par exemple, vous pouvez vérifier que Python 3 n'utilise pas de littéraux Unicode ou vérifier que seuls les caractères ASCI sont présents dans la chaîne d'octets.

Conclusions


Nous avons eu un problème avec la révision du code. Les développeurs ont effectué le travail du linter, ont passé leur temps à balayer du code inutile et à informer l'auteur des erreurs. Avec Pylint, nous:

  • Transfert des vérifications de routine au linter, mise en œuvre des accords internes.
  • Augmentation de la vitesse et de la révision du code de qualité.
  • Réduction du nombre de demandes d'extraction rejetées et réduction du temps de passage des tâches en production.

Un simple vérificateur est écrit en une demi-heure, et un complexe en quelques heures. Le vérificateur économise beaucoup plus de temps qu'il n'en faut pour l'écriture et se bat pour plusieurs demandes d'extraction non rejetées.

Vous pouvez en savoir plus sur Pylint et comment écrire des vérificateurs dans la documentation officielle , mais en termes d'écriture de vérificateurs, c'est plutôt médiocre. Par exemple, à propos de TokenChecker, il n'y a qu'une mention, mais pas sur la façon d'écrire le vérificateur lui-même. Plus d'informations sont disponibles dans les sources Pylint sur GitHub . Vous pouvez voir ce que les vérificateurs sont dans le package standard et vous inspirer pour écrire le vôtre.

La connaissance de la conception interne de Pylint économise des heures de travail et simplifie
performances et améliore le code. Économisez votre temps, écrivez un bon code et
utilisez du linter.
La prochaine conférence Moscow Python Conf ++ se tiendra le 5 avril 2019 et vous pouvez déjà réserver un billet anticipé Birf maintenant. Il est encore mieux de recueillir vos réflexions et de demander un rapport, puis la visite sera gratuite et de jolis petits pains seront offerts en prime, y compris du coaching sur la préparation du rapport.

Notre conférence est une plate-forme pour rencontrer des personnes partageant les mêmes idées, les moteurs de l'industrie, pour communiquer et discuter des choses que les développeurs Python aiment: backend et web, collecte et traitement de données, AI / ML, tests, IoT. Comment cela s'est passé à l'automne, regardez le reportage vidéo sur notre chaîne Python et abonnez-vous à la chaîne - bientôt, nous publierons les meilleurs rapports de la conférence en accès gratuit.

Source: https://habr.com/ru/post/fr433474/


All Articles