2008-07-18

ReSharper 4.0 Keymap

I keep forgetting where I find this pdf, so this is for future reference.

http://www.jetbrains.com/resharper/docs/ReSharper40DefaultKeymap2.pdf

Learning by Sample: Monorail and AspView

I really like the AspView View engine for Monorail, not that the other view engines are bad - but...
Intellisense and a familiar syntax just does it for me, however the documentation is lacking behind.

So I have converted the monorail getting started sample application to use aspview. Also it is now a Visual Studio 2008 solution.
I have added a SQLCE sql server as a database backend, and all dependencies are also included. So you can get started by just downloading, no other software has to be installed. I am assuming that you are using ReSharper, if not do you self a favor and install it now.

It is in my trunk, and there is a zip file for download.

Using the sample

All logging from nhibernate is logged by the trace writer, and should be visible in the Output/Debug window in VS.

 

Version: Both Monorail and AspView are trunk versions (Rev. 5227).

2008-07-15

The Seductive Visual Designer

An effective tool in the toolbox

Creating a user interface with a Visual Designer, makes a lot of sense due to the WYSIWYG experience. Shortening the feedback loop from editing content, to verification of the result. Prototyping, demoing and trying out different solutions, are the key strengths of the Visual Designer.

Moving towards a GUI-guide

Every tool has its shortcomings, no matter how good it is. Due to the flexibility of the Visual Designer, controls can be placed anywhere on the view, and size, color almost anything can be changed. So in order to create a uniform user experience across multiple views, a GUI-guide is often created.

The GUI-guide is usually written in Word, with examples and descriptions of how to layout and compose the GUI. So all GUI work is a manual merge process, of the Visual Designer and the GUI-guide.

Alternatives

Letting go of the Visual Designer sounds counter productive to most developers, but as with most tools there is a golden rule of when (not) to use it.

"It depends"

Its hard, and takes years of experience. Using any tool in the toolbox too much or too little, could have severe impact on productivity, maintainability and pretty much any other aspect of development.

When I first learned about database normalization, I though "what is it good for?". When I finally figured out why I should use it, I started to use it everywhere. Temporal data, auditing, you name it - everything was normalized. It took me years of experience, trying different levels of normalization before I was able to do it properly.

So instead of blindly using the tool, we should ask.

"Is this tool optimal for the task at hand, are there any alternatives?"

So if we need a GUI where there are multiple variations of the same theme, think "OrderDetailsView", "CustomerDetailsView", etc. Is the Visual Designer optimal for creating views, with components arranged after certain rules?

I don't think so, the Visual Designer exposes all behavior of all controls, and have no mechanism that allow rules to be applied across multiple views. You might think that User Controls fit the bill, but there is a big drawback with User Controls - They don't stack or combine! In my experience User Controls offer the worst form of reusability, I have ever encountered (An alternative could be a light weight micro controller).

So the Visual Designer exposes too much flexibility, and not enough automatability(is that a word?). I have seen dozens and dozens of views where tab order is not set correctly. Why, because it is a manual process to apply it. I have seen endless hours wasted because some control, was 1-10 pixels out of alignment or buttons was glued to the top and not the bottom of the view. Clearly the Visual Designer is not ideal for solving the problem at hand.

Automation

The art of codifying rules and processes, in essence what software developers do. We usually automate business rules for customers, but some times it is useful to automate our own business. Build scripts and Continuous Integration are good examples, but the possibilities are nearly endless. Figure out the most repetitive tasks you do, and automate them!

The framework trap

I often fall in this one, when automating some of my tedious tasks. I start to over design the automation, taken into account requirements that does not exist yet. Frameworks should not be build up front, but rather harvested from several projects.

Example

Suppose we have the following GUI-guides, for details views.

  1. All fields should have a label with a description.
  2. All fields should have a editing control. (textbox for text and numbers, checkbox for boolean and combobox for enums).
  3. All fields should be in a single column with label first and then the textbox next to it.
  4. Fields have a "xxx2" field, should be align next to each other in 2 columns. (name and name2 should be in a single row in 2 columns).
  5. Tab order goes from left to right and top to down. (name->name2->address->address2->country->email).

In the real world, the list would go on and on.

