Trouvez une théorie nouvelle et testable
Maintenant que nous avons éliminé la théorie précédente, nous devons en trouver une nouvelle. Pour commencer, regardons à nouveau la méthode DragonSaddleSizeEstimator::estimateSaddleSizeInCentiMeters(int targetYear)
. Observez la méthodeestimateSaddleSizeInCentiMeters(int targetYear)
:
public Double estimateSaddleSizeInCentiMeters(int targetYear) throws Exception {
double roundedSaddleSize = calculateSaddleSizeFromYear(targetYear);
// slow down the magic
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
throw e;
}
// Verify that we have a valid saddle size
verifier.verify(roundedSaddleSize);
return roundedSaddleSize;
}
À présent, sur cette base, nous pouvons dire que :
Nous avons éliminé la théorie selon laquelle l’année cible était modifiée. Elle était toujours définie comme 2020 au point où nous avons appelé la méthode levant notre exception observée à la ligne 13.
Nous savons également que la valeur de la taille de selle passée à cette méthode est déjà un nombre négatif, ce qui implique que le bug existe quelque part dans la classe où la taille de la selle est calculée :
DragonSaddleSizeEstimator
. Cela semble se produire dans la méthode privéecalculateSaddleSizeFromYear()
à la ligne 2.
Si vous regardez cette méthode privée, vous voyez une série d’étapes assez étrange – après tout, il s’agit d’un algorithme fondé sur le mysticisme et les dragons. 💫 La méthode prend une année comme information entrante et renvoie une estimation de taille de selle :
private double calculateSaddleSizeFromYear(int targetYear) {
// ((42-1)/41.0)
double universalLuckyNumber = new Double(UNIVERSAL_LUCKY_NUMBER);
double mysticalMultiplier = (copyOfUniversalConstant - yearOfBirth)/ universalLuckyNumber;
// Start by setting the saddle size to the dragon's current age
int saddleSizeFactor = 0;
// Count down how many years it's been alive
for (int i = targetYear; i>DRAGON_SPAWN_YEAR; i--) {
saddleSizeFactor++;
if (i < UNIVERSAL_CONSTANT) {
int modifier = enchant();
saddleSizeFactor += modifier;
}
}
// calculate the final saddle size
double exactSaddleSize = (saddleSizeFactor * mysticalMultiplier) - mysticalMultiplier /10;
return (double) Math.round(exactSaddleSize);
}
Lignes 3 à 4 : on dirait du charabia, mais ce sont des constantes métier que nous multiplions par la taille de selle. Pensez-y comme faisant partie des règles métier.
Lignes 6 à 13 : nous comptons les années depuis
DRAGON_SPAWN_YEAR
(ce qui, selon l’histoire du README.md, doit être l’an 1 après J.-C.).Lignes 11 à 12 : il y a aussi une variable modifier particulière ajoutée à notre estimation. Cela se produit quand la variable compteur (mal nommée au passage), i. est inférieure à
UNIVERSAL_CONSTANT
, que le code initialise à 42.Lignes 16 à 17 : le chiffre est agréablement mystifié, converti en centimètres et arrondi.
Jusqu’à ce que nous ayons étudié la science du calcul des tailles de selle, un grand nombre de ces règles métier resteront un mystère ; néanmoins, nous pouvons nous concentrer sur le flux général en tant que développeurs Java :
une valeur est passée à une méthode ;
calcul de certaines valeurs d’entiers en utilisant des variables statiques et des champs de l’objet actuel ;
boucle et exécution d’un peu d’arithmétique pour incrémenter la variable saddleSizeFactor ;
applique une formule à saddleSizeFactor ;
caste le résultat en un double et le renvoie !
Tout ceci est utile, mais quelle est notre prochaine théorie ?
En regardant les lignes 6 à 13 ci-dessus, nous pouvons supposer que si nous comptons en descendant de l’année 2020 à l’an 1 après J.-C., nous devrions obtenir une taille de selle de 2019 au minimum. Nous avons vu que nous avions un nombre négatif au point d’appel de DragonSaddleSizeVerifier::verif
y, donc quelque chose le rend négatif.
Étant donné qu’il y a peu de visibilité sur ce que fait le modificateur à la ligne 12, nous allons supposer qu’il ajoute un nombre négatif à notre taille de selle.🕵️ C’est ce qui pourrait causer le changement de notre valeur positive en valeur négative et, ensuite, faire que le vérificateur lève une InvalidSaddleSizeException
. Plongeons-nous dans cette boucle for et enquêtons !
Débuggez dans une boucle en utilisant des points d’arrêt conditionnels
Comment débugger dans une boucle for ? Réfléchissons de façon logique. Lorsque vous faites vos courses alimentaires, est-ce que vous parcourez toutes les allées et examinez chaque élément avant de trouver ce que vous voulez ? Probablement pas. J’imagine que cela rendrait vos courses inutilement longues, et que vous vous retrouveriez avec des tas d’achats spontanés de choses que vous n’utiliseriez jamais. Alors, que faites-vous ? Vous avez habituellement une idée de ce que vous cherchez et vous allez directement dans les allées ou les sections appropriées. Si vous ne voulez que des lasagnes, vous n’avez pas besoin d’inspecter toutes les marques de riz !
Cette situation au supermarché n’est pas si éloignée de ce qui se produit dans le code. Par exemple, dans une boucle for, le même bloc est revisité encore et encore, mais avec des données différentes. Si vous placez un point d’arrêt dans une boucle for, il sera suspendu de nombreuses fois. Cela signifie que lorsque vous enquêtez sur un problème, vous devrez passer au crible manuellement des tas de suspensions, qui ne sont peut-être même pas nécessaires à votre enquête. Cela ressemble d’un peu trop près au cas des lasagnes et du riz ci-dessus. Une des façons d’éviter cela consiste à utiliser des points d’arrêt conditionnels, des points d’arrêt intelligents qui s’arrêtent quand les bonnes données sont visibles à votre code.
Ça ne me semble pas si mal…
Si vous n’êtes pas convaincu que c’est fastidieux, regardons un exemple de notre code. Pour mener l’enquête et voir si notre taille de selle était modifiée de façon incorrecte dans notre boucle for, nous allons placer un point d’arrêt à la ligne 11 dans la boucle for sur l’affirmation if (i < UNIVERSAL_CONSTANT) {
. Voyons si nous pouvons débugger cela en plaçant un point d’arrêt dans cette boucle for !
Avez-vous vu ce qu’il s’est passé ? Le débugger a essayé de nous aider en s’arrêtant à chacune des itérations sur la condition if (i < UNIVERSAL_CONSTANT) {
. C’est très bien, mais cela ajoute peu de valeur et beaucoup de travail, si nous devons le vérifier 2019 fois. Est-ce que ce ne serait pas génial si nous pouvions uniquement l’inspecter à la 2019e boucle ?
Mais ce code est tout simplement faux. Pourquoi devons-nous même boucler ? Ne devrions-nous pas simplement le réparer ?
C’est vrai, le code pourrait être amélioré. Peut-être pourrions-nous le réécrire pour éviter complètement la boucle for. Malheureusement, il est rare que vous puissiez choisir l’état du code que l’on vous confie. En supposant que la personne qui vous a précédé a fait de son mieux, résistez à la tentation de réécrire le code avant de savoir ce qui ne va pas.
La réécriture du code pour le rendre plus lisible est une bonne chose, mais pas tant qu’il est peut-être cassé. Vous pourriez introduire des problèmes supplémentaires. De plus, comme vous savez qu’il est cassé, vous pourriez avoir à modifier le code que vous réécrivez maintenant.
Alors, revenons à notre code. Comment pouvons-nous le débugger sans être obligés de cliquer sur « resume » 2 019 fois ?
Les points d’arrêt conditionnels sont des points d’arrêt intelligents qui s’arrêtent quand les bonnes données sont visibles par votre code. Avec les points d’arrêt conditionnels, vous pouvez créer un point d’arrêt qui s’arrêtera uniquement (c’est-à-dire que le débugger s’arrêtera) quand une affirmation Java sera évaluée comme vraie. Par exemple, dans le cas de cette boucle, nous pourrions nous arrêter suri==42
ou i<UNIVERSAL_CONSTANT
. Utilisons cela pour nous aider à débugger l’application !
Comme vous l’avez vu avec notre point d’arrêt non conditionnel dans la boucle for, il peut être peu pratique de définir un point d’arrêt uniquement nécessaire à certaines requêtes. Nous pouvons définir un point d’arrêt conditionnel ici par un clic droit sur le point d’arrêt existant et en spécifiant une expression booléenne.
En définissant ce point d’arrêt conditionnel, la JVM s’arrêtera toujours quand l’expression sera évaluée comme vraie. Vous pouvez y penser comme à l’ajout d’une déclaration if dans votre code juste avant le point d’arrêt signifié, qui fait quelque chose comme if(conditional_expression) { suspendTheJVM! }
.
Maintenant que nous avons défini un point d’arrêt conditionnel avant la déclaration if dans notre boucle for, débuggons le test et poursuivons l’enquête ! 🕵️
Comme vous le voyez, en spécifiant un point d’arrêt conditionnel, nous avons pu éviter d’avoir à suspendre et reprendre des milliers de fois ! La première fois que notre débugger s’est arrêté a été quand i a eu une valeur de 41. Nous avons même pu changer le point d’arrêt conditionnel en plein débug et passer directement à la suite, en changeant notre condition en i < 3
.
Alors, est-ce que c’était la variable modifier qui était la cause de notre bug ?
Pour examiner l’impact de la valeur modifier, nous avons utilisé le bouton de contrôle d’exécution Step Over
pour reprendre l’exécution, mais immédiatement suspendre la JVM à la ligne suivante.
Cela nous a permis de descendre dans le code et d’inspecter la valeur retour de la variable modifier. De façon intéressante, elle ne renvoie jamais que zéro. Cela n’a donc pas d’impact.
Un détail que nous avons remarqué est que la variablemysticalMultiplier
a un nombre négatif :
Bizarrement, elle est utilisée pour estimer la taille de la selle en la multipliant avec le compteursaddleSizeFactor
que nous avons incrémenté dans notre boucle for.
La formule finale pour notre taille de selle est définie dans le code comme suit :
double exactSaddleSize = (saddleSizeFactor * mysticalMultiplier) - mysticalMultiplier /10;
En substituant par les valeurs de notre volet Variables :
(2019*-0.024390243902439025)
-0.024390243902439025 / 10
= 49.24146341463415
Regardons à nouveau notre exception qui bugge :
com.openclassrooms.debugging.exception.InvalidSaddleSizeException: Unexpected saddle size:-49.0
Il y a une ressemblance surprenante ici. L’une est -49 et l’autre 49,24146... Nous avons une nouvelle piste sur laquelle enquêter. 🕵️♀️
En résumé
Les points d’arrêt conditionnels vous permettent de suspendre la JVM (par le biais de votre débugger) à une ligne donnée, si une condition est remplie.
Les points d’arrêts conditionnels sont spécifiés en définissant une expression booléenne en Java, qui est évaluée en utilisant des variables visibles par, ou dans le périmètre du code à ce point d’arrêt.
Les points d’arrêt conditionnels sont particulièrement utiles lorsque l’on débugge du code qui est revisité de nombreuses fois, comme avec une boucle for.
Dans le chapitre suivant, nous allons regarder certaines façons plus spécialisées de réparer des bugs !