Saturday, December 12, 2009

Design Review and the DRY Principle

DRY in the DRY principle is an acronym for Don’t Repeat Yourself.  While we are going to focus on applying the principle to code, it can and should be applied to more than just code.  The can be applied to database schemas, tests, documentation, test plans, design documents, build scripts and more.  Any time we start duplicating our work we are signing up for parallel maintenance and chances are we will eventually have the items fall out of synch.

We can use several techniques to avoid duplicate code.  Some obvious things we can do include using classes and methods to organize common code.  We can write an abstract or base class to implement common behaviors for multiple derived classes. 

We can use properties to perform a validation in one place rather than performing the validation anywhere we want to use a member variable:



Code Snippet



  1.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  2.         int aComplexInteger;
  3.         public int AComplexInteger
  4.         {
  5.             get { return aComplexInteger; }
  6.             set
  7.             {
  8.                 if (value == 0)
  9.                     throw new ArgumentOutOfRangeException("AComplexInteger");
  10.                 if (value != aComplexInteger)
  11.                 {
  12.                     aComplexInteger = value;
  13.                     //Maybe raise a value changed event
  14.                 }
  15.             }
  16.         \




One of my favorite techniques is constructor chaining.  If we have multiple constructors that perform similar logic , we should use constructor chaining to avoid duplicating code:



Code Snippet



  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace CicMaster
  6. {
  7.     class BetterConstructorLogic
  8.     {
  9.         #region WhyItIsBetter
  10.         //No duplicate code, this is called constructor chaining
  11.         //step through this
  12.         #endregion
  13.         string someString = string.Empty;
  14.         int someInteger = 0;
  15.         List<int> myIntegers = new List<int>();
  16.         public BetterConstructorLogic():this("A Default Value")
  17.         {
  18.             //someString = "A Default Value";
  19.             System.Diagnostics.Debug.WriteLine("In Default Constructor");
  20.         }
  21.         public BetterConstructorLogic(string aString):this(aString,123)
  22.         {
  23.             //someInteger = 123;
  24.             System.Diagnostics.Debug.WriteLine("In one param constructor");
  25.         }
  26.         public BetterConstructorLogic(string aString, int anInteger)
  27.         {
  28.             System.Diagnostics.Debug.WriteLine("In two param constructor");
  29.             someString = aString;
  30.             someInteger = anInteger;
  31.             myIntegers.Add(someInteger);
  32.             myIntegers.Add(someInteger ^ 3);
  33.             myIntegers.Add((int)(2 * 3.14 * someInteger));
  34.         }
  35.     }
  36. }




The final technique I would like to mention is use a factory to create all but the simplest objects.  The following (admittedly nonsensical) code needs to execute maybe a half dozen lines of code to construct an Order object. 



Code Snippet



  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. namespace BusinessLayer
  7. {
  8.     class ObjectContext
  9.     {
  10.         public ObjectContext(String username)
  11.         {
  12.             //Look up the user permissions
  13.         }
  14.         public bool IsInTranaction { get; set; }
  15.         public bool BeginTransaction()
  16.         {
  17.             //do some transaction logic
  18.             return true;
  19.         }
  20.     }
  21.     class Order
  22.     {
  23.         public Order(ObjectContext theContext)
  24.         {
  25.         }
  26.     }
  27.     class Consumer
  28.     {
  29.         public Consumer()
  30.         {
  31.             ObjectContext ctx = new ObjectContext(Thread.CurrentPrincipal.Identity.Name);
  32.             if (!ctx.IsInTranaction)
  33.             {
  34.                 if (ctx.BeginTransaction())
  35.                 {
  36.                     //...
  37.                 }
  38.             }
  39.             Order order = new Order(ctx);
  40.         }
  41.     }
  42. }




Duplicating these few lines of code in a couple places is not that difficult.  Now say the application is enhanced and grows for a few years and suddenly we see this code duplicated dozens or hundreds of times.  At some point it is likely that we want to change the construction logic, finding and changing all the code we use to create the order is difficult, time consuming, and a QA burden. 

A better approach would be to encapsulate the logic required to build a new order.  Here is an implementation using a simple factory.  It is much easier to find, change, and test this code:



Code Snippet



  1.     static class OrderFactory
  2.     {
  3.         public static Order GetOrder()
  4.         {
  5.             ObjectContext ctx = new ObjectContext(Thread.CurrentPrincipal.Identity.Name);
  6.             if (!ctx.IsInTranaction)
  7.             {
  8.                 if (ctx.BeginTransaction())
  9.                 {
  10.                     //...
  11.                 }
  12.             }
  13.             return new Order(ctx);
  14.         }
  15.     }
  16.     class DryConsumer
  17.     {
  18.         public DryConsumer()
  19.         {
  20.             Order order = OrderFactory.GetOrder();
  21.         }
  22.     \




If we recognize that we are duplicating even a few lines of code over and over we need to take a serious look at the code and figure out a way to encapsulate the logic.

 

My design review checklist

No comments:

Post a Comment