Friday, December 28, 2007

Treat Warnings as Errors

In a previous post I mentioned the importance of fixing compiler warnings. I forgot about a useful project setting in Visual Studio that will cause all build warnings to display as errors instead. From the project's properties page, select the Build tab. Under "Treat warnings as errors," set the value to "All." While this may be difficult to justify on existing projects with hundreds of warnings, this is something that should be set on all new projects.





Tuesday, November 6, 2007

Quick and dirty debugging - DebugView and Trace.WriteLine

Here's another debug scenario for you. After taking great effort in testing your application you hand it off to QA. They throw it back with some interesting bug. Using the exact same steps, you're unable to reproduce on your machine. You have a general idea of where the app is failing, but can't be sure precisely what the issue is.

At this point one usually resorts to some sort of logging. These logging statements are placed in strategic locations through the suspect code (at every other line.) If the bug is in the UI, the common method is MessageBox.Show. If the problem is in an underlying assembly, you must resort to writing output to a text file. The downside is that lots of message boxes wear you out, and it's always a pain tracking down the necessary code to write files.

An easier option is to call Trace.WriteLine (located in System.Diagnostics.) In my sample application (MyApp) I have a single button. Inside the Click event, I add the following line

 Trace.WriteLine("Button1 was clicked");

The event then calls a method in a separate library. In this library method, I've added the following line ('x' was the parameter passed into the method)

 Trace.WriteLine("SomeMethod - Parameter x: " + x.ToString());

When I run the application from VisualStudio and click the button, the Output window contains the trace messages


That's fine if you're running VS, but what about on a QA box? For that I'm using DebugView. After starting up the utility, I fire up the sample application. Click the button (which again calls Trace.Writeline) and DebugView displays the messages previously seen in Visual Studio.

Invalid Data Source crashes Visual Studio

Binding data object to a WinForms control is usually straightforward. Say you have a ComboBox control. Clicking the triangle on the top-right will display the ComboBox Tasks dialog.


Once you check the box to "Use data bound items" you start by selecting the Data Source. Normally, clicking the Data Source dropdown provides you with a list of existing items, as well as the option to create a new one. A few days ago we ran into an issue where, instead of the datasources list, we were given this rather entertaining dialog


With a few hints from the blogging community, we were directed to the Data Sources window (Data > Show Data Sources, or Shift+Alt+D.)


Notice the first entry has an error icon. This is due to a datasource pointing to a non-existent class. Right-click the invalid entry and choose Remove Object. Now the Data Source dropdown behaves as expected.

Thursday, November 1, 2007

Debugging with the Fusion logger

If you've spent much time developing, you've probably run into this scenario. You're working along coding a new application. Everything runs on your machine. Then you hand it off to someone to test (that, or load it on the production server - let the users tell you what's wrong.) Unfortunately, the app crashes as soon as you attempt to start it. Looking in the Event Logs, you see generic .Net errors, but nothing that appears to help. Short of installing VisualStudio on the box in question, how do you track down the issue?

One place to start is the Fusion logs. For this demo, I've created a class library (MyClassLib) and a command-line app (MyConsoleApp.) The app makes a single method call into the library and then exits. Nothing interesting to see when everything works. If I delete MyClassLib.dll and run the application, I witness a rather unpleasant dialog


When an app closes in a violent manner, the first place to look is the computer's event logs. Unfortunately, the only entry for the app looks something like:

"Faulting application myconsoleapp.exe, version 1.0.0.0, stamp 47293d14, faulting module kernel32.dll, version 5.1.2600.3119, stamp 46239bd5, debug? 0, fault address 0x00012a5b."

So now we turn to the Fusion logs. The first step is to copy the Assembly Binding Log Viewer (fuslogvw.exe) from any machine with VS2005. Start it up; it looks something like this:


Click on the Settings button. In the dialog, select "Log bind failures to disk." Check the box to "Enable custom log path" and specify an already-existing folder (the app won't create it for you.)


Note: According to official documentation, you should be able to use the default log directory. In my experience, that never seemed to work.

With the logger set up I again attempt to run the application. Which again crashes. Going back to the Fusion log viewer, click Refresh. I now have a single entry


Clicking on the entry loads the details in Internet Explorer (shown below.) If you look halfway down the log, you'll see the reference to MyClassLib. Near the end, you'll see the various locations it searched for the file. Now it's a simple matter of finding a copy of my dll and placing in the search path.



*** Assembly Binder Log Entry (10/31/2007 @ 10:14:45 PM) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
Running under executable C:\temp\testcode\MyConsoleApp\bin\Release\MyConsoleApp.exe
--- A detailed error log follows.

=== Pre-bind state information ===
LOG: User = SH\pgoins
LOG: DisplayName = MyClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
(Fully-specified)
LOG: Appbase = file:///C:/temp/testcode/MyConsoleApp/bin/Release/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = MyConsoleApp.exe
Calling assembly : MyConsoleApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/temp/testcode/MyConsoleApp/bin/Release/MyClassLib.DLL.
LOG: Attempting download of new URL file:///C:/temp/testcode/MyConsoleApp/bin/Release/MyClassLib/MyClassLib.DLL.
LOG: Attempting download of new URL file:///C:/temp/testcode/MyConsoleApp/bin/Release/MyClassLib.EXE.
LOG: Attempting download of new URL file:///C:/temp/testcode/MyConsoleApp/bin/Release/MyClassLib/MyClassLib.EXE.

LOG: All probing URLs attempted and failed.

Friday, October 19, 2007

Implicit conversions

I'm working on a project where request and response messages are serialized before sending across the wire. We do this by calling an override of ToString(). So a line in the code might look like
    response = myProvider.ProcessMessage(myMessage.ToString());

If I try to type
    response = myProvider.ProcessMessage(myMessage);

I get a build error stating that it cannot implicitly convert type 'SomeCoolMessage' to 'string.' So, what if I want to implicitly convert between the two? In the SomeCoolMessage class I add a new operator

    public static implicit operator string(SomeCoolMessage m)
{
return m.ToString();
}

Now I can write
    response = myProvider.ProcessMessage(myMessage);

and everything just works.

Friday, October 5, 2007

Lessons learned

My current project is nearing completion of Phase 1. Along the way, I've compiled a list of observations, lessons learned, etc, some of which I've already blogged. Here are a few more:

1) When possible, use project references instead of file references. It may seem convenient to break up the code into smaller solutions and use file references across solutions. Problems arise when you have one master solution that builds all of the projects - you must explicitly set the build order. With project references, VisualStudio determines the proper order for you, meaning less maintenance. Also, when you right-click on a method/class/whatever and choose "Go To Definition," file references will take you to a page of metadata instead of the actual code.

2) Refactor often, and sooner rather than later. Say you're working on set of classes that use a common interface. You then decide to build a base class from that interface, and derive the other classes from the base. Don't leave old classes as-is and use the base only for new classes. One issue is you potentially leave bugs that the base was specifically designed to address. Another is that when a new team member joins the project, he will likely use existing code as a template for adding new functionality. If he sees the non-base-derived class and builds his own directly from the interface, refactoring later becomes more difficult.

3) Remove dead code. When you replace one method with another, delete the old method. When functionality is no longer required, delete that functionality. Don't leave it in. Don't comment it out. If you need that code later, pull it from your source repository. As the source grows in size, you'll have enough to deal with without adding the extra hassle associated with dead code.

4) Obsolete code if you can't currently delete it. Say you replace a method with another, but you can't replace all of the method calls at the moment (this especially happens with public methods.) Mark the method as obsolete and note the proper method to use instead. In C# this looks like [Obsolete("Use method foo instead")].

5) When changing the database schema, change the data access code at the same time. Until these two are in sync, your code doesn't work. You should have failing unit tests to flag the issues, but that's not always the case. In that instance, your first indication that there is a mismatch is when a developer attempts to run code that he thought was working. Often, the developer attempts to track down the problem, which another dev already knew about. All of which leads to wasted time.

6) Reduce confusion within the code. Maybe there's a method being incorrectly called, or called when another should have been used instead. It's not enough to correct the developer. Look at why the error occurred. Perhaps better comments on the method would help (use xml comments to populate intellisense.) Or perhaps the method could be named better. The problem may also be due to poorly architected code in need of refactoring. Bottom line: fix the issue instead of addressing symptoms.

7) If you can't unit test the entire codebase, write tests for the error-prone code. Ideally you want 100% code coverage from your unit tests. In reality this isn't going to happen. Therefore, focus unit tests on problematic and/or complex areas of code. We have lots of code binding to dataset columns. These columns are taken directly from the database. If a column was dropped from the table, you won't receive a compiler error on row["DroppedColumn"] but you will receive a runtime exception. These issues need to be caught by the unit tests, not the QA team.

8) Code generation must be handled very carefully. Most see code generation as an easy way to save time on a project. This is especially true for database access code - basic CRUD operations don't change from one table to the next. Once you have the first generation complete, how you proceed becomes critical. Say you spend a few weeks writing code that uses the autogen code. Someone then decides to make changes to the templates and regen everything. While the intent may have been valid, this regen quite likely broke existing code. At a minimum, your unit tests fail and you can easily find and fix all of the problems. But even then, time must be spent on the fix. If you don't have a decent set of unit tests - be prepared for the increase in tickets from QA. Unless your autogen code is truly separate from the rest of the project, it's probably better in the long run to gen once and modify by hand after that.

Monday, September 24, 2007

Hidden Desktops and NUnitForms

In my last post I noted a problem working with NUnitForms. I was unable to get the hidden desktop to work with a [SetUp] method. After digging through the source code, I have my solution.

The code that sets up the hidden desktop is in the method init(). This method is marked with the [SetUp] attribute. When I created my own method with this attribute, init() was no longer being called. Thus, no hidden desktop.

The solution is to override the Setup() method provided in NUnitFormsTest, but to not mark it with the [SetUp] attribute. The last thing init() does before exiting is make a call to Setup(). The TearDown attribute and method have a similar implementation.

When the documentation seems to be lacking, it's always nice to have the source code.