Skip to main content

Fonctionnalités avancées du débogueur IntelliJ que vous ne connaissez peut-être pas

Écrit par
feature datacenters black

30 janvier 2023

0 minutes de lecture

Je viens de terminer la rédaction de mon livre sur le débogage et d’un cours sur le débogage. Depuis, on me demande souvent quelles sont mes fonctionnalités de débogage préférées. Le débogage ne se limite pas au débogueur de l’IDE. En fait, seul le premier chapitre du livre traite de cet aspect. Pourtant, quand on pense au débogage, on pense tout de suite à l’IDE. Or, ces outils formidables recèlent encore bien des fonctionnalités à découvrir.

La raison fondamentale est simple : nous n’avons jamais appris à déboguer. Comme il est impossible de tester les connaissances en débogage, les universités ne l’enseignent pas.

Nous l’apprenons sur le tas, au fil du temps, et c’est loin d’être idéal. Pas étonnant que certains développeurs considèrent le débogage comme une corvée — ils se bouchent le nez et se précipitent vers la sortie pour s’en débarrasser. Le débogage ne se résume pas à la somme de ses composantes, et les débogueurs sont des outils formidables qui nous aident à comprendre en profondeur notre travail. Ils révèlent les rouages internes de l’application sous un jour entièrement nouveau et nous offrent un niveau de compréhension que peu d’autres disciplines peuvent égaler.

Dans cet article, nous présentons trois fonctionnalités du débogueur qui, d’après mon expérience, plaisent particulièrement au public lors de mes conférences sur le sujet. Elles devraient toutes fonctionner avec la plupart des IDE JetBrains, mais, malheureusement, aucune des trois n’est disponible dans VS Code. Je pense que l’équipe de VS Code a fait ce choix délibérément pour simplifier l’expérience utilisateur — un choix que j’espère la voir reconsidérer.

Trois fonctionnalités de débogage d’IntelliJ :

  • Configuration du débogage avec des objets marqueurs

  • Rendu des éléments

  • Suivi de la mémoire

Configuration du débogage IntelliJ avec des objets marqueurs

L’un des principaux défis du débogage consiste à garder une trace de son travail. Nous nous arrêtons sur un point d’arrêt et observons un objet. Est-ce celui que nous avons vu précédemment ? A-t-il la même référence, les mêmes valeurs, la même identité, la même adresse mémoire ?

Avant, je notais les adresses mémoire sur un bout de papier pour garder une trace de ce que j’avais vu et vérifier qu’il ne s’agissait pas d’une nouvelle adresse. C’est frustrant et déroutant. Je n’arrive même pas à me relire… Il doit bien y avoir une meilleure façon de faire.

Et il y en a une. Lorsque nous examinons un objet dans le débogueur, au lieu de noter sa valeur, nous pouvons le marquer à l’aide du menu contextuel.

Utilisez le menu contextuel pour marquer des objets dans votre débogueur.

Après avoir sélectionné cette option, nous sommes invités à donner un nom à l’objet. Cette boîte de dialogue peut prêter à confusion. Vous pourriez penser, à tort, qu’il s’agit simplement d’un alias pour l’expression surveillée. Ce n’est pas le cas.

Utilisez un libellé d’objet pour définir une nouvelle variable globale.

Nous définissons ici une nouvelle variable globale. Nous pouvons en examiner la valeur dans les expressions surveillées, quel que soit le contexte (ce qui est déjà très pratique), mais aussi l’utiliser dans des conditions, afin que le point d’arrêt ne soit atteint que si la valeur change — comme vous pouvez le voir ici.

Configuration d’une instruction conditionnelle.

C’est remarquablement puissant. Nous pouvons l’utiliser pour détecter les problèmes liés aux threads en marquant le thread actuel. Vous pouvez aussi suivre des objets ayant la même identité, mais des pointeurs différents. C’est notamment utile avec le mapping objet-relationnel, où deux instances de la même entité peuvent coexister. Ces bogues sont très difficiles à repérer.

Rendu des éléments

Le panneau des expressions surveillées est l’une des fonctionnalités les plus formidables des IDE modernes. À mesure que nous parcourons le code, il nous donne en un coup d’œil toutes les informations nécessaires. Mais parfois, ce coup d’œil tourne à la chasse au trésor : il faut développer les variables et explorer une hiérarchie complexe pour trouver la valeur qui nous intéresse.

En Java, une solution courante consiste à redéfinir toString() pour renvoyer une valeur plus descriptive. Mais plusieurs raisons peuvent m’en dissuader :

  • Il se peut que l’objet ne m’appartienne pas, mais qu’il soit défini par le framework.

  • C’est peut-être une modification qui ne me concerne que moi. Je ne veux pas changer toString() pour tout le monde.

  • toString() est important et peut avoir un impact considérable sur les performances de l’application (à cause du coût de la journalisation, par exemple).

  • toString() est trop basique. Et si je veux examiner une variable contenant une liste d’éléments ?

Les renderers nous permettent de tirer parti du panneau des expressions surveillées sans ces inconvénients. Je vais vous le montrer à partir d’une version légèrement modifiée de la démo Spring Pet Clinic. Cette démo utilise l’API Java Persistence (JPA) pour le stockage. Spring Data prend en charge le concept de repository, que l’on peut assimiler à une table de base de données. Ce n’est pas tout à fait exact, car un repository est une abstraction plus large, mais c’est une approximation généralement utile.

