Recently we encountered a problem with the client application I had been developing. The application features a force logoff, whereby when the device is connected to an external power source the application will log the current user off. This was designated a requirement by the client to prevent confusion amongst factory workers. However, the flaw was that the force logoff caused dispose problems with the main form. I wanted to detail the process of solving the problem.
When I initially designed the system I designated the Login form act as a “host” for the application, that is used the ShowDialog method to effectively place the main form over the login form. While this seemed to work, after some time the QA team began complaining about a crash when the application was removed from a charging cradle. We examined this problem over the period of a day and determined that the form was seeing part of it garbage collected. You see, this form is rather large so my attempt was to keep a reference to it and simply hide/show the main form as needed per the login process.
Our first attempt was to completely decouple this process: thus a new instance would be created each time within a using statement. We knew this would hamper performance as the application transitioned; but we would rather not have the application crash. On the whole, this approach seemed to work and so QA was made happy….for a day. It started when one of the QA members had the application crash with a similar error, but this time coming from a different place. Furthermore, the change seemed to permit the existence of ”zombie” forms under certain situations. Data became corrupted due to double execution of certain operations. It was clear we had to return to the drawing board.
A two hour brainstorm session yielded some ideas, but nothing we felt would actually work. That night at my hotel I was watching Futurama and playing with idea on a notepad when suddenly it hit me. The application uses a strategy pattern to carry out workflows which map to business operations. My thought was, what if I consider the entire application to be one giant workflow. This would allow me to lose the multi-form interactions that were giving us grief and make the application simpler. A firm belief of mine is that a refactor should be more about removing code then about adding code.
The next morning I presented the idea to the client and my team. At this stage I am on my way out with this client as I return to Michigan, so there is a need for me, as the lead, to begin writing concept documentation. However, it was decided that I was the only one who had the vision and could carry this change out quickly, thus I took the task.
At its root the problem is very easy. 1) Transform the login form into a login user control 2) Split the existing MainForm apart so that a user control is used for this process that fits into the “workflow” 3) update type loading so that we can operate on a higher abstraction then those simply for control involved in workflows.
This provides an example of why it is important to decouple components of responsibilities in an application and why the use of a consistent abstraction is recommended for application. UserControls in this system all inherent from one of two classes: WorkflowUserControlBase and UserControlBase. As you might guess from the naming, WorkflowUserControlBase inherits from UserControlBase. This is so when working with the components within the plumbing I dont need to worry about what control I am working with, I can operate on its abstraction; however these were all coded for WorkflowUserControlBase because only workflow control were involved in this process. This is why the definition of workflow within the application had to be changed.
The workflows operate via .NET events which allow for highly decoupled pieces. It made this substantially easier as my changes never went beyond the entry point tier, a good sign of an orthagonal design. The only concession I had to make was while it would be ideal to fully implement the concept of a ProcessWorkflow which contains subworkflows upon subworkflows managed through the Strategy pattern, the time given to me simply did not allow for it, thus I improvised.
I coupled (loosely) the LoginUserControl and MainMenuUserControl in a symbiotic relationship. The login will take the user to the main menu on a successful login and the main menu will load the login user control if a logout is requested. I was even able to move our splash screen from the login form into the main form. Remember the main menu acted (and still does) as a launch pad for the rest of the application. In a properly developed application Point C should be launched from Point B. But Point C should NOT care about Point B was reached. This is the very essence of decoupling and orthagonality.
The end result is a much cleaner, straightforward, and simpler application; which is what a refactor should always yield. With a single form managing the entire application, the chances of the bug rearing its head again are nearly non-existent (never say never, especially with computers). There are still some pending issues with the application which I will not be able to get to ahead of my departure back to Michigan on Friday, however, I can rest easy knowing that I went from knowing nothing about developing with Compact Framework to developing a solid stable application that will help to improve the clients productivity; and lets not forget the lessons that I have learned that will me with future development projects.