DRY aka "
Don't
Repeat
Yourself" was first coined in the book
Pragmatic Programmer by Andrew Hunt and David Thomas. Here is the
snippet from the book explaining the principle:
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Why do we call it DRY?
DRY—Don't Repeat Yourself
"Don't Repeat Yourself" is just a short and punchy statement so it sticks around in the head. The principle is
based on three clear callouts: Single, Unambiguous, and Authoritative.
DAMP aka "
Descriptive
And
Meaningful
Phrases",
was coined by
Jay Fields. He
published a blog post comparing the two approaches and proposing that DAMP is required in scenarios where you
are working with DSL. I want to widen the lens here a bit and propose that they are complimentary. DAMP
emphasises being descriptive and meaningful, so does DRY on being unambiguous. Focusing on the intent they both
want the intent of the outcome clear and self-explanatory.
Show-Time!
Here is a sample code: class `ProductOffer` which stores the `actualPrice` and the `discountPercentage`. The
class is responsible for displaying formatted Prices and calculating the finalPrice.
class ProductOffer(float actualPrice, float discountPercentage)
{
public float ActualPrice { get; } = actualPrice;
public float DiscountPercentage { get; } = discountPercentage;
public float FinalPrice()
{
return ActualPrice - (ActualPrice * DiscountPercentage / 100);
}
public float Discount()
{
return (ActualPrice * DiscountPercentage / 100);
}
public string FormattedActualPrice()
{
return "AUD" + ActualPrice.ToString("0.00");
}
public string FormattedDiscount()
{
return "AUD" + (ActualPrice * DiscountPercentage / 100).ToString("0.00");
}
public string FormattedFinalPrice()
{
return "AUD" + (ActualPrice - (ActualPrice * DiscountPercentage / 100)).ToString("0.00");
}
}
This is not great as there are quite a few duplications in the code. The formatting itself is copy-pasted in
each method, prefix of currency is present in each of the methods responsible for formatting. And finally, the
discount and the final prices are calculated in 2 different places.
Let's start by DRYing out some of these duplications:
class ProductOffer(float actualPrice, float discountPercentage)
{
public float ActualPrice { get; } = actualPrice;
public float DiscountPercentage { get; } = discountPercentage;
public float FinalPrice()
{
return ActualPrice - (ActualPrice * DiscountPercentage / 100);
}
public float Discount()
{
return (ActualPrice * DiscountPercentage / 100);
}
public string FormattedActualPrice()
{
return FormattedPrice(ActualPrice);
}
public string FormattedDiscount()
{
return FormattedPrice(Discount());
}
public string FormattedFinalPrice()
{
return FormattedPrice(FinalPrice());
}
private static string FormattedPrice(float price)
{
return "AUD" + price.ToString("0.00");
}
}
That is looking better for sure, but is it??
While I have definitely DRYed it out, and we aren't seeing any duplication, I would argue whether it is
"unambiguous" and is "single". Also going by DAMP principles, neither was the first approach descriptive and
meaningful, nor I find this one adding a fair amount of meaning. The reason I make this argument is we have
taken a simplistic view of the class `ProductOffer`. Let's broaden our view, and take the lens of the
application which has other classes such as `Order`. There would be an overall price associated with the
`Order`, and `OrderLineItem` would also store the pricing information from a point-in-time aspect. All these
classes would be duplicating the code.
As a developer when I came across the first code snippet, I quickly identified duplication and I extracted it
out. I created an abstraction. It is in this case
"the wrong abstraction". This takes me to call out another
related principle AHA aka "Avoid Hasty Abstractions". The principle calls out for
Optimize for change first and
Avoid premature optimisations.
With this new-found mindset, we will write another iteration of the above with the new context:
class Price(float value, string currency)
{
public readonly float Value = value;
public readonly string Currency = currency;
public static Price operator +(Price first, Price second)
{
ValidateCompatibleCurrency(first, second);
return new Price(first.Value + second.Value, first.Currency);
}
public static Price operator -(Price first, Price second)
{
ValidateCompatibleCurrency(first, second);
return new Price(first.Value + second.Value, first.Currency);
}
public static Price operator *(Price price, float multiplier)
{
return new Price(price.Value * multiplier, price.Currency);
}
public string Format()
{
return Currency + Value.ToString("0.00");
}
private static void ValidateCompatibleCurrency(Price first, Price second)
{
if (first.Currency != second.Currency)
throw new InvalidOperationException("Cannot add prices with different currencies");
}
}
class ProductOffer(Price actualPrice, float discountPercentage)
{
public Price FinalPrice()
{
return actualPrice - (actualPrice * (discountPercentage / 100));
}
public Price Discount()
{
return actualPrice * (discountPercentage / 100);
}
}
This code above removes the need for all the formatting functions as the `Format()` now abstracts the formatting
away (You can even choose to abstract it by overriding `ToString()`, which I didn't prefer for myself as it
hides away the formatting). One may think, the line of codes in the examples have increased,
BUT this is reusable code, which would, as discussed above, be reused by `Order` and
`OrderLineItem`.
I would happily say it is now - DRY and DAMP 😛
Ultimately...
These compliment each other, rather than contradict -
There was a huge upsurge on DRY production code and DAMP unit tests, and as Vladimir points out in
his blog
it is a false dichotomy. You can DRY out your unit tests and also have them fairly descriptive as they serve as
live documentation. This can be applied to any aspect of the code, not just unit tests.
These are guiding principles not lines in stones - You should understand what works for the codebase
you own. It is okay to duplicate and sit on the duplication for a bit, until you feel right to abstract out. By
being mindful of the decisions you make and the impact they can have, iterative steps often navigate us to the
right directions.