Wednesday, March 31, 2010

Open/Closed Principle, Part II

In the first post, we took a look at what the Open/Closed Principle is, why it is important, and how to use it. I’d like to continue the discussion with another example, and how the Strategy design pattern can help us out.

First, let’s take a look at the example code that we will be working with:

   1: public class ReportBuilder
   2: {
   3:     public ReportBuilder(Report report)
   4:     {
   5:         this.Report = report;
   6:     }
   7:  
   8:     public Report Report { get; private set; }
   9:  
  10:     public void BuildReport()
  11:     {
  12:         foreach (ReportField field in Report.Fields)
  13:         {
  14:             string valueToFormat = field.Data.ToString();
  15:             string valueToWrite;
  16:             CultureInfo current =
  17:                 CultureInfo.CurrentCulture;
  18:  
  19:             switch (field.FieldType)
  20:             {
  21:                 case "Date":
  22:                     DateTime dt =
  23:                         DateTime.Parse(valueToFormat);
  24:                     valueToWrite =
  25:                         dt.ToString("d", current);
  26:                     break;
  27:                 case "Currency":
  28:                     Double dbl =
  29:                         double.Parse(valueToFormat);
  30:                     valueToWrite =
  31:                         dbl.ToString("c", current);
  32:                     break;
  33:                 default:
  34:                     valueToWrite = valueToFormat;
  35:                     break;
  36:             }
  37:  
  38:             Report.Write(valueToWrite);
  39:         }
  40:     }
  41: }

This example takes a Report object, iterates its fields, and formats each based on its FieldType. This example is very simple, but is enough to illustrates code that is rather typical. So let’s see where we can make some improvements.

The most obvious place where we will get burned is in the BuildReport method. This method violates both the Single Responsibility Principle (SRP) and the Open/Closed Principle (OCP). What happens when the client wants a new format type? We compound the violation of both, thus making the code harder to read, test, and maintain. Remember, we want to try and affect as little existing code as we can, and change behavior through new code. To start, let’s introduce a simple IFieldFormatter interface.

   1: public interface IFieldFormatter
   2: {
   3:     public string Write(object data);
   4: }

Now, let’s create the formatters that will replace the logic in the switch statement in our BuildReport method:

   1: public class CurrencyFormatter : IFieldFormatter
   2: {
   3:     public string Write(object data)
   4:     {
   5:         CultureInfo current = CultureInfo.CurrentCulture;
   6:         double value = 0.00;
   7:  
   8:         if (!ReferenceEquals(data, null))
   9:         {
  10:             value = double.Parse(data.ToString());
  11:         }
  12:  
  13:         return value.ToString("c", current);
  14:     }
  15: }
  16:  
  17: public class DateFormatter : IFieldFormatter
  18: {
  19:     public string Write(object data)
  20:     {
  21:         CultureInfo current = CultureInfo.CurrentCulture;
  22:  
  23:         if (!ReferenceEquals(data, null))
  24:         {
  25:             DateTime value = DateTime.Parse(data.ToString());
  26:             return value.ToString("d", current);
  27:         }
  28:  
  29:         return string.Empty;
  30:     }
  31: }
  32:  
  33: public class NullFormatter : IFieldFormatter
  34: {
  35:     public string Write(object data)
  36:     {
  37:         if (ReferenceEquals(data, null))
  38:         {
  39:             return string.Empty;
  40:         }
  41:         return data.ToString();
  42:     }
  43: }

And now here is the updated BuildReport method:

   1: public void BuildReport()
   2: {
   3:     foreach (ReportField field in Report.Fields)
   4:     {
   5:         IFieldFormatter formatter;
   6:  
   7:         switch (field.FieldType)
   8:         {
   9:             case "Date":
  10:                 formatter = new DateFormatter();
  11:                 break;
  12:             case "Currency":
  13:                 formatter = new CurrencyFormatter();
  14:                 break;
  15:             default:
  16:                 formatter = new NullFormatter();
  17:                 break;
  18:         }
  19:  
  20:         Report.Write(formatter.Write(field.Data));
  21:     }
  22: }

This gets us closer to keeping in line with SRP and OCP.

The next step is to refactor that switch statement. In this case, I am going to move it into a new factory class:

   1: public interface IReportFieldFormatterFactory
   2: {
   3:     IFieldFormatter CreateFormatter(string fieldType);
   4: }
   5:  
   6: public class ReportFieldFormatterFactory 
   7:     : IReportFieldFormatterFactory
   8: {
   9:     public IFieldFormatter CreateFormatter
  10:         (string fieldType)
  11:     {
  12:         switch (field.FieldType)
  13:         {
  14:             case "Date":
  15:                 formatter = new DateFormatter();
  16:                 break;
  17:             case "Currency":
  18:                 formatter = new CurrencyFormatter();
  19:                 break;
  20:             default:
  21:                 formatter = new NullFormatter();
  22:                 break;
  23:         }
  24:     }
  25: }

You may be thinking that all I have really done is move the creation logic from one class to another, and technically you would be correct, but let us look a little deeper. Now our ReportBuilder can do just that, build reports without having to worry about low level details. We also have a class whose sole responsibility is to create formatters. This eliminates the need to update ReportBuilder every time we need to add a new formatter type.

Next, let’s update our ReportBuilder to accept an IReportFieldFormatterFactory parameter and update our BuildReport method:

   1: public class ReportBuilder
   2: {
   3:     private IReportFieldFormatterFactory 
   4:         fieldFormatterFactory;
   5:  
   6:     public ReportBuilder(Report report, 
   7:         IReportFieldFormatterFactory 
   8:             fieldFormatterFactory)
   9:     {
  10:         this.Report = report;
  11:         this.fieldFormatterFactory = 
  12:             fieldFormatterFactory;
  13:     }
  14:  
  15:     public Report Report { get; private set; }
  16:  
  17:     public void BuildReport()
  18:     {
  19:         IFieldFormatter formatter;
  20:  
  21:         foreach (ReportField field in Report.Fields)
  22:         {
  23:             formatter = fieldFormatterFactory
  24:                 .CreateFormatter(field.FieldType);
  25:  
  26:             Report.Write(formatter.Write(field.Data));
  27:         }
  28:     }
  29: }

Now when we need to add a new formatter, we can easily extend our existing factory and inject it into our ReportBuilder.

By adhering to the concepts behind the Open/Closed Principle we are able to modify existing functionality by adding new code and leaving the existing codebase alone. It also forces us to use other SOLID design principles which will keep us on the path to writing clean, flexible, testable, and maintainable code.

Wednesday, March 24, 2010

What’s in a Name?

I was asked a question the other day about a table layout:

My first table is tbl_Department with the columns as 'dept_id-auto generated', 'dept_name'.

My second table is tbl_Employee with the columns as 'emp_id-auto generated', 'emp_name', 'deptname'.

It is completely un-necessary to prefix database tables with tbl. It is an archaic practice that should be abandoned, outlawed, and punishable by exile from the development community.

Notice the field names. They were kind enough to name the tables Department and Employee, but then decided to abbreviate the table name as part of the field name (dept_id). Most developers are intelligent enough to discern that a field call ID in a table called Department is probably the unique value assigned to a department and used to identify it among all of the other departments.

Now don’t get me wrong. I am all for writing code that uses naming conventions that help to elucidate its intent and meaning. And, I will admit, my interfaces begin with “I”, but that does not mean adorning variables, methods, functions, classes, and tables with needless prefixes and/or suffixes is, as a practice, a good thing. It creates noise and rather than clarity.

Wednesday, March 17, 2010

Where Does Responsibility End?

As developers. we are responsible for creating the highest quality software products for our clients. That includes writing clean and maintainable code, thorough testing and QA, usability, aesthetic quality, and relevance to the business owner. It is what they deserve and expect from us as professionals by virtue of the fact that they are willing to invest money and faith in our ability to do so. But there are other implied responsibilities such as deployment and post-implementation support. This is where things start to get a little fuzzy.

Where does our responsibility really end and the user’s begin? For example, how far should we take testing for different operating systems and web browsers? I know the knee-jerk reaction might be, it depends on the business requirements, so let’s just say it is a public e-commerce site. Are we expected to test and  support versions of operations systems or browsers that even aren’t event supported by their developers any more? Must we support Windows 95 and IE4? Or, put another way, how many versions of compatibility are we testing for?

Personally, I would suggest that it is the user’s responsibility to keep their machines up to date. With automatic updates and free downloads, it couldn’t be simpler to maintain the health of your computer. And for those enterprises that are afraid to upgrade to IE7, are you still using Windows 3.1? Technology changes and I know that as developers, always looking to use the latest new fangled whatchamahoozit, we are partly to blame; out with the old and in with the new. But, as a responsible “netizen”, people must realize that the computer of today is the computer of tomorrow the minute you take it out of the box. That is just a harsh reality of technological dependency.

Wednesday, March 10, 2010

Add an Interface Using the Keyboard in Visual Studio

I realized long ago that using a keyboard-centric approach rather than a mouse-driven approach to coding is much more productive and efficient. As such, I thought it might be nice to share one keyboard function that isn’t currently available in Visual Studio – adding a new interface to a project.

Step 1: Create a new macro
Open Visual Studio and navigate to Tools > Macros > Marcos IDE… (or use the keyboard shortcut Alt+F11)

Step 2: Double-click one of the modules in the MyMacros project

Step 3: Past this code into the code window:

   1:     Sub AddInterface()
   2:  
   3:         Dim interfaceName As String = 
   4:             Microsoft.VisualBasic
   5:             .Interaction.InputBox("Name", 
   6:             "Add Interface")
   7:  
   8:         If Not interfaceName.ToLower.EndsWith(".cs") Then
   9:             interfaceName &= ".cs"
  10:         End If
  11:  
  12:         DTE.ItemOperations
  13:             .AddNewItem(
  14:                 "Visual C# Items\Code\Interface", 
  15:                 interfaceName)
  16:  
  17:     End Sub

Note: You may need to change the path “Visual C# Items\Code\Interface” to point to your interface template.

Step 4: Assign the macro to a Keyboard shortcut:

Navigate to Tools > Options… and in the Environment section select Keboard (there is no shortcut key for this by default, but I added on Ctrl+W, K). In the “Show commands containing” text box, type MyMacros to see a list of all your macros. Select AddInterface (the new macro we just added). Put your mouse cursor into the “Press shortcut keys” text box and press the keystroke you wish to use to cause the macro to run (mine is Ctrl+Shift+A,I). Click the Assign button and then Ok.

That’s all there is to it. Now when you use the assigned keystroke, an input box will display asking for the name of your new interface, and it will be added to your project.