This has been a spectacularly intensive week. The new YouTube channel carrying the course is exploding with subscriptions, it's just entering its 3rd week... The course website is now live, you can see the entire course there although I'm adding videos all the time and did roughly 1/3 of the work. Right now it has 3 hours and 17 minutes of content. I have another hour of content ready to add and I'm working on several more. I'm pretty sure the course will be well over 6 hours when completed.
I'm also working on some other interesting videos such as thing one about protecting yourselves from serialization exploits. I'll write a full blog post covering it in the coming weeks.
If you have any questions, thoughts or ideas I'd love to hear from you. Below is this week's video and the transcript.
Welcome back to the third part of debugging at Scale where we learn to hunt bugs like the pros!
In this section, we’ll discuss watch expressions which are the cornerstone of debugging. This is so important I’ll revisit the subject again later on.
The watch area is the area at the bottom of the screen
In IntelliJ, we can also embed watch expressions next to the code. The watch is one of the most important capabilities of a debugger. This is how we can see what’s going on in the debugger. But I’ll go much deeper than that. Especially for object marking which is one of the coolest debugger features ever.
Have you ever returned from a method only to think, what did that method return?
This is very common especially when the return value isn’t stored in a variable. Luckily the IDE has the option to save that value for us so we can inspect it right away!
By enabling this option we can see the return values from all future methods. If we step into the isPrime method and then step out you will be able to see the the
return value for the method at the bottom here.
Evaluate expression is one of the cool features of the debugger that we don’t use enough.
We can launch the evaluate expression dialog from the right-click menu and type in any valid Java expression. This is a powerful tool that can invoke any method, do arithmetic and even change the value of variables as a result. If you need to simulate something that’s hard to test in the current code this is the place where you can play with the platform and test out “wild theories”.
This is very much like the REPL we have in newer versions of Java, but it’s better in many ways because we can execute code in the context of our application. If I have a method that returns the wrong value I often copy the call into an evaluate dialog and try various combinations of the call to see “what works”. Just trying all the options without restarting can save us a lot of time!
You can launch this dialog with the
Alt-F8 key combination.
The watch ability in IntelliJ is absolutely spectacular.
IntelliJ lets us embed the watch directly into the IDE editor by selecting “Add Inline Watch” from the context menu. This is an amazing feature that as far as I know is unique to JetBrains
Once selected the watch appears on the right next to the line where we added the inline watch which makes it easy to evaluate with the code. This is very convenient when returning to the same line over and over again.
We can also use the standard watch which will add elements with the other variables. This is useful for objects we want to track over large areas of the code. I have a lot to say about the watch area as we move forward but for now, let’s put this on hold.
Set value is a feature I often forget to use when debugging.
That’s a shame because it’s so powerful. We can set the value of any field by right-clicking it and selecting set value.
We can also use
F2 to speed this up
I can change the value to any arbitrary value. This can also apply to objects where I can assign an existing value or invoke a creation method, a new statement or any expression I want. It’s a remarkably powerful feature where we can mutate the object dynamically to reproduce a state we want to test.
We can combine this capability with jump to line that we discussed previously and test a method through many different permutations. Even ones that might not be reproducible normally. A good example would be code that I have that only runs on Windows. But I have a Mac. I just change the value of the static variable that indicates the current operating system and test that code.
Object marking is one of the coolest features we’ll discuss in this course and it’s almost unknown.
It’s a bit subtle, first we’ll add a watch for
Thread.currentThread() which returns the object instance representing the current thread. As you can see I can see the current thread in the watch.
Now I can run this method again and again and see the current thread, is the method always executed from the same thread?
Well, I can look at the thread ID and yep. It’s the same. So it’s probably thread-safe. But how can I verify this?
I usually write down the object ID or the pointer of the variable on a piece of paper and check if it’s the same value. That’s a pain and doesn’t scale. How often can I press continue again and again?
In the right-click menu I select Mark Object and type in an arbitrary name.
MyThread in this case once I did that I can press OK
This replaces the value of the current thread right now with the new label. So we might incorrectly assume that this is just a name for a watch expression. It isn’t. We declared a new variable and gave it the name
MyThread. We copied the current value of the watch expression into that variable. We can now treat that variable as we treat most variables in the IDE.
We can evaluate the value here and get everything we want. Notice the
_DebugLabel suffix added by IntelliJ to avoid naming collisions but other than that we can invoke any operation on this object such as get the name or even access the private field name.
But this gets much better…
Let’s add a breakpoint to this method, a completely standard breakpoint like we did before. This will be a standard conditional breakpoint, we’ll discuss those soon enough in-depth but right now all you need to know is that I can define a condition that will determine if the breakpoint will stop or not. Let’s zoom in
Let’s type in the condition, I can compare
MyThread to the current value of the thread. Notice that this condition will behave accordingly since the value of
MyThread is independent from the original watch statement of
Thread.currentThread(). So if the current thread is indeed different the breakpoint will stop at this point.
This is immensely useful when dealing with many objects. In this case, I can literally check if the method will be hit with the same thread. I can use it to compare any objects instead of writing their pointers on a piece of paper! Yes. I would literally sit with a piece of paper and copy the pointer address to make sure I got it right if I see it again. This is better in so many ways!
I often use this with APIs like JPA where we might sometimes have two object instances with the same identity. This is really hard to detect. Just mark one object and then you can instantly see it’s a different instance.
Since this case is obviously single-threaded the breakpoint will never get hit again. But this works rather well for very elaborate cases and is a remarkably useful tool!
Next up we will dive deep into breakpoints and the amazing things they can do. This is a deep-dive video that you don’t want to miss. If you have any questions please use the comments section. Thank you!