In Windows Forms we could manually create every details view, or we could create a details view builder.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Windows.Forms;
   4:  
   5: namespace NonVisualDesigner
   6: {
   7:     public class DetailsViewBuilder
   8:     {
   9:         private readonly List _detailsViewFieldModels;
  10:  
  11:         public DetailsViewBuilder()
  12:         {
  13:             _detailsViewFieldModels = new List();
  14:         }
  15:  
  16:         public DetailsViewBuilder AddField()
  17:         {
  18:             _detailsViewFieldModels.Add(new DetailsViewFieldModel());
  19:             return this;
  20:         }
  21:  
  22:         public DetailsViewBuilder WithName(string name)
  23:         {
  24:             _detailsViewFieldModels[_detailsViewFieldModels.Count - 1].Name = name;
  25:             return this;
  26:         }
  27:  
  28:         public DetailsViewBuilder WithType(Type type)
  29:         {
  30:             _detailsViewFieldModels[_detailsViewFieldModels.Count - 1].Type = type;
  31:             return this;
  32:         }
  33:  
  34:         public Panel Build()
  35:         {
  36:             Panel panel = new Panel();
  37:  
  38:             int tabOrderIndex = 0;
  39:             int rowIndex = 0;
  40:             DetailsViewFieldModel previousDetailsViewFieldModel = null;
  41:             
  42:             TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
  43:             foreach (DetailsViewFieldModel detailsViewFieldModel in _detailsViewFieldModels)
  44:             {
  45:                 Label label = new Label();
  46:                 label.Name = detailsViewFieldModel.Name + "Label";
  47:                 label.Text = detailsViewFieldModel.Name;
  48:                 Control editingControl;
  49:                 if(detailsViewFieldModel.Type == typeof(bool))
  50:                 {
  51:                     editingControl = new CheckBox();
  52:                 }
  53:                 else if (detailsViewFieldModel.Type.IsEnum)
  54:                 {
  55:                     editingControl = new ComboBox();
  56:                 }
  57:                 else
  58:                 {
  59:                     editingControl = new TextBox();
  60:                 }
  61:                 editingControl.Name = detailsViewFieldModel.Name + editingControl.GetType().Name;
  62:                 editingControl.TabIndex = tabOrderIndex;
  63:  
  64:                 int labelColumnIndex = 0;
  65:                 int editingControlColumnIndex = 1;
  66:  
  67:                 if (previousDetailsViewFieldModel != null && previousDetailsViewFieldModel.Name + "2" == detailsViewFieldModel.Name)
  68:                 {
  69:                     labelColumnIndex = labelColumnIndex + 2;
  70:                     editingControlColumnIndex = editingControlColumnIndex + 2;
  71:                     rowIndex--;
  72:                 }
  73:  
  74:                 tableLayoutPanel.Controls.Add(label, labelColumnIndex, rowIndex);
  75:                 tableLayoutPanel.Controls.Add(editingControl, editingControlColumnIndex, rowIndex);
  76:  
  77:                 previousDetailsViewFieldModel = detailsViewFieldModel;
  78:  
  79:                 rowIndex++;
  80:                 tabOrderIndex++;
  81:             }
  82:             tableLayoutPanel.Dock = DockStyle.Fill;
  83:             panel.Controls.Add(tableLayoutPanel);
  84:             return panel;
  85:         }
  86:     }
  87:  
  88:     public class DetailsViewFieldModel
  89:     {
  90:         public string Name;
  91:         public Type Type;
  92:     }
  93: }

A customer details view could then be created like this.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Windows.Forms;
   4:  
   5: namespace NonVisualDesigner
   6: {
   7:     public class CustomerDetailsView : Form
   8:     {
   9:         public CustomerDetailsView()
  10:         {
  11:             Panel panel = new DetailsViewBuilder()
  12:                 .AddField().WithName("Name").WithType(typeof (string))
  13:                 .AddField().WithName("Name2").WithType(typeof(string))
  14:                 .AddField().WithName("Address").WithType(typeof(string))
  15:                 .AddField().WithName("Address2").WithType(typeof(string))
  16:                 .AddField().WithName("Phone").WithType(typeof(string))
  17:                 .AddField().WithName("Fax").WithType(typeof(string))
  18:                 .AddField().WithName("Email").WithType(typeof(string))
  19:                 .AddField().WithName("Gender").WithType(typeof(Gender))
  20:                 .AddField().WithName("Active").WithType(typeof(bool))
  21:                 .Build();
  22:             panel.Dock = DockStyle.Fill;
  23:             Controls.Add(panel);
  24:         }
  25:     }
  26:  
  27:     public enum Gender
  28:     {
  29:         Male,
  30:         Female
  31:     }
  32: }

It looks like this.

BuilderCustomerDetailsView

Conclusion

Carefully consider if a tool is appropriate to solve a problem, always try to find alternatives. Automate guidelines and rules written in non-executable form. Save your self from the pain of manual, repetitive and error prone tasks.

2008-07-10

Visual Studio #regions are a code smell

Why do #regions exists?

My best guess is that back in the the good old .NET 1.0 and 1.1 days, before partial classes, MS needed a way to visually remove, all the noise the auto generated Windows Forms code made. It kind a made sense, because you don't need to understand the auto generated code, you can just open the form in design view.

Why use #regions?

Often I see #regions used to group different code artifacts, like "Fields", "Constructors", "Properties", "Events", "Methods" etc. When the class gets big, navigating to an event handler becomes as easy as:

  1. Collapse all "regions.
  2. Expand the "Events" region
  3. Start to scroll for the event handler.

That is faster than scrolling through all the code in a big file.

Do we need #regions?

As with most other "coding tools", #regions tackle complexity. A class with several thousands lines of code, can be visually laid out in 20 lines or so. If you have a class with many, many lines of code, #regions do offer an advantage over normal layout. The #regions don't take up to much space compared to the total number of lines, so it is easy to ignore them if not needed. But if the #regions makes sense, they can be used to slice the lines into categories.

An obvious problem with #regions

Like any other categorization it falls apart if there are multiple dimensions. What if we are looking for an event handler, but the #regions define business rules?

My wife categorizes our pictures by month. She creates a folder with a "Month Year" name, and places pictures taken that month there. Every now and then we need to find a picture of someone, it could be my son. The problem with the "Month Year" categorization is that is does not help, we need a "who is on the picture" categorization.

If google created a fixed categorization, it would be much harder to find anything. Because the same topic could be categorized with many dimensions.

A not so obvious code smell lurking behind the #regions

So if #regions are good at categorizing many lines of code, why is it a code smell to use them?

This leads to another question.

>Why do we need to categorize?

The answer is easy.

>Because of the many lines of code.

And finally the question we should be asking

>Why do we allow many lines of code in a single file?

A single class with many lines of code, is called a god object. It is a well known anti-pattern, and will kill maintenance. I will not in depth with the god obejct anti-pattern, but it is well described elsewhere.

Moving away from the god class

Is it possible? It most certainly is...

CustomerDetailsForm

This form god'ified could look like this in code.

   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace GodClass
   5: {
   6:     public partial class CustomerDetails : Form
   7:     {
   8:         public CustomerDetails()
   9:         {
  10:             InitializeComponent();
  11:         }
  12:  
  13:         #region Events
  14:         private void CustomerDetails_Shown(object sender, EventArgs e)
  15:         {
  16:             DefaultFocus(_nameTextBox);
  17:         }
  18:  
  19:         private void _saveButton_Click(object sender, EventArgs e)
  20:         {
  21:             FocusAll(_nameTextBox, _name2TextBox, _addressTextBox, _address2TextBox, _phoneTextBox, _emailTextBox);
  22:             //Save the object
  23:         }
  24:  
  25:         private void _nameTextBox_TextChanged(object sender, EventArgs e)
  26:         {
  27:             TextBoxRequired(_nameTextBox);
  28:         }
  29:  
  30:         private void _name2TextBox_TextChanged(object sender, EventArgs e)
  31:         {
  32:             MoveTextToTextBoxIfTextIsEmpty(_nameTextBox, _name2TextBox);
  33:         }
  34:  
  35:         private void _addressTextBox_TextChanged(object sender, EventArgs e)
  36:         {
  37:             TextBoxRequired(_addressTextBox);
  38:         }
  39:  
  40:         private void _address2TextBox_TextChanged(object sender, EventArgs e)
  41:         {
  42:             MoveTextToTextBoxIfTextIsEmpty(_nameTextBox, _name2TextBox);
  43:         }
  44:  
  45:         private void _phoneTextBox_TextChanged(object sender, EventArgs e)
  46:         {
  47:             TextBoxRequired(_phoneTextBox);
  48:         }
  49:  
  50:         private void _emailTextBox_TextChanged(object sender, EventArgs e)
  51:         {
  52:             TextBoxRequired(_emailTextBox);
  53:         }
  54:  
  55:         #endregion
  56:  
  57:         #region Procedures
  58:         public void DefaultFocus(Control control)
  59:         {
  60:             control.Focus();
  61:         }
  62:  
  63:         public void TextBoxRequired(TextBox textBox)
  64:         {
  65:             if (string.IsNullOrEmpty(textBox.Text))
  66:             {
  67:                 textBox.Focus();
  68:             }
  69:         }
  70:  
  71:         public void FocusAll(params Control[] controlsToFocus)
  72:         {
  73:             foreach (Control controlToFocus in controlsToFocus)
  74:             {
  75:                 controlToFocus.Focus();
  76:             }
  77:         }
  78:  
  79:         public void MoveTextToTextBoxIfTextIsEmpty(TextBox firstTextBox, TextBox secondTextBox)
  80:         {
  81:             if (string.IsNullOrEmpty(firstTextBox.Text))
  82:             {
  83:                 firstTextBox.Text = secondTextBox.Text;
  84:                 secondTextBox.Text = "";
  85:             }
  86:         }
  87:         #endregion
  88:     }
  89: }

