Saturday, May 30, 2009

On .NET : Components.Efficient.Design



Recently, as I was extending a product my company is working on,  I found something that in my humble opinion can be treated as an inconsistency of Microsoft .NET development team. 

First of all, let me point out that my views are heavily influenced by OO and I usually try to enforce as much encapsulation as possible in my UDTs , because that's one of the core benefits of OO . For me that's the most important benefit of the OO, as it reduces my stress level when managing components in a project. 
So, my issue is with .NET System.Windows.Forms.ListView component . You see, if I'm  creating one or more  ListViewSubItem instances via the new ListViewSubItem(ListViewItem owner, string name) constructor, I am expecting that the .NET will automatically copy this sub-item into ListViewItem's SubItems collection. However it does not, and I have to invoke the AddRange method on that collection each time I'm doing an operation on a ListView.   Now for me , it just creates unecessary overhead, almost always incurs additional time costs as I practically always forget about this tiny fact . 

To be honest, surely, I too  make such mistakes when designing my own components, so I've come up with an evergrowing checklist for OO component compliancy enforcement. Bear in mind that these are here just so that one can reduce the stress factor, and not to claim that one follows some fancy philosophy, for the sake of it. 

W/O further ado :
1. If you find simple code sequences repeating themselves, put them in a method

2. If you use an empty constructor, and afterwards setup the UDT instance by various method calls or value copying, consider a constructor that does this for you.

3. Test-case your UDT's method to check for possible inconsistencies. For example , if you find your self writing code like this: 

UDT a= new UDT();
int nFirst = a.First;
int nLast = a.Last;
if(nLast!=nFirst)
{
DoSomething();
}
You might be better off with something like this : 

UDT a= new UDT();
if( a.IsEqualityOK() )
{
DoSomething();
}
In the latter example , you have not only reduced the clutter in main logic block, but have also made it much easier to maintain your  comparison logic later on, as you might need to check for equality in other code blocks as well. 


4. When creating  complex data types, layered one on top of another, make sure that one method call does all that is required . 

5. When primitive types  count starts growing in your UDT (i.e. you have 50 string properties), it might be wise to group them into smaller UDTs, and provide accessors . This of course is managed by your own prefs. And there is always the issue on not overdoing with the UDTs. In my experience, a UDT is of optimally managed when it contains at most 10 properties and at most 5 methods. Of course, this is my ideal case, which is not always possible to maintain.

6. Design your components before you start coding, but do so in an environment-aware setting. That is , make sure you understand  how your component will be used. Setup a scenario on the whiteboard, explore possible interactions with other pieces of components , explore why these make sense (or not). This is IMHO the single most important step, altough it is often understood well only after running into trouble with a quick-designed component. Yeah, happened to me a few times too ..

7. If at all possible, try to keep one constructor for your component .

8. Reject the "Code first, refactor later" rule. If on the other hand you use this rule to write code efficiently and on-time, please accept my sincere apology and envy. In my humble opinion this rule is of no good.

9.  When you need to use threads, use .NET method delegates instead of standard threads (if your particular application need does not specifically call for use of System.Threading namespace of course) . It will make thread management far easier , as .NET creates and manages these threads for you, which bring us to point 10.

10. Keep program flow as simple as possible. And here is how I do it : I usually enforce a predictable program flow eventhough I really overuse the .NET delegates and events, by making all events and delegates go through a single "worker" class instance ,which is responsible for dispatching them further . That way I know that no matter what happens in my program, I have a single entry/exit point for all program data . It also gives additional benefit of  stripping other smaller UDT components of responsibility for main processing logic , so it narrows bug tracking to two essential areas :
  • Main logic
  • Data formatting and forwarding by UDTs
And that's about it. Simple, no ?


No comments:

Post a Comment