Sunday, May 19, 2013

.NET Debugging Tips

In my three years of professional coding experience, I have fixed many problems in .NET programs and unit tests. One example of this is a WPF problem that ultimately involved a faulty GetHashCode() implementation. Most of the problems that I have faced involve NullReferenceExceptions, off-by-one errors, bad regular expressions, and "normal" stuff like that. I've fixed some major concurrency-related issues and even a bug that took 13 steps to re-produce. I don't have a decade of experience, but I know a thing or two about debugging .NET code.

(I) Don't Rely on Breakpoints

Visual Studio has excellent support for breakpoints, but I rarely use them. Removing a breakpoint can be a time-consuming process if you want to leave other breakpoints enabled. They also stop the program entirely, and I've seen cases where breakpoints prevent concurrency issues from being re-produced.

I use breakpoints only when I need to look at local variables in a buggy piece of code. Otherwise, I just sprinkle Debug.WriteLine statements when needed. I can act on Debug info faster than I can a breakpoint, and the program doesn't stop for seconds while printing debug statements.

There is one major disadvantage to using Debug.WriteLine: you have to alter source code to use it. Please do not commit code with Debug statements that you don't plan to keep. Every version control system that I know of has a diff support, and you should check these diffs when you commit code.

(I) Have Plenty of Unit & Integration Tests

I am a big proponent of unit & integration testing. I feel that good tests help developers figure out what isn't causing a problem before they start. Automated testing is not the end-all, be-all of bug prevention. You'll miss edge cases, and there's no way (that I know of) to detect weird GUI-related issues before you stumble upon them. Tests are merely good tools.

If you are able to, I highly recommend writing integration tests. In past endeavors, I had to mock everything out to such an extent that real-world issues (like a missing table, column, or bad parameters) would not be detected until the manual testing phase. My software uses SQLite, and I write tests that write data to real SQLite database. These integration tests helped me write persistence code that didn't easily break.

While debugging, I try to write unit & integration tests that cover the bug. This helps me debug the problem and ensures that the exact same problem does not return in future code revisions.

Rules of Thumb

Common NullReferenceException causes

When my code throws a  NullReferenceException, it's usually one of two things:
  1. I forgot to check a parameter or property for null
  2. Type cast returns null (and I forgot to check for null)

Silently handling exceptions is bad

Silently handling exceptions silence major problems with your code. It also makes your code harder to debug. I highly recommend not writing error-handling code that does nothing.

Of course, I've written error-handling code that basically does nothing. The hack was very well documented to indicate why I was silently handling exceptions, and I didn't catch Exception. That's bad because StackOverflowException and OutOfMemoryException exist.
 
On a related note, catching an exception and throwing a new one can hide the true source of the exception.

 It's probably your regular expression

If a regular expression can be the cause of a problem, it probably is! This isn't a knock against regular expressions, as they can be very useful for validating certain types of input. It's just tough to write one that works all of the time for all cases.

No comments:

Post a Comment