views:

182

answers:

5

i want to not repeat myself (DRY), but i cannot have a single piece of code. For example here is code repeated 3 times with the same bug:

class StarWars : Movie
{
   //Calculate "base ^ exponent"
   public float Power(float base, float exponent)
   {
      return (base * exponent);
   }
}

class Customer: Object
{
   //Calculate "base ^ exponent"
   public float Exponential(float base, float exponent)
   {
      return (base ^ exponent);
   }
}

class Student: Person
{
   //Calculate "base ^ exponent"
   public float CalculateExpoential(float base, float exponent)
   {
      return CalculateExponential(2.7182818, exponent * Ln(base));
   }
}

Now ideally i would have extracted this common function into it's own helper somewhere:

class LibraryOfHelperCode
{
    public static float Exponentiation(float base, float exponent)
    {
       return Exp(2.71828183, base * Ln(exponent));
    }
}

And converted the existing code to use it:

class StarWars : Movie
{
   //Calculate "base ^ exponent"
   public float Power(float base, float exponent)
   {
      return LibraryOfHelperCode.Exponentiation(base, exponent);
   }
}

class Customer: Object
{
   //Calculate "base ^ exponent"
   public float Exponential(float base, float exponent)
   {
      return LibraryOfHelperCode.Exponentiation(base, exponent);
   }
}

class Student: Person
{
   //Calculate "base ^ exponent"
   public float CalculateExpoential(float base, float exponent)
   {
      return LibraryOfHelperCode.Exponentiation(base, exponent);
   }
}

The value is that now i've extracted the repeated code from

  • Power
  • Exponential
  • CalculateExpoential

into a single function. This means that if there are any bugs, they only have to be fixed once. Which is good in this case, because there is a bug:

   public float CalculateExpoential(float base, float exponent)
   {
      //19971012: Oops, should be natrual log of base, not exponent
      return Exp(2.71828183, exponent * Ln(base));    
   }

and a few years after that:

   public float CalculateExpoential(float base, float exponent)
   { 
      //19990321: Oops, need to handle when exponent is zero
      if (exponent == 0)
         return 1.0;

      //19971012: Oops, should be natrual log of base, not exponent
      return Exp(2.71828183, exponent * Ln(base));    
   }

and later on:

   public float CalculateExpoential(float base, float exponent)
   { 
      //19990321: Oops, need to handle when exponent is zero
      if (exponent == 0)
         return 1.0;

      //20040523: Another special case
      if (Base = 0.0) && (Exponent > 0.0) then
         return 0.0; // 0**n = 0, n > 0

      //19971012: Oops, should be natrual log of base, not exponent
      return Exp(2.71828183, exponent * Ln(base));    
   }

and finally:

   public float CalculateExpoential(float base, float exponent)
   { 
      //20101027: Microsoft just release a method in .NET framework 4.0 that does
      //what we need. Use it:
      return Math.Pow(base, exponent);
   }

And everyone gets the fixes. On the other hand, i cannot guarantee that any one of those increment fixes won't break existing code.

Imagine a guy was calling:

char ps = Math.Trunc(Exponential(ProblemSize, ProblemComplexity));

and was never expecting the value to be larger than 128. He was wrong. And while the code was wrong all this time: it happened to work.

Now i come along and fix things, and suddenly code is crashing due to overflow and/or wraparound.


The problem i'm facing today is that a change in DRY common code affects everywhere it's used. The only acceptable (polotical) solution is to keep a copy of the library class for every executable/moduble/namespace/class that uses it.

Undoing any DRYness.

Is there any way out of this mess? When i can not repeat myself, but continue to get fixes and improvements as they are added to the single DRY code?


i mean...should i have shared code, but branch it at every release? But the issue that is polotically nobody wants the code every to be reverse-integrated.

A: 

The only acceptable (polotical) solution is to keep a copy of the library class for every executable/moduble/namespace/class that uses it.

DRY is a principle of design for a particular sofware solution, but does not always make sense across assembly or domain bounds. The methodology of Domain Driven Design uses concepts such as bounded domain contexts to deal with issues of shareable code across assemblies and projects.

While you have given us a problem in generic language, there is no generic solution for this issue.


Dan G makes a good point about composition (making the main root object contain a sub object that can implement the needed behavior without having to use implementation). Microsoft's Architecture guide advocates this approach over inheritance whenever it makes sense.


I would upvote Meager and Dan G if I were able, they both made good comments.

wllmsaccnt
A: 

I don't think that having a helper class in each of your classes that use this functionality really undoes the DRY-ness. Even though you do repeat the inclusion/declaration of the helper everywhere, that then gives you the power to not have to repeat any of the further functionality.

Any architectural changes you make (such as either inheritance or a helper) are going to have to affect everything that uses them in some way, and if inheritance doesn't make sense, composition with some kind of Date object or Helper is probably a good way to go.

Dan G
+1  A: 

Your programs should only break if you change your library's interface. If changing the implementation of the library breaks your program, you're probably binding the program to the library too strongly. The program shouldn't depend on the library's internal workings. If you're constantly changing your library's interface and breaking your projects, you probably need to spend more time designing your library.

You should also use version control for your libraries. Build your code against a specific branch/version of the library. If the libraries interface changes significantly and you don't want to update the existing projects, make a new branch for the new interface, which new projects can use, while old projects can continue using the old branch. Bug fixes can be written against one branch and merged into the other.

Git is particularly good at this sort of thing. Use submodules to link your project to a specific commit of a library.

meagar
We do something similar. A "References" folder exists in a branch, with the compiled components that are used in that branch.
StingyJack
In this example, where the person is calling `GetCurrentDate` it is documented to return a the date (i.e. no time portion). But through a bug in the calling code they come to *depend* on the bug in `GetCurrentDate`. If i fix `GetCurrentDate` to return what it's documented to return, then i can break calling code. i.o.w: i didn't change the interface, i fixed a bug.
Ian Boyd
Pretend `GetCurrentDate` is actually `Power(base, exponent) { return base * exponent; }` Obviously the code is wrong - but though come strange quirk: calling code will break if `Power` is fixed.
Ian Boyd
@Ian Then you've discovered a bonus bug in your program (hopefully via automated unit testing) and can fix it! You've also discovered a point where the code depends too heavily on the implementation of the library.
meagar
@meagar: How could i not depend on the implementation of the library? If the library says it calculates `SIN(90)`, i expect it to come back as one...
Ian Boyd
@Ian If you know that a library function is broken and not returning what it is supposed to, you should avoid that function, not write your program to depend on broken behaviour which is almost certain to change in a subsequent revision. If calling `sin(x)` actually returns `sqrt(x)`, you probably shouldn't write your program assuming that this obviously incorrect behaviour will never be fixed.
meagar
@meagar It would be great to have perfect code, or where changing things never results in incompatibilities. And while i would love to fix the calling code, you have to now explain it, *"Why did you change this?" "Well, i changed code in this client's application ,without being asked, because it was broken by change in library code that was wrong." "But the old version worked fine." "Well, that's only because the code it was using was broken."*
Ian Boyd
A: 

It would seem that your issue is not so much one of DRY-ness but rather version control of dependencies. So what you have is a situation where your helper class (with the bug fix) is a dependency of each of your primary classes. However, only one of the primary classes actually references the version of the helper class that contains the fix. The other two classes are at liberty to choose when they will upgrade to the improved helper class (via a dependency management process).

StarWars-1.0.0.jar -> Helpers-1.0.0.jar
Empire-1.0.1.jar -> Helpers-1.0.1.jar
Jedi-1.0.0.jar -> Helpers-1.0.0.jar

The helpers team issue an update to fix stuff, and the other teams decide when they will upgrade.

You're still DRY, but you manage the change.

Gary Rowe
Unfortunately (or fortunately) the environment i use doesn't support jars (or assemblies, or classes exported from DLLs). Plus, from a aesthetic and practical point of view i prefer the single executable. (you don't see uTorrent shipping with 30 dlls)
Ian Boyd
@Ian Fair point (I too like uTorrent for it's compactness). In that case, perhaps making judicious use of automated testing will highlight areas that have been affected by the bug fix. Software evolves and DRY is a good way to control that evolution.
Gary Rowe
A: 

It seems that part of the problem here is the normal result of changing requirements. Three pieces of code rely on the common business concept of "Today". Something has changed such that there is no longer a single concept for "Today." Some of the code needs to continue to the current understanding based on the galactic standard calendar and some code now needs a more flexible concept based on the local star system's calendar (talk about a localization headache!).

This is just a normal thing that happens. Put in SRP terms, what was a single responsibility, handled by a single piece of code is now two responsibilities that needs to be handled by separate pieces of code because a new vector for change has been identified. Time to refactor.

Piskvor suggested one way to tackle the problem which is good in some scenarios. Another approach, if you are using IOC containers, or at least some form of DI, would be to introduce an IHelper interface (hopefully it was there from the start). Then all you need is new implementation of the interface and some configuration to wire up the correct implementation for each system.

wllmsaccnt is also correct that DRY is often not appropriate across system boundaries where you don't want to create dependencies. The fact that different systems have different product owners is pretty much a defacto vector for change that argues for a separate code bases, even if one starts out as a fork of the other. A shared code base means shared ownership and if that is not politically realistic then its not technically appropriate.

Kenneth Baltrinic
Very well put. I'd give +1 but I'm out of votes for the day.
Spudley
In this case it wasn't a change in business requirements. The change was fixing the bug that the refactored function used to get "today" was also returning a time. People calling the function expecting the returned value to only represent a "day" would/could/did have error because they assumed (correctly) that the time portion would be "empty" (e.g. 0, midnight, etc)
Ian Boyd