In addition, the tab order is set in the visual designer.

The problem with this sample is ofcourse, it is not several thousans of lines.

If we wanted to de-god'ify the class it could look like this.

   1: using System.Windows.Forms;
   2:  
   3: namespace NonGodClass
   4: {
   5:     public partial class CustomerDetails : Form
   6:     {
   7:         public CustomerDetails()
   8:         {
   9:             InitializeComponent();
  10:             new DefaultFocus(this, _nameTextBox);
  11:             new TextBoxRequiredMicroController(_nameTextBox);
  12:             new TextBoxRequiredMicroController(_addressTextBox);
  13:             new TextBoxRequiredMicroController(_emailTextBox);
  14:             new TextBoxRequiredMicroController(_phoneTextBox);
  15:             new ValidateFormMicroController(_saveButton, _nameTextBox, _name2TextBox, _addressTextBox, _address2TextBox,
  16:                                             _phoneTextBox, _emailTextBox);
  17:             new MoveTextToTextBoxIfTextIsEmptyMicroController(_nameTextBox, _name2TextBox);
  18:             new MoveTextToTextBoxIfTextIsEmptyMicroController(_addressTextBox, _address2TextBox);
  19:         }
  20:     }
  21: }

Things to notice, there are zero regions!

The microcontrollers look like this.

   1: using System.Windows.Forms;
   2:  
   3: namespace NonGodClass
   4: {
   5:     public class TextBoxRequiredMicroController
   6:     {
   7:         private readonly TextBox _textBox;
   8:  
   9:         public TextBoxRequiredMicroController(TextBox textBox)
  10:         {
  11:             _textBox = textBox;
  12:             _textBox.TextChanged += ((sender, e) => FocusIfEmpty());
  13:         }
  14:  
  15:         private void FocusIfEmpty()
  16:         {
  17:             if (string.IsNullOrEmpty(_textBox.Text))
  18:             {
  19:                 _textBox.Focus();
  20:             }
  21:         }
  22:     }
  23:  
  24:     public class ValidateFormMicroController
  25:     {
  26:         private readonly Button _button;
  27:         private readonly Control[] _controlsToFocus;
  28:  
  29:         public ValidateFormMicroController(Button button, params Control[] controlsToFocus)
  30:         {
  31:             _button = button;
  32:             _controlsToFocus = controlsToFocus;
  33:             _button.Click += ((sender, e) => FocusAll());
  34:         }
  35:  
  36:         private void FocusAll()
  37:         {
  38:             foreach (Control controlToFocus in _controlsToFocus)
  39:             {
  40:                 controlToFocus.Focus();
  41:             }
  42:         }
  43:     }
  44:  
  45:     public class DefaultFocus
  46:     {
  47:         private readonly Form _form;
  48:         private readonly Control _defaultControlToFocus;
  49:  
  50:         public DefaultFocus(Form form, Control defaultControlToFocus)
  51:         {
  52:             _form = form;
  53:             _defaultControlToFocus = defaultControlToFocus;
  54:             _form.Shown += ((sender, e) => FocusDefaultControl());
  55:         }
  56:  
  57:         public void FocusDefaultControl()
  58:         {
  59:             _defaultControlToFocus.Focus();
  60:         }
  61:     }
  62:  
  63:     public class MoveTextToTextBoxIfTextIsEmptyMicroController
  64:     {
  65:         private readonly TextBox _firstTextBox;
  66:         private readonly TextBox _secondTextBox;
  67:  
  68:         public MoveTextToTextBoxIfTextIsEmptyMicroController(TextBox firstTextBox, TextBox secondTextBox)
  69:         {
  70:             _firstTextBox = firstTextBox;
  71:             _secondTextBox = secondTextBox;
  72:             _secondTextBox.TextChanged += ((sender, e) => MoveText());
  73:         }
  74:  
  75:         private void MoveText()
  76:         {
  77:             if (string.IsNullOrEmpty(_firstTextBox.Text))
  78:             {
  79:                 _firstTextBox.Text = _secondTextBox.Text;
  80:                 _secondTextBox.Text = "";
  81:             }
  82:         }
  83:     }
  84: }

Even more these MicroControllers are reusable in the next form!

Things normally done(or forgotten) in the visual designer can also be micro controlled.
Tab order is as easy as.

   1: namespace NonGodClass
   2: {
   3:     public class TabOrderMicroController
   4:     {
   5:         public TabOrderMicroController(params Control[] controls)
   6:         {
   7:             for (int i = 0; i < controls.Length; i++)
   8:             {
   9:                 controls[i].TabIndex = i;
  10:             }
  11:         }
  12:     }
  13: }

Conclusion

If you use #regions, you might need to apply separation of concerns. This forces your code to be more readable, although it creates a navigation issue, when having lots of classes.