Lorsqu’un point d’arrêt est atteint, nous voyons généralement le fouillis inutile illustré ci-dessous. visitRepository est un repository, mais il ne m’apporte aucune information utile pour le débogage. Combien d’éléments contient la table ? Quelles sont leurs valeurs ?

Je peux développer davantage l’expression surveillée, mais cela ne me mènera nulle part : cet objet proxy ne fournit aucune information utile.

Les points d’arrêt peuvent afficher beaucoup d’informations superflues et rendre difficile la recherche des données dont nous avons besoin.

Les renderers permettent de faire mieux. Pour commencer, faites un clic droit dans le panneau des expressions surveillées et sélectionnez Customize Data Views.

Commencez à configurer votre moteur de rendu en cliquant sur « Personnaliser les vues de données ».

Dans la boîte de dialogue qui s’affiche, je peux définir les éléments de mon renderer pour obtenir un résultat bien plus utile. Le panneau des expressions surveillées affiche désormais le nombre d’éléments en un coup d’œil et permet d’examiner chacun d’eux individuellement, si nécessaire.

Nos paramètres personnalisés pour le JPARepository apparaissent à gauche, tandis qu’une liste d’éléments du dépôt s’affiche à droite dans la boîte de dialogue du débogueur.

À gauche, vous pouvez voir comment procéder. Nous choisissons de personnaliser JPARepository. Remarquez que perRepository est d’un autre type de repository dans le panneau des expressions surveillées ; il n’a donc pas été affecté. Nous allons ensuite définir le code qui sera exécuté pour effectuer le rendu. Appelez la méthode count() sur l’objet pour obtenir le nombre d’objets, puis créez une chaîne à afficher dans le panneau.

La deuxième partie est encore plus intéressante. Nous pouvons utiliser findAll() pour récupérer les éléments du repository et les afficher lorsque l’objet est développé. La dernière partie détermine si un signe plus doit s’afficher à côté du repository pour permettre son développement.

C’est génial. Mais il y a un inconvénient majeur : il faut faire la même chose pour chaque type d’objet que nous définissons. C’est fastidieux… ou pas ?

Nous pouvons définir des annotations représentant ces configurations et intégrer ainsi des solutions de ce type directement à notre bibliothèque. Le débogage des bibliothèques tierces devient alors beaucoup plus simple.

Suivi de la mémoire

Quand on parle de mémoire, on pense aux outils de profilage. Existe-t-il un meilleur moyen de suivre l’utilisation de la mémoire ?

Suivi de l’allocation d’un objet spécifique, java.lang.String, avec l’option Track New Instances.

Les profileurs sont formidables, c’est indéniable. Mais ce sont des outils généralistes. Ils ne permettent pas d’aller jusqu’au bout lorsqu’on cherche à isoler un bogue. Ils ne conviennent pas non plus au suivi d’un objet égaré ni à l’obtention d’une vue détaillée du système. Par exemple, nous pouvons activer la vue mémoire en cliquant en haut à droite du panneau des expressions surveillées, puis en sélectionnant « Memory ».

Activez la vue mémoire pour suivre tous les objets en mémoire et afficher la liste complète dans la zone de surveillance.

Nous voyons alors la liste des objets en mémoire et pouvons double-cliquer sur chacun d’eux pour afficher la liste complète des objets qu’il contient. La colonne « diff » indique également le nombre d’objets de chaque type alloués entre le point d’arrêt actuel et le précédent.

Suivi de l’allocation d’un objet spécifique, java.lang.String, avec l’option Track New Instances.

C’est déjà remarquable. Mais il y a une limite : nous ne savons pas quels objets ont été alloués à un instant donné. Pour cela, l’IDE devrait suivre chaque allocation de chaque type et conserver une référence à tous les objets créés. Ce n’est pas viable. En revanche, nous pouvons choisir un type d’objet précis, cliquer dessus avec le bouton droit et sélectionner l’option Track New Instances.

Dans la capture d’écran ci-dessus, j’ai fait cela pour java.lang.String. L’icône de surveillance à côté indique que le suivi est activé. Nous pouvons ainsi voir les objets précis qui ont été alloués entre deux points d’arrêt. C’est extrêmement utile pour comprendre ce que fait notre code système en coulisses. Et ce n’est pas tout…

Nous pouvons également obtenir la trace complète de la pile pour chaque allocation d’objet et voir les lignes qui l’ont déclenchée. Cela nous aide à comprendre chaque allocation et les raisons qui la motivent. Nous pouvons ainsi mieux comprendre les rouages internes de systèmes vastes et peu familiers.

C’est le complément idéal de votre profileur.

Pour conclure

J’espère que cet article a produit l’effet « waouh » que je recherchais. Je pense que les développeurs abordent le débogage avec une appréhension injustifiée. Cela s’explique en grande partie par le manque d’outils, de processus et d’accompagnement adapté.

Le débogage devrait être une expérience libératrice. Il nous rend humbles : nous avons tous l’impression de débuter lorsque nous déboguons, mais ce n’est pas une mauvaise chose. Malgré les difficultés, le processus devrait être amusant et stimulant. Nous devrions célébrer les outils à notre disposition et les exploiter pleinement.

Ne traitez pas votre prochain bogue comme un délit de fuite. Voyez-le plutôt comme une aventure, l’occasion de sortir ces nouveaux jouets du garage et de vous attaquer au bogue grâce à vos nouvelles compétences.

Pour aller plus loin :

Publié dans: