Back to basics #1 – DRY, KISS and YAGNI

I do like a good acronym. When I was little my parents would keep my sister and me entertained on long car journeys by challenging us to make up phrases based on the letters in car registration plates, for example “DRY” could be anything from “Deidre’s Really Young” to “Don’t Rile Yoda”. But that’s not what I’m thinking about today. These three acronyms help us remember some of the basic principles of clean code. They speak for themselves really but I thought I’d include them here as it’s always good to keep them at the back of your mind.


DRY – Don’t Repeat Yourself

Sounds simple, right?

Well, yes and no. It’s easier said than done when you have a large and complex codebase. You really need to bear this in mind when writing code – will you be writing code that already exists? Is there a variable that always has the same value but is used in multiple places? If so, you should think about extracting it into its own method, or even a separate class if it is used in multiple classes.

But why should we keep code DRY? Well, it means that our code will be:

  • Maintainable. If we have the same code used in multiple places, every time we want to change it we will need to remember where it is used and go in and make that change, whereas if we only have it in one place we only need to make that change once.
  • Readable. Extracting logic into another class/method or creating a static variable gives us the opportunity to give that class/method/variable a descriptive name. By doing this we can make it obvious to anyone reading the code exactly what is happening (see the first example below – instead of writing out the logic to validate a string we call a method called “ValidateString” – it is immediately obvious what this piece of code is doing).
  • Efficient. Keeping code DRY and having reusable classes/components means that developers can spend less time writing the same code over and over again.
  • Bug-free. Hahahahahahahaha no, if only! But ensuring that logic is not repeated in multiple places means that it is less likely that we will introduce bugs if the logic is modified for any reason.

So to give a quick example, here we have a UserRegistrationService. We want to be able to validate the parameters to ensure that they meet the requirements: for this example they cannot be null, cannot be an empty string and cannot contain whitespace.

public class UserRegistrationService
{
    public void CreateUserAccount(string userName, string firstName, string lastName)
    {
        if (userName.Contains(" ") || String.IsNullOrEmpty(userName))
        {
            // Handle the username error.
        }

        if (firstName.Contains(" ") || String.IsNullOrEmpty(firstName))
        {
            // Handle the firstName error.
        }

        if (lastName.Contains(" ") || String.IsNullOrEmpty(lastName))
        {
            // Handle the lastName error.
        }

        // Logic to create the account goes here...
    }
}

So far so good, our code works and the parameters are being validated. But what if the validation logic changes? The code will now need to change in at least three places. To handle this we can extract the logic to its own private method within the class.

public class UserRegistrationService
{
    public void CreateUserAccount(string userName, string firstName, string lastName)
    {
        if (!ValidateString(userName))
        {
            // Handle the username error.
        }

        if (!ValidateString(firstName))
        {
            // Handle the firstName error.
        }

        if (!ValidateString(lastName))
        {
            // Handle the lastName error.
        }

        // Logic to create the account goes here...
    }

    private bool ValidateString(string input)
    {
        if (input.Contains(" ") || String.IsNullOrEmpty(input))
        {
            return false;
        }
        return true;
    }
}

By extracting the validation logic to its own method the code is now easier to read as well as much more maintainable.

Making this a little more complex – what if the logic is to be used in two different classes? Our restaurant needs to be able to calculate tax for a number of reasons. Here we have a BillService and a WageService – both of these need to calculate tax (NB – we’re making the assumption here that tax is the same percentage in all cases).

public class BillService
{
    public double CalculateBill(List<double> prices)
    {
        var totalPrice = prices.Sum();
        var tax = totalPrice * 0.2;
        return totalPrice + tax;
    }
}

public class WageService
{
    public double CalculateWages(double hourlyRate, int hoursWorked)
    {
        var totalWage = hourlyRate * hoursWorked;
        var tax = totalWage * 0.2;
        return totalWage - tax;
    }
}

We can see that the tax calculation is repeated in both classes. But what if the way tax is calculated changes, or the amount of tax changes? We will need to change the code in every single method that uses the calculation.

To remedy this we can extract the tax calculation to its own class. The BillService and WageService then call the method from the TaxService.

public class BillService
{
    TaxService _taxService = new TaxService();
    public double CalculateBill(List<double> prices)
    {
        var totalPrice = prices.Sum();
        var tax = _taxService.CalculateTax(totalPrice);
        return totalPrice + tax;
    }
}

public class WageService
{
    TaxService _taxService = new TaxService();
    public double CalculateWages(double hourlyRate, int hoursWorked)
    {
        var totalWage = hourlyRate * hoursWorked;
        var tax = _taxService.CalculateTax(totalWage);
        return totalWage - tax;
    }
}

public class TaxService
{
    public double CalculateTax(double amount)
    {
        return amount * 0.2;
    }
}

If the calculation or amount of tax needs to change in future, the changes only need to be made in the TaxService itself. Side note: in the real world we would use interfaces and dependency injection to decouple the classes but I’m keeping things simple here for clarity!


KISS – Keep It Simple, Stupid

Don’t over-complicate things! I do this all the time. What KISS boils down to is basically to strip things down to the bare necessities (the simple bare necessities… have an earworm, you’re welcome!).

When writing code, KISS applies to naming variables, methods and classes (and tests, and interfaces, and factories, and so on…). Keep them descriptive and easy to understand. In the second example above we have BillService, WageService and TaxService classes. The names tell us what they are responsible for. The method names (CalculateTax, CalculateWages) and the variable/parameter names (hourlyWage, hoursWorked) are self-explanatory and easy for anyone reading the code to understand. Find a naming convention that works for you and your team and stick to it.

KISS also applies to most other aspects of system design, for example a UI (user interface) should be self-explanatory, easy to use and with no unnecessary features that will confuse the user and detract from the main purpose of the UI.

In short, start with the absolute basics you need to create a minimum viable product or proof of concept. The bells and whistles of nice-to-have features can be added later, but only when they are needed.

Which brings us nicely on to…


YAGNI – You Ain’t Gonna Need It

If you follow the KISS principle, you’re probably not going to fall foul of this. The rule of thumb here is to only implement what you know is needed to meet your requirements. For example, if you are developing a system for a client and they ask you to provide an area to display reports it may seem a good idea to write the logic to allow the client to filter the data by date/month/year, just in case the client comes back to you at a later date and asks you to add that feature.

Don’t do it! “But why?” I hear you ask. Well, if you write that code and it isn’t used anywhere, it may work now – but if the rest of that reports page changes for whatever reason it’s very possible that your filtering logic will no longer be suitable. And if the client does ask for that feature to be added you’ll just have to rewrite it again.

Another reason is that if another developer works on your codebase they won’t know why it’s in there or what it’s supposed to be doing. This can cause maintainability issues and make your code very complex when it really doesn’t need to be.


Summary

DRY, KISS and YAGNI are simple yet effective principles that help us, as developers, create simple, reusable and maintainable code. They may seem like common sense but it is easy to fall foul of them. I find it particularly difficult to find descriptive names for classes and variables – they make sense to me but quite often colleagues will review my PRs and suggest more appropriate names. Invariably I wonder why I didn’t think of the names in the first place as they seem so obvious (never underestimate the importance of code reviews!). I’ve made a conscious effort to apply these principles to my coding and a colleague commented on the difference when reviewing a PR – so they do work! I’d love to hear people’s thoughts on them so please do leave a comment below.


Discover more from Diffident Coder

Subscribe to get the latest posts sent to your email.

Leave a comment