If you've been reading my blog, you know that I am developing an application using Visual Studio 2005 and Castle's ActiveRecord framework. Recently, I bought Visual Studio 2005 Pro so I could use Crystal Reports. Believe it or not, The user was able to install my application without a working version of .Net 2.0 around. As far as I can tell, they did a Windows Update, and somehow it corrupted their video drivers. Then they did a system restore back to before .Net 2.0 was installed. After this, they installed my application, and it crashed (of course!). They tried to repair .Net 2.o Framework, but that didn't quite do it. So now I will probably be connecting via Remote Desktop to survey the damages.
I learned something from this, and I will probably have two tiny executables in my bin folder ... a "hello world" application that maybe says "If you can see this, then .Net 2.0 is installed." and maybe does some sniffing around to see if an important font (Bar Code 3 of 9) is installed. When someone calls you up and says that an application crashed before it has shown the login form, that is never good. My first guess was that there was a security problem, so I asked him to go to Administrative Tools, but he could not find the .Net 2 Configuration Wizard. Aha! I guess I need to include the re-distributable components on CD, just for these cases. And I need to add a logging library, so I have some idea what the user is doing if and when the program crashes.
Why am I having so many problems? Can you say 3 week delivery time for a 5 month project? So I gave them "what I have", even though it is not fully tested. I hate doing this, but I did not have a choice. So I will be updating the application every few weeks as I enable menu items and enable or add forms. The good side of this is that the users will be testing my code, and because there is a real need, I was forced to focus my efforts on the core feature set. Since the most critical feature is creating an XML report, I started with that, hard-coding as I went. Then I dropped each value into the schema where it made the most sense, until I had no more hard-coded values. Then I refactored the code two or three times, separating user interface, business logic and entity / data management. I probably have more work to do here, but I have NUnit tests for some of the application, and since the UI (user interface) is in an EXE assembly, and the business logic and data classes are in a DLL assembly, all I have to do is write tests, and anything that I can't do from the NUnit tests needs refactoring.
Since I have about 100 things to do before I finish the application, I have resorted to two types of comment tags, the standard "//TODO: Implement method bar.MarkWidgetAsAssembled" and "//FUTURE: Do this later, since it is not a critical deliverable element". After my first official deliverable, I will do a file find and replace of //FUTURE: with //TODO: and finish up.
Another defensive tactic that I commonly use is try / catch blocks in my event methods. For example if I have a "Create ..." button, I will wrap my processing in a try / catch block, and throw a Debug.WriteLine(ex.ToString()) or log the exception then inform the user that the request failed. The business code that I called will be self-cleaning, so I do not need a finally block. The business code will have a try / finally if there is a file that needs to be closed, etc. .Net 2.0 has a BackgroundWorker that allows me to send progress percent and messages, without any System.Windows.Forms (SWF) references. This is important, since one of my rules is that business / data library code should NEVER refer to UI components aka SWF. This allows me to test my library code more easily, and who knows, some parts of the application might be used in a batch environment. Perhaps my file import code could be hooked to a directory watcher that imports new files and then moves them to /loaded or /error subdirectories. If my import code popped up a MessageBox under any circumstance, I could never use it in an automated fashion. Here is some pseudo-code:
// Button Event
private void BtnCreate_Click(sender, event)
{
try
{
DateTime selectedMonth = GetDateFromUI();
CreateWidgetSet(selectedMonth);
}
catch (Exception ex)
{
LogError(ex);
MessageBox("An error occurred while trying to create a widget set.\n\n" + ex.Message,
Title, MessageBoxButtons.OK);
}
}
// business action method
private void CreateWidgetSet(DateTime selectedMonth)
{
// actual business method
Widget.CreateASet(selectedMonth);
}
I learned something from this, and I will probably have two tiny executables in my bin folder ... a "hello world" application that maybe says "If you can see this, then .Net 2.0 is installed." and maybe does some sniffing around to see if an important font (Bar Code 3 of 9) is installed. When someone calls you up and says that an application crashed before it has shown the login form, that is never good. My first guess was that there was a security problem, so I asked him to go to Administrative Tools, but he could not find the .Net 2 Configuration Wizard. Aha! I guess I need to include the re-distributable components on CD, just for these cases. And I need to add a logging library, so I have some idea what the user is doing if and when the program crashes.
Why am I having so many problems? Can you say 3 week delivery time for a 5 month project? So I gave them "what I have", even though it is not fully tested. I hate doing this, but I did not have a choice. So I will be updating the application every few weeks as I enable menu items and enable or add forms. The good side of this is that the users will be testing my code, and because there is a real need, I was forced to focus my efforts on the core feature set. Since the most critical feature is creating an XML report, I started with that, hard-coding as I went. Then I dropped each value into the schema where it made the most sense, until I had no more hard-coded values. Then I refactored the code two or three times, separating user interface, business logic and entity / data management. I probably have more work to do here, but I have NUnit tests for some of the application, and since the UI (user interface) is in an EXE assembly, and the business logic and data classes are in a DLL assembly, all I have to do is write tests, and anything that I can't do from the NUnit tests needs refactoring.
Since I have about 100 things to do before I finish the application, I have resorted to two types of comment tags, the standard "//TODO: Implement method bar.MarkWidgetAsAssembled" and "//FUTURE: Do this later, since it is not a critical deliverable element". After my first official deliverable, I will do a file find and replace of //FUTURE: with //TODO: and finish up.
Another defensive tactic that I commonly use is try / catch blocks in my event methods. For example if I have a "Create ..." button, I will wrap my processing in a try / catch block, and throw a Debug.WriteLine(ex.ToString()) or log the exception then inform the user that the request failed. The business code that I called will be self-cleaning, so I do not need a finally block. The business code will have a try / finally if there is a file that needs to be closed, etc. .Net 2.0 has a BackgroundWorker that allows me to send progress percent and messages, without any System.Windows.Forms (SWF) references. This is important, since one of my rules is that business / data library code should NEVER refer to UI components aka SWF. This allows me to test my library code more easily, and who knows, some parts of the application might be used in a batch environment. Perhaps my file import code could be hooked to a directory watcher that imports new files and then moves them to /loaded or /error subdirectories. If my import code popped up a MessageBox under any circumstance, I could never use it in an automated fashion. Here is some pseudo-code:
// Button Event
private void BtnCreate_Click(sender, event)
{
try
{
DateTime selectedMonth = GetDateFromUI();
CreateWidgetSet(selectedMonth);
}
catch (Exception ex)
{
LogError(ex);
MessageBox("An error occurred while trying to create a widget set.\n\n" + ex.Message,
Title, MessageBoxButtons.OK);
}
}
// business action method
private void CreateWidgetSet(DateTime selectedMonth)
{
// actual business method
Widget.CreateASet(selectedMonth);
}
Comments