Don't repeat yourself: DRY Principle in software engineering

Don't repeat yourself: DRY Principle in software engineering

Be lazy and write code only once not multiple times.

Β·

5 min read

As developers, our main goal is to build our software more reliable, and to do so we need to make our developments easier to understand and maintain. Maintenance is the most important aspect of the software lifecycle. Maintenance doesn't take only place only after the application shipping.

We always find ourselves in being in maintenance mode from day 1 of development. Every day requirements gets changed, and we need to change/update our code to match the requirements. While performing maintenance if you haven't followed best practices, you will end up being in a maintenance nightmare. that's the situation, where DRY will help you.

The DRY principle is stated as in The Pragmatic Programmer

"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system".

Most of the people took DRY as a reference to code only, that you should not duplicate the source code twice. but actually, It refers to knowledge. DRY all is about the duplication of knowledge in the software. A piece of knowledge that can be available at two different places, maybe in two totally different ways.

You shouldn't be changing your source code at multiple places for a single requirement. If you do so, then your code isn't DRY.

As I already mentioned, DRY is not about the code. It can be found in the documentation, data structures also. Let's understand all these concepts by code examples.

πŸ™…β€β™‚οΈ Duplication in code

fun printInvestmentDetails(account: Account) {

        if (account.todayProfit < 0) {
            print("Today's profit : - Rs${"%.2f".format(-account.todayProfit)}/- \n")
        } else {
            print("Today's profit : + Rs${"%.2f".format(account.todayProfit)}/- \n")
        }

        print("------\n")

        if (account.totalOverallProfit < 0) {
            print("Total profit : - Rs${"%.2f".format(-account.totalOverallProfit)}/- \n")
        } else {
            print("Total profit : + Rs${"%.2f".format(account.totalOverallProfit)}/- \n")
        }
    }

For now, just ignore all the complications. Here, as a new programmer, we have done duplications of negative and positive numbers, float formatting. We can fix this by adding two functions.

    fun getFormattedAmountWithCurrency(amount: Float): String {
        val result = getFormattedAmount(amount)
        return if (result < 0)
            "- Rs${result.absoluteValue}/- "
        else
            "+ Rs$result/- "
    }

    fun getFormattedAmount(amount: Float) : Float {
        return  "%.2f".format(amount).toFloat()
    }

    fun printInvestmentDetails(account: Account) {
        print("Today's profit : ${getFormattedAmountWithCurrency(account.todayProfit)}\n")
        print("------\n")
        print("Total profit : ${getFormattedAmountWithCurrency(account.totalOverallProfit)}\n")
    }

Are we missing anything? what if in the future the requirement comes to add more spaces between the labels and lines. We need to change 3 lines to achieve that. let's remove the duplication and optimize it.

    fun getFormattedAmountWithCurrency(amount: Float): String {
        val result = getFormattedAmount(amount)
        return if (result < 0)
            "- Rs${result.absoluteValue}/- "
        else
            "+ Rs$result/- "
    }

    fun getFormattedAmount(amount: Float) : Float {
        return  "%.2f".format(amount).toFloat()
    }
    fun printLine(label: String, value: String) {
        print("$label : $value\n")
    }

    fun reportLineWithCurrency(label: String, amount: Float) {
        printLine(label, getFormattedAmountWithCurrency(amount))
    }

    fun printInvestmentDetails(account: Account) {
        reportLineWithCurrency("Today's profit",account.todayProfit)
        printLine("","------")
        reportLineWithCurrency("Total profit",account.totalOverallProfit)
    }

I think we had pretty much broken down our source code into small pieces of knowledge. This is now a DRY code.

πŸ€·β€β™‚οΈ Duplication of code that doesn't violate the DRY principle.

As "The Pragmatic Programmer" said

Not all code duplication is knowledge duplication.

Let's understand this by an example.

As per your stock broker application, you are adding and validating the user's new unit's allocation and total allocation. As per requirements, you should allow users to only buy if their purchase has a minimum of 5 units/stocks and your total investment should not exceed 100 units/stocks.

    fun validateMutualFundInvestment(newUnits: Int, previouslyBoughtUnits: Int): Boolean {
        return newUnits >= 5 && previouslyBoughtUnits + newUnits <= 100
    }

    fun validateStockInvestment(newStocks: Int, previouslyBoughtStocks: Int): Boolean {
        return newStocks >= 5 && previouslyBoughtStocks + newStocks <= 100
    }

If you see we are duplicating the code here. Most of the people will say, it's a DRY violation. But it's not true. The above code is the same, but the knowledge they are representing is different. If we convert those 2 functions into one function and implement, if the requirement comes to change the minimum stock quantity needs to be 3 in the future, then you need to again do the separation of functions. That's why this code falls down under the DRY code.

πŸ€¦β€β™‚οΈ Duplication in documentation

As we have heard most of the time, that we should comment on all your functions. It will be helpful to update information in the future for other developers. but there is a saying that tells

Good code documents itself.

Yes, you read it right. you should not add comments on code if it's self-explanatory. Comments are needed where you have complex logic. Comments should only be added for WHY not HOW. Let's understand this by an example.

     /**
     * Calculate the brokerage fee for every day
     * Each order costs 5 rupees.
     * If the total orders in a day are more than 15
     *  decrease the fee by 50%
     */
    fun fee(p: Purchase): Float {
        var f = 0.0f

        if (p.totalOrdersInADay > 0) {
            f += 5 * p.totalOrdersInADay
        }

        if (p.totalOrdersInADay > 15) {
            f /= 2
        }

        return f

    }

In this code, if we need to update each order's cost then we have to change the values at two places. One in code and another in the comments. Here, function and variables are also having bad namings. Let's make this code DRY.

fun calculateDailyBrokerageFee(purchase: Purchase): Float {
        var brokerageFee = 5 * purchase.totalOrdersInADay

        if (purchase.totalOrdersInADay > 15)
            brokerageFee /= 2

        return brokerageFee
    }

This function, its variables, and the code explain very well that what it is doing. This is DRY code.

πŸ“ Conclusion

The DRY principle helps to write more reliable, maintainable, less complex code. Be lazy and write code only once not twice.

As you already reached the end of the article, I hope you liked and got something in your knowledge bag. If you find this article helpful then share it with everyone. Maybe it’ll help someone who needs it πŸ˜ƒ.

Let's connect on Twitter, Youtube, Linkedin, Github, Instagram

Thank you! πŸ˜ƒ

Special thanks to "The Pragmatic Programmer" which was a major part of the learning source for writing this article.