• 6 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 04/03/2020

Examine Specific Conditions in Your Code Using Conditional Breakpoints

Find a New, Testable Theory

Now that we've eliminated previous theory, we've got to find a a new one.  To start, let's have another look at the method  DragonSaddleSizeEstimator::estimateSaddleSizeInCentiMeters(int targetYear). Check out the estimateSaddleSizeInCentiMeters(int targetYear) method:

    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;
    }

Now, based on that, we can say:

  • We’ve eliminated the theory that the target year was being modified. It was still set to 2019 at the point at which we called the method throwing our observed exception at Line 13.

  • We also know that the value of saddle size passed to this method is already a negative number, implying that the bug exists somewhere in DragonSaddleSizeEstimate  or the class, where the saddle size is calculated. This appears to happen in the private method calculateSaddleSizeFromYear() at Line 2.

If you look at that private method, you see a rather peculiar set of steps; it is, after all, an algorithm based on mysticism and dragons. 💫  The method takes a year as input and returns a saddle size estimate:

    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);
    }
  • Line 3 to 4: Looks like mumbo jumbo, but these are magical business constants we multiply with the saddle size. Think of these as part of the business rules. 

  • Line 6 to 13: We count the years since  DRAGON_SPAWN_YEAR  (which, based on the story in the README.md,  must be AD 1).

  • Line 11 to 12: There is also an odd modifier variable added to our estimate. This occurs when the badly named counter variable, i.  is less than UNIVERSAL_CONSTANT, which the code sets to the number 42.

  • Line 16 to 17: The number is nicely mystified, converted to centimeters and rounded.

Until we earn our wizarding cloaks, a lot of these business rules will remain a mystery; however, we can focus on the overall flow as Java developers:

  • A value is passed to a method.

  • Calculate some integer values using static variables and fields of the current object.

  • Loop and do some arithmetic to increment the variable saddleSizeFactor.

  • Apply a formula to saddleSizeFactor.

  • Cast the result to a double and return it!

That's all useful, but what's our next theory?

Looking at Lines 6 to 13 above, we can assume that if we're counting down from the year 2019 to the year AD 1, we should have a saddle size of at least 2018.  We've seen that we had a negative number at the point of calling DragonSaddleSizeVerifier::verify, so something is making that negative.

Since there isn't much visibility around what that modifier at Line 12 does, we will assume it is adding a negative number to our saddle size. 🕵️ This might be causing our positive value to turn negative, and in turn, making the verifier throw an InvalidSaddleSizeException. Let's get into that for loop and investigate!

Debug in a Loop: Use Conditional Breakpoints

How do you debug in a for loop? Let's think about it logically. When you're food shopping, do you pace all the aisles and examine each item before finding what you want? Probably not.  I'd imagine that your shopping trips would be unnecessarily long, and you'd end up with lots of spontaneous purchases for things you'll never use.  So what do you do? You usually have an idea of what you're looking for and go straight to the right aisles or sections. If all you want is soy milk, you don't have to examine every brand of toilet paper!

This grocery store situation is not too far off from what happens in code. For example, in a for loop, the same block is being revisited over and over again, but with different data. If you set a breakpoint in a for loop, it will suspend many times over. That means that when you investigate an issue, you’ll have to manually sift through a lot of suspends, which may not even be necessary for your investigation. This is a little too close to the above toilet paper situation. One way to avoid this is by using conditional breakpoints, which are smart breakpoints that stop when you have the right data visible to your code.

That doesn't seem so bad...

If you're not convinced it's tedious, let's look at an example from our code. To investigate if our saddle size was being modified incorrectly in the for loop, we will set a breakpoint at Line 11 inside the for loop on the statement  if (i < UNIVERSAL_CONSTANT) {. Let’s see if we can debug this by setting a breakpoint in that for loop!

 Did you see what happened? The debugger tried to help us by breaking on every single visit of the  if (i < UNIVERSAL_CONSTANT) {   conditional. This is great, but there’s little value, and a lot of work involved, in our checking it 2018 times. Wouldn’t it be great if we could just inspect it on the 2018th loop? 

That code is just wrong, though. Why do we need to loop at all? Shouldn't we just fix it? 

Admittedly, the code could be improved. Perhaps we could re-rewrite this to avoid a for loop altogether. Sadly, you don't often get to pick the state of the code handed over to you. Assuming that the last person was doing their best, hold off the temptation to rewrite the code before you know what's wrong with it.

Rewriting code for readability is great, but not while it's potentially broken. You could introduce additional issues. Further, since you know it's broken, you might have to modify the code you’re rewriting now. 

So, back to our code. How can we debug it without having to click resume 2018 times?

Conditional breakpoints are smart breakpoints that stop when you have the right data visible to your code. With conditional breakpoints, you can create a breakpoint that will only break (i.e., the debugger will stop) when a Java statement evaluates to true. For instance, in the case of that loop, we could break on  i==42  or  i<UNIVERSAL_CONSTANT.  Let's use this to help debug our Dragon Saddle Size Guesser!

As you saw with our non-conditional breakpoint in the for loop, it can be impractical to set a breakpoint that is only needed for certain requests. We can set a conditional breakpoint here by right-clicking on the existing breakpoint and specifying a boolean expression.

Shows a dialogue for creating a conditional breakpoint in the left margin.
Setting a conditional breakpoint

By setting this conditional breakpoint, the JVM will always suspend when the expression evaluates to true. You can think of it as adding an if statement to your code right before the marked breakpoint, which does something like  if  (conditional_expression) { suspendTheJVM! }.

Now that we've set a conditional breakpoint before the if statement in our for loop, let's debug the test and investigate further! 🕵️

 As you can see, by setting a conditional breakpoint, we were able to avoid having to suspend and resume thousands of times! The first time our debugger suspended was when i  had a value of 41. We were even able to change the conditional breakpoint midway through debugging and skip ahead, by changing our condition to   i < 4 . 

So was the modifier variable the cause of our bug?

To examine the impact of the modifier value, we used the Step Over execution control button to resume execution, but immediately suspend the JVM on the next line.

The Step Over button allows you to resume execution, but immediately suspend the JVM on the next line.
Step Over

This allowed us to move down the code and inspect the return value of the modifier variable. Interestingly it only ever returns zero. There's a little FIXME in the code, which was a red herring!

One detail we did pick up on is that the  mysticalMultiplier variable has a negative number:

Suspicious values in the variables pane!
Suspicious values in the variables pane!

Oddly, this is used to estimate the saddle size by multiplying it with the saddleSizeFactor  counter that we incremented in our for loop.

The final formula for our saddle size is defined in the code as:

double exactSaddleSize = (saddleSizeFactor * mysticalMultiplier) - mysticalMultiplier /10;

Substituting in those values from our variables pane:

(2018*-0.024390243902439025)

-0.024390243902439025 / 10

= 49.22195121951219

Let's have a look at our buggy exception again:

com.openclassrooms.debugging.exception.InvalidSaddleSizeException: Unexpected saddle size:-49.0

There is a surprising resemblance there. One is -49 and the other is 49.22195. We have a new avenue to investigate.  🕵️‍♀️

Let's Recap!

  • Conditional breakpoints allow you to suspend the JVM (via your debugger) at a given line, if a condition is met. 

    • Conditional breakpoints are set by defining a boolean expression in Java which is evaluated using variables visible to, or in-scope of, the code at that breakpoint.

    • Conditional breakpoints are especially useful when debugging code that is revisited many times, such as a for loop.

 In the next chapter, we'll look at some more specialized ways of fixing bugs! 

Exemple de certificat de réussite
Exemple de certificat de réussite