Abstracting Your Code to Remove Duplication

Posted By: Jingyi

Every developer knows duplicated code is bad. We should avoid duplicating code. The following code snippets are from my own work.


public SomeMethod() {
string hiddenFunds = ".....";
string hiddenSubFunds = ".....";
string hiddenPledgeDrives = ".....";

proxy.Funds = ToFunds(hiddenFunds);
proxy.SubFunds = ToSubFunds(hiddenSubFunds);
proxy.PledgeDrives = ToPledgeDrives(hiddenPledgeDrives);

}

private FundCollection ToFund(string hidden) {
var ret = new FundCollection();

if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(new Fund {ID = int.Parse(arr[0]), Name = arr[1] });
        }
  }
  return ret;
}

private SubFundCollection ToSubFunds(string hidden) {
var ret = new SubFundCollection();

if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(new SubFund {ID = int.Parse(arr[0]), Name = arr[1] });
        }
  }
  return ret;
}

private PledgeDriveCollection ToPledgeDrives(string hidden) {
var ret = new PledgeDriveCollection();

if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(new PledgeDrive {ID = int.Parse(arr[0]), Name = arr[1] });
        }
  }
  return ret;
}

Look at these three private methods (ToFunds/ToSubFunds/ToPledgeDrives), they are almost identical. The only difference is the entities and its corresponding collections are different. Apparently, the code is duplicated. It is a pretty common scenario. How do we re-factor the code and remove the duplication? Let’s look at these 3 methods again. The return values are different, they represent three different collections. So, we should take it out and make it a variable. The second difference is the way generating the entities. Except for these 2 differences, the rest are same. Since C# supports generic, let’s make 2 generic variables to represent the returning collection and the way generating entity. So we can come up something as following:


private U ToList

(U collection, T generate, string hidden) {
if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(generate(...)); // we should pass arr as parameter to generate
}
      }
      return collection;
}

Of course, it won’t compile. What is generate here? generate is a delegate that takes 2 strings (arr[0] and arr[1]) and returns a different entity. So the type of generate should be a delegate that takes 2 string parameters and returns a unknown type entity.  .Net has already has something like that for us, it is Func, so we can change the above method as the following,


private U ToList

(U collection, Func

generate, string hidden) {
if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(generate(arr[0], arr[1]));
}
      }
      return collection;
}

Of course, it won’t compile. When you try to compile it, the compiler will complain about U doesn’t define a method Add. The good news is all these 3 collections inherit from List interface which defines a method called Add. So we can add a constraint to make compiler happy. After adding the constraint, the ToList method becomes


private U ToList

(U collection, Func generate, string hidden) where U : List

{
if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(generate(arr[0], arr[1]));
}
      }
      return collection;
}

The constraint where U: List T satisfies 1) U has a method called Add. 2). The element of U is T which satisfies the type requirement of Add method. Of course, now it compiles. After we have this generic method in place, the client code becomes the following.


public SomeMethod() {
string hiddenFunds = ".....";
string hiddenSubFunds = ".....";
string hiddenPledgeDrives = ".....";

proxy.Funds = ToList(new FundCollection(), (id, name) => new Fund {ID = int.Parse(id), Name = name}, hiddenFunds);
proxy.SubFunds = ToList(new SubFundCollection(), (id, name) => new SubFund {ID = int.Parse(id), Name = name}, hiddenSubFunds);
proxy.PledgeDrives = ToList(new PledgeDriveCollection(), (id, name) => new PledgeDrive {ID = int.Parse(id), Name = name}, hiddenPledgDrives);
}

The key here is abstracting. We have to identify the varied parts and abstracting them out. Of course it needs some thinking. The final code is not as straightforward as the duplicated one. But it localizes all the algorithm and logic in one place. The above code comes from my own work. It is an interesting experience. All the ugliness of generic variable declaration is due to the static type nature of the C# language. If you use any dynamical language like Python or Ruby, you don’t have to build the relationship between these type variables; however, you will lose the type checking though. If you like duck typing plus type checking, then you should give OCaml a try. That is a language that (I know of) offers type checking and duck typing behavior.

The above programming interface can be simplified further. By adding one more type type constraint, the collection parameter can be removed into the function body and the programming interface becomes:


private U ToList

(string hidden, Func generate) where U : List

, new() {
U collection = new U();
if(!hidden.IsEmpty()) {
String[] pairs = hidden.Split(';');
foreach (var pair in pairs) {
string[] arr = pair.Split(',');
collection.Add(generate(arr[0], arr[1]));
}
  }
  return collection;
}

That is it.

Jingyi Wang is a developer for Fellowship Technologies. He has been with Fellowship Tech since August of 2005. He is passionate about improving himself and is always striving to write better code than the day before.

 

Posted In: Tips,

Comments:
No one has commented yet. Be the first!
Commenting is not available in this channel entry.

Categories:

Previous Posts:


Subscribe to the RSS feed!