.NET Debugging Tips Redux; Javascript Context Search
There are two major deficiencies I've noticed with newer developers that seem to be rampant, based on my unscientific analysis of newsgroup and forum post questions that I've read or answered.
First, many new developers (and some older ones too, unfortunately) haven't learned how to use the Try / Catch / Finally block semantics to catch an exception and examine the results. A simple example pattern would be:
try
{
// Your buggy code here
}
catch(Exception ex)
{
// place a breakpoint on the next line:
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
}
This will save you hours of time wasted on making reallyreallyDUMB newsgroup posts and waiting for answers, because 9 times out of 10, the Message and StackTrace will tell you not only WHAT happened, but exactly WHERE it happened, no matter how big the "Your buggy code here" block of code is. The modus operandi is "teach a man to fish". By simply replacing the word Debug with "Trace" for an ASP.NET app, and turning on Page Tracing in a deployed application, you can actually do some debugging of release-built apps in production - provided you've instrumented your code with enough of the above. As Joel Spolsky reports, this is a programming habit you want to firm up fast, and learn to do right -- not just for debugging, but for handling ACTUAL EXCEPTIONS in production code. Read Joel's piece and either laugh - or weep!
Another important concept is how to "interpret" exception messages. When the runtime says "Object reference not set to an instance of an object" you need to take this LITERALLY, not post to newsgroups asking "What's wrong"! The runtime is telling you that your code is attempting to use an object variable that does not contain an instance of the object it is supposed to represent. If that's the case, then it should (under ordinary circumstances) be a relatively simple matter to work your way backwards and find out why when you thought "SelectedValue" was supposed to be holding something, it is not! Yet, hordes of developers post mindlessly to newsgroups and forums asking "what is wrong?". Jeesh! The Common Language Runtime JUST TOLD YOU what is wrong, in PLAIN ENGLISH!
Second, few developers except the most professional have taken the time to really study the art and science of DEBUGGING. I certainly don't profess to be an expert, but there are a number of books on the subject that have helped me immeasurably. Here are a few:
Also, consider looking over the following documents:
Debug.Doc and DebugRef.Doc
which can be found in folder:
c:\\Program files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Tool Developers Guide\Docs
and in the Documentation or on MSDN Online in the section "Building Debugging and Testing:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsintro7/html/vxoribuildingdebuggingandtesting.asp
Here is a list of some of my favorite learned tips that increase the power of the developer and the debugger!
Tip: Call Stack Window Debugging: Many developers are not aware that you can set breakpoints in the Call Stack Window (except when doing SQL debugging). Just highlight the call you want to stop on and either press F9 or right-click on the line and select Insert Breakpoint from the shortcut menu. You can also right-click on any breakpoint in the Call Stack Window to enable, disable or set the properties of the breakpoint. For those unfamiliar with the Call Stack Window, this is how you find out "how did I get here" - it enables you to go back up the call stack to find out "from where" the method that you are in was called. Not only can you see where it was called from, you can actually go "Back there" and trace the exact execution path in the debugger. Not to be an evangelist, but if you haven't discovered it yet, please do so now and "get religion".
Tip: Use Run to Cursor for one-shot breakpoints: All you need to do is right-click on the line you want and choose "Run to Cursor" from the menu, and program execution will immediately continue to the point where you are at. Many developers are not aware that this is available from both debugging and editing. In edit mode, this will actually start the debugger and run your program to the cursor location. As with breakpoints, right-clicking in the Call Stack window pops up the shortcut menu that also offers this feature.
Tip: Set sub-expression breakpoints: For example, if you have the expression:
for (int j=0; m=0;j<activelines.count> you can set a breakpoint in the middle of it, not on the whole line.
Tip: Use the BreakPoints Window: If you know the name of the class and method you want to break on, you can choose New from the Breakpoints window and type it directly into the Function area of the window. Developers can spend 15 minutes wandering through their projects opening files just so they can position the cursor over a line of code and hit the F9 key to set a breakpoint. If you know the name of the class and method, doing it this way will find it and set the breakpoint. In addition, you can click it in the Breakpoints list of the window and go to the location automatically.
You can also set a breakpoint by just entering the method name, if it is unique in your application. And, if it isn't you will actually get a list of ALL the locations where the method can be found, which is extremely useful for setting multiple breakpoints on a commonly used function throughout your project. It also displays the overloaded methods in the same class.
Tip: Use the Find ComboBox: If you enter a method name in the Find Box (that's on the top menu bar, just to the right of the Configuration dropdown that reads Debug, Release, Configuration Manager) and hit F9, this will also find the method and set a breakpoint.
Tip: Use Hit Counts: In the Breakpoints window, when you right-click and choose Properties and then click the Hit Counts button, you can modify a breakpoint with four possible combinations of choices. Experiment with this to see how useful it can be. What makes this especially useful is that when you have stopped in the Debugger, it tells you how many times your breakpoint has executed. You can also create conditional expressions that will trigger the debugger only if your expression evaluates to true or has changed since the last time it was evaluated.
Tip: Use the Watch window (and its brethren): The power offered by the Watch window and its close cousins, the Quick Watch Dialog, Autos window, Locals window, and the This / Me window are what can make the difference between hopping around aimlessly all day looking for a bug and quickly solving a problem. Also, you can use any of these windows to change a variable's value by simply typing in a new value.
Tip: Break only when a specific thread calls a method: To set a per-thread breakpoint, you need to uniquely identify a particular thread that you have given a name with its Name property. You can set a conditional breakpoint for a thread by creating a conditional expression such as "ThreadToStopOn" == Thread.CurrentThread.Name .
You can manually change the name of a thread in the Watch window by watching variable "myThread" and entering a Name value for it in the value window. If you don't have a current thread variable to work with, you can use Thread.CurrentThread.Name to set the current thread's name. There is also a private integer variable in the Thread class, DONT_USE_InternalThread, this is unique to each thread. You can use the Threads window to get to the thread you want to stop on, and in the Watch window, enter Thread.CurrentThread.DONT_USE_InternalThread to see the value of it so you can create the right conditional breakpoint expression.
Tip: Use Assertion Debugging: The idea behind asserts is very simple: When you are writing code, you understand that some condition is always expected to be true. If that condition is ever not true, then there is a bug. Asserts can be written so that if this condition is ever not true, the debugger will be launched at that exact point in your code:
System.Diagnostics.Debug.Assert (myValue >=0) The nice thing about Assert windows is that they not only give you the opportunity to break or not, they also show you the stack trace at that point. A very powerful, yet highly under-utilized feature.
I've installed an experimental script that allows you to highlight any text on a page, right-click and my eggheadcafe.com SearchRelated page will come up with our search results. You can try it anywhere on this page. So far it seems to work with IE 6, IE7, and Firefox. With Firefox I couldn't "select Text" on a page with my mouse; you have to use the "Edit / Find on this page" feature to get the text selected. If you would like to try it out on a page of your own, just add this script block to any web page:
<script src="http://www.eggheadcafe.com/articles/search.js"></script>
First, many new developers (and some older ones too, unfortunately) haven't learned how to use the Try / Catch / Finally block semantics to catch an exception and examine the results. A simple example pattern would be:
try
{
// Your buggy code here
}
catch(Exception ex)
{
// place a breakpoint on the next line:
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
}
This will save you hours of time wasted on making reallyreallyDUMB newsgroup posts and waiting for answers, because 9 times out of 10, the Message and StackTrace will tell you not only WHAT happened, but exactly WHERE it happened, no matter how big the "Your buggy code here" block of code is. The modus operandi is "teach a man to fish". By simply replacing the word Debug with "Trace" for an ASP.NET app, and turning on Page Tracing in a deployed application, you can actually do some debugging of release-built apps in production - provided you've instrumented your code with enough of the above. As Joel Spolsky reports, this is a programming habit you want to firm up fast, and learn to do right -- not just for debugging, but for handling ACTUAL EXCEPTIONS in production code. Read Joel's piece and either laugh - or weep!
Another important concept is how to "interpret" exception messages. When the runtime says "Object reference not set to an instance of an object" you need to take this LITERALLY, not post to newsgroups asking "What's wrong"! The runtime is telling you that your code is attempting to use an object variable that does not contain an instance of the object it is supposed to represent. If that's the case, then it should (under ordinary circumstances) be a relatively simple matter to work your way backwards and find out why when you thought "SelectedValue" was supposed to be holding something, it is not! Yet, hordes of developers post mindlessly to newsgroups and forums asking "what is wrong?". Jeesh! The Common Language Runtime JUST TOLD YOU what is wrong, in PLAIN ENGLISH!
Second, few developers except the most professional have taken the time to really study the art and science of DEBUGGING. I certainly don't profess to be an expert, but there are a number of books on the subject that have helped me immeasurably. Here are a few:
- Debugging Applications for Microsoft .NET and Microsoft Windows by John Robbins (MS Press)
- Debugging Strategies for .NET Developers by Darin Dillon (aPress)
- Comprehensive VB.NET Debugging by Mark Pearce (aPress)
Also, consider looking over the following documents:
Debug.Doc and DebugRef.Doc
which can be found in folder:
c:\\Program files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Tool Developers Guide\Docs
and in the Documentation or on MSDN Online in the section "Building Debugging and Testing:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsintro7/html/vxoribuildingdebuggingandtesting.asp
Here is a list of some of my favorite learned tips that increase the power of the developer and the debugger!
Tip: Call Stack Window Debugging: Many developers are not aware that you can set breakpoints in the Call Stack Window (except when doing SQL debugging). Just highlight the call you want to stop on and either press F9 or right-click on the line and select Insert Breakpoint from the shortcut menu. You can also right-click on any breakpoint in the Call Stack Window to enable, disable or set the properties of the breakpoint. For those unfamiliar with the Call Stack Window, this is how you find out "how did I get here" - it enables you to go back up the call stack to find out "from where" the method that you are in was called. Not only can you see where it was called from, you can actually go "Back there" and trace the exact execution path in the debugger. Not to be an evangelist, but if you haven't discovered it yet, please do so now and "get religion".
Tip: Use Run to Cursor for one-shot breakpoints: All you need to do is right-click on the line you want and choose "Run to Cursor" from the menu, and program execution will immediately continue to the point where you are at. Many developers are not aware that this is available from both debugging and editing. In edit mode, this will actually start the debugger and run your program to the cursor location. As with breakpoints, right-clicking in the Call Stack window pops up the shortcut menu that also offers this feature.
Tip: Set sub-expression breakpoints: For example, if you have the expression:
for (int j=0; m=0;j<activelines.count> you can set a breakpoint in the middle of it, not on the whole line.
Tip: Use the BreakPoints Window: If you know the name of the class and method you want to break on, you can choose New from the Breakpoints window and type it directly into the Function area of the window. Developers can spend 15 minutes wandering through their projects opening files just so they can position the cursor over a line of code and hit the F9 key to set a breakpoint. If you know the name of the class and method, doing it this way will find it and set the breakpoint. In addition, you can click it in the Breakpoints list of the window and go to the location automatically.
You can also set a breakpoint by just entering the method name, if it is unique in your application. And, if it isn't you will actually get a list of ALL the locations where the method can be found, which is extremely useful for setting multiple breakpoints on a commonly used function throughout your project. It also displays the overloaded methods in the same class.
Tip: Use the Find ComboBox: If you enter a method name in the Find Box (that's on the top menu bar, just to the right of the Configuration dropdown that reads Debug, Release, Configuration Manager) and hit F9, this will also find the method and set a breakpoint.
Tip: Use Hit Counts: In the Breakpoints window, when you right-click and choose Properties and then click the Hit Counts button, you can modify a breakpoint with four possible combinations of choices. Experiment with this to see how useful it can be. What makes this especially useful is that when you have stopped in the Debugger, it tells you how many times your breakpoint has executed. You can also create conditional expressions that will trigger the debugger only if your expression evaluates to true or has changed since the last time it was evaluated.
Tip: Use the Watch window (and its brethren): The power offered by the Watch window and its close cousins, the Quick Watch Dialog, Autos window, Locals window, and the This / Me window are what can make the difference between hopping around aimlessly all day looking for a bug and quickly solving a problem. Also, you can use any of these windows to change a variable's value by simply typing in a new value.
Tip: Break only when a specific thread calls a method: To set a per-thread breakpoint, you need to uniquely identify a particular thread that you have given a name with its Name property. You can set a conditional breakpoint for a thread by creating a conditional expression such as "ThreadToStopOn" == Thread.CurrentThread.Name .
You can manually change the name of a thread in the Watch window by watching variable "myThread" and entering a Name value for it in the value window. If you don't have a current thread variable to work with, you can use Thread.CurrentThread.Name to set the current thread's name. There is also a private integer variable in the Thread class, DONT_USE_InternalThread, this is unique to each thread. You can use the Threads window to get to the thread you want to stop on, and in the Watch window, enter Thread.CurrentThread.DONT_USE_InternalThread to see the value of it so you can create the right conditional breakpoint expression.
Tip: Use Assertion Debugging: The idea behind asserts is very simple: When you are writing code, you understand that some condition is always expected to be true. If that condition is ever not true, then there is a bug. Asserts can be written so that if this condition is ever not true, the debugger will be launched at that exact point in your code:
System.Diagnostics.Debug.Assert (myValue >=0) The nice thing about Assert windows is that they not only give you the opportunity to break or not, they also show you the stack trace at that point. A very powerful, yet highly under-utilized feature.
Javascript Context Search
I've installed an experimental script that allows you to highlight any text on a page, right-click and my eggheadcafe.com SearchRelated page will come up with our search results. You can try it anywhere on this page. So far it seems to work with IE 6, IE7, and Firefox. With Firefox I couldn't "select Text" on a page with my mouse; you have to use the "Edit / Find on this page" feature to get the text selected. If you would like to try it out on a page of your own, just add this script block to any web page:
<script src="http://www.eggheadcafe.com/articles/search.js"></script>
Preach it, Peter.
ReplyDelete-james peckham
Excellent piece of article. Thanks a Billion
ReplyDelete