Dont Repeat Yourself (DRY) Principle

So the DRY principle is actually an interesting principle that I feel sometimes kinda conflicts with the optimizing to soon principle. The principle states that you should not repeat code pieces, so, for instance when you have a loop where you are iterating over a dictionary and you find that you run over the same / similar dictionary again, you should extract the loop into a method / function and use the function. You have seen this many times where you hit a shape in the code where you can see that the only difference is a variable or something that can easily be made fit using a parameter. This is the point were you would normally do this.

foreach (var item in revenueByMonth)
{
    PrintLine("Revenue", item.Key, item.Value);
}
 
foreach (var item in costByMonth)
{
    PrintLine("Cost", item.Key, item.Value);
}
 
// At this point you would usually extract:
void PrintSection(string label, Dictionary<string, decimal> values)
{
    foreach (var item in values)
    {
        PrintLine(label, item.Key, item.Value);
    }
}

I also know that if you try and generalize to soon that you can sit with so many of these functions and then you are set in your way of doing them that you do not even try to see if there is a better solution for handling it.

PrintSection("Revenue", revenueByMonth);
PrintSection("Cost", costByMonth);
PrintSectionAsPercent("Tax Rate", taxRateByMonth);
PrintSectionDescending("Top Products", productSales);
PrintSectionSkippingZeroes("Cashflow", cashflowByMonth);
PrintSectionWithTotal("Profit", profitByMonth);
 
// Now the first abstraction is showing strain.
// A better one is to make the differences part of the data:
record ReportSection(
    string Label,
    IEnumerable<KeyValuePair<string, decimal>> Values,
    string Format = "N2",
    bool SortDescending = false,
    bool SkipZeroes = false,
    bool ShowTotal = false
);
 
void PrintSection(ReportSection section)
{
    var items = section.SortDescending
        ? section.Values.OrderByDescending(item => item.Value)
        : section.Values;
 
    foreach (var item in items)
    {
        if (section.SkipZeroes && item.Value == 0)
        {
            continue;
        }
 
        PrintLine(section.Label, item.Key, item.Value.ToString(section.Format));
    }
 
    if (section.ShowTotal)
    {
        PrintTotal(section.Values.Sum(item => item.Value));
    }
}

I have found that I DO repeat myself, especially when I start doing the functionality, and try not to generalize before I hit like 4 to 6 of the same / similar shapes. Why, the abstraction from doing it from 4 to 6 is going to be WAY better than doing it from 1 or 2. Worse is when you right a function that you think will be dry and then you realize that you have like 4 to 6 versions of that method, catering for the use cases you could have solved better had you waited for those other use cases.

That is my take on the DRY principle, great principle to apply, do it too soon and it will bite you in the buttocks.


📇 Additional Metadata

  • 🗂 Type:: note
  • 🏷️ Tags::
  • 📡 Status:: #status/🌱