KISS, SOLID, YAGNI And Other Fun Acronyms

KISS, SOLID, YAGNI And Other Fun Acronyms

We’re constantly dealing with acronyms as software developers, but do we actually know what they all mean?

I’ve been working in the Software Development industry for over 17 years and I’ve seen my share of acronyms fly by, some of them making more sense than others, some of them forming funnier words than others. But let’s face it, as developers, we like our acronyms, we have some regular ones such as MVP and PoC that really sound like whoever created them made no real effort. And then we have others such as SOLID, DRY and KISS that well, lend themselves to some funny interactions.

However, there are so many around that I thought I’d do a round down of the most common (and some less common) ones out there, in case you need a refresher on their meaning.

DRY

Let’s start with an easy one, I’m sure you’ve seen this one around plenty of times, but do you know what it means?

This is a very common software development principle. It stands for Don’t Repeat Yourself. And it essentially means that when you’re writing code, you should consider modularizing functionality that you use more than once.

Don’t get me wrong, by modularization I don’t meant go around creating libraries for a function that you use twice, but rather, if you have the same logic (or similar enough that you can generalize it with little effort) in different places, then you should consider moving that logic inside a function and if you have a function that you’ve defined in several places, then move that into a common module.

And then again, if you happen to be copying a module around from place to place, consider turning that module into a common library.

In other words: Don’t Repeat Yourself, get it?

Personally, no matter the language you’re working on or the platform you’re developing for, there are very few cases when this principle would not be advised. Consider that as a developer, your job is to automate behavior (speaking at a macro scale here), so if you zoom into your particular code you should be doing the same. Instead of writing the same logic over and over again, consider generalizing it and moving it to a commonplace.

Tip: The DRY rule should not be limited to a single repository. To make your modules/components easily discoverable and available across repositories use Bit (Github).

Bit supports Node, TypeScript, React, Vue, Angular, and more.

Example: exploring reusable React components shared on Bit.dev

KISS

I always liked this one, both for the word that forms and for what it means: Keep It Simple Stupid (or if you don’t like to be called stupid, you can think of it as Keep It Stupid Simple, which I think works even better for the actual meaning of the acronym).

Sometimes, when solving a problem, you might incur in what is known as over-engineering or how I like to call it: using a bazooka to kill a mosquito. It’ll get the job done, but you can definitely do the same thing a lot simpler.

There are moments when complexity is required, don’t get me wrong. When simple code, or a simple architecture is definitely not useful, adding complexity to your logic is required. However, more often than not, you should ask yourself whether you’re KISS’ing it or not.

Or rather, are your logic paths simple enough? What is the cognitive load required to understand them? Simple code, or simple design in general, means you’re less prone to errors and those trying to understand it should have an easier time doing so. So in general, remember to KISS your code.

SOLID

Yet another common programming principle. This one though, stands for:

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

So if you think about it, it’s really 5 principles all packed into one. So let’s unpack it to make some sense out of it.

Single Responsibility Principle

Here we’re talking about how your functions should only do one thing and nothing else. If you’re familiar with *nix-based systems, such as Linux distributions, macOS or similar, you’ve probably experienced their terminal and used some of their commands (I’m talking about things like ls , cd, and the like). They follow this principle as well, they only perform one type of task, mind you, they do everything related to that task, but you won’t find a command line utility that, for example, helps you change directory and list their content.

The point here is that your functions should be simple enough to only have one responsibility, if you need more complex behavior, then combine their input and output in order to compose them.

And although I’m talking about functions here, this principle applies to everything in our industry. Think about the architecture of a platform, if instead of creating a mega-module responsible of doing everything you need, you create several microservices responsible of their own little tasks, this system becomes a lot easier to maintain and grow. The same goes for functions, simpler functions are easier to maintain, to read and understand and even to write.

Essentially, if you find yourself writing a function called getUserAndRelatedBooks where you actually have all the logic for both tasks, consider creating two functions: getUser and getUsersBooks where the second one takes the output of the first one, so you can then implement getUserAndRelatedBooks simply saying getUsersBooks(getUser) (i.e by composing them).

Open-Closed principle

This one is a reference to the fact that your modules or libraries (depending on how you’re exporting your code) need to be Open for extension (as in, extending their behavior) but Closed for modification (I mean, nobody wants to go around changing other peoples modules).

The moment you find others having to change your code in order for them to add or extend the existing behavior, you my friend, have successfully failed the Open-Closed principle.

The following code, for example, fails the principle, because if you needed to add extra cities to make it work for your use case, you would have to open the file and modify the knownCities array.

let knownCities = ["Madrid", "Barcelona", "New York"]
module.exports = {
  isAValidCity: function(cityName) {
    return knownCities.indexOf(cityName) != -1
  }
}

However, the following code fixes that and remains Open-Closed.

let knownCities = ["Madrid", "Barcelona", "New York"]
module.exports = {
  isAValidCity: function(cityName) {
    return knownCities.indexOf(cityName) != -1
  },
  addValidCity: function(cityName) {
    knownCities.push(cityName)
  }
}

Now the addValidCity method allows you to extend the behavior to make it work for you, without having to change its code.

Liskov Substitution Principle

Nice name uh? This is taking us as close as I like to be to programming theory. I don’t want to go too deep into it, but let’s first take a look at what this principle, also known as LSP, states:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

The first thing you can see is that we’re dealing with an OOP principle, one meant to help us correctly use inheritance and avoid using it when we can use other alternatives.

Think about the following example: in geometry, a square can be considered a specialization of a rectangle, since essentially it is a rectangle with matching width and height. But if you try to model that relationship with code, such as the code below:

class Rectangle {
    width
    height

    constructor(w, h) {
        this.width = w;
        this.height = h
    }

    setWith(w) {
        this.width = w
    }

    setHeight(h) {
        this.height = h
    }
}

class Square extends Rectangle{

}

The abstraction makes no sense, since using either the setWidth or setHeight methods they would not modify the other, essentially turning the square into a rectangle, which is not what you want. So the above code is breaking Liskov Substitution Principle.

In other words, this principle makes sure your code is using inheritance correctly, so even though it might sound like a terrible name, you should keep it in mind when you’re designing your classes.

Interface Segregation Principle

This one references the fact that you should not expose your users (i.e developers using your code) to unneeded methods. Remember, an interface is just a contract for your classes (listing the methods that need to be implemented). So if you’re creating interfaces (for example in TypeScript), make sure the required methods that need to be implemented are the ones your users will need, don’t force them to implement methods that are optional.

interface MyModule {
    close(): void
    open(connectionString: string): void
    handleIE8Compatibility(): void
}

Consider the above code, users of MyModule might want to implement the close and open methods, but if they don’t have to deal with IE8, then that last method is definitely not needed.

So this principle takes care of looking out for interface design. Try to avoid blotted interfaces and segregate the methods into different ones, allowing your users to decide how which ones to implement depending on their needs.

Dependency Inversion Principle

Finally, closing the SOLID concept, we have dependency inversion, also known as dependency injection.

In essence, this principle states that if you have dependencies in your code, you should allow for them to be injected into your logic. How do you ask? Simple! By allowing them to be passed in as parameters.

This is a very useful tool for many scenarios, one of them being unit testing, because if you’re testing code that you wrote and that depends on a third party library, you can inject a mocked version of that library to control its behavior. Let me show you an example:

const dbConn = //....

export function saveUser(user) {
    return dbConn.save(user)
}

Consider the above code, the connection to the database is declared and performed inside the same module, but you’re only exporting the saveUser function. So if you were to test it somehow, it would automatically try to connect to the database and accomplish its original goal: save the user data into the database.

However, if you were to allow for dependency injection and receive the database connection (which is an external dependency) as a second parameter, then you would be able to inject a mocked version in the future:

export function saveUser(user, dbConn) {
    return dbConn.save(user)
}

Now you can even switch dependencies for other purposes such as using a different module to connect to the database and your code is not affected.

Dependency injection is a wonderful tool, it creates a very extensible design and shows you care about extensibility. So consider it while defining your modules.

YAGNI

Otherwise known as “You ain’t gonna need it” is a principle taken from eXtreme Programming that argues that you should not build functionality in advance, or rather, until you actually need it.

The point being that within an agile development framework, you should only focus on your current work iteration and not in the ones to come. This is because even though it might sound tempting to work ahead of time and deliver more functionality that you were expecting, the changing nature of the agile workflow makes the future unstable.

What do I mean by unstable? I mean that given the short-iterations that characterize this methodology, you can receive early feedback, potentially completely changing the future of your project and rendering that feature you delivered without needing, useless.

Of course, the same train of thought does not apply for waterfall environments, where you plan ahead of time and try to follow that same plan without deviations. In such environments, YAGNI wouldn’t apply.

BDUF

Talking about waterfall, this is a principle that is perfect for that environment. Meaning “Big Design Up Front” it states that you should spend more time fully designing the application before you even write the first line of code.

Clearly, this says waterfall all over the place, where you can spend the required amount of time completely defining everything, with the hopes that once you start developing, you won’t spend too much time finding and fixing design flaws.

Of course, like with many of the other principles described here, it is not without its detractors. Especially agile developers tend to argue against this one, considering that the ever changing nature of agile life cycles make this principle useless. Unless of course, you enjoy spending time designing something that you know will change completely by the end of the project.

SOC

One of my favorite principles listed here, and definitely one I tend to have always present when designing either the architecture of a platform or simply the internal architecture of my project: Separation Of Concerns

Not to be confused with the previously covered Single Responsibility Principle, this one is meant to help you group functions or modules into a service. The point being if you’re designing a system that deals with several concepts (which normally most systems do), you want to group your functions into modules depending on what they have to deal with.

Take the following example: consider a blogging platform, a simple one, where your users can publish their blog posts. You could have a single system taking care of everything (user management, blog posts, analytics, and so on). But if you want to follow the SOC principle, you could end up with something more in the lines of:

This is of course a very crude representation of the architecture, but the point being you can separate different responsibilities into different modules, this in turn allows for benefits such as:

  • Scaling individual functionalities becomes easier. You can now easily consider scaling your user management module, because it is getting too much traffic, while leaving the rest of the platform untouched.
  • Making changes is easier now that your code is not tightly coupled. You can make considerable modifications to how you manage blog posts without affecting any other section of the platform.
  • Your platform is now more stable. If one of these modules crashes, then the system can potentially still function, with less features, of course, but the potential is there nevertheless.

SOC can also apply to API design, library architecture and more. It’s simply about having control over how you group functionalities in a way that makes sense to the users of those functionalities.

MVP

Leaving aside programming principles, there are other acronyms we tend to use in our industry that are also important to understand.

In this case, MVP stands for Minimum Viable Product and it represents the minimum amount of functionality your product needs to have in order to understand how viable it is in reality.

This is a great technique used to understand if finishing and reaching 100% of your desired product is not going to be a waste of your time. The output of your MVP phase, usually entails a product that while is not necessarily production ready, it can be used by a focused audience which in turn, can provide the required feedback to understand its viability.

POC

Unlike the MVP, which requires a considerable amount of planning and effort to develop, the Proof Of Concept is usually a smaller version of it. It normally comes before the MVP and it is only meant as a practical proof that the core functionality of what you’re trying to build is possible.

Now, this also means that PoCs tend to be throw-away pieces of code. You don’t build them to last, you build them to make a point. Mind you, there might be cases where evolving a successful POC is possible, but there is also the possibility that you’ll find yourself building several POCs in order to proof one point, so planning all of them to be evolved into an actual product might not be the best idea.

Personally I really like the concept of POCs but I like to consider them throw-away code. Think about them as Iron Man’s Mark I armor, it was a great way to show he could use the ARK reactor and build an armor around it, but the Mark II was already several orders of magnitude better.

Conclusion

All these acronyms are important, and some of them might be words you find yourself reading over and over again on tutorials or hearing your colleagues saying them. Now you know what they mean and how they apply to our context.

Have I left any of your favorite ones out? Leave a comment down below and share the acronym so we can all learn.

Did you find this article valuable?

Support The Rambling of an Old Developer by becoming a sponsor. Any amount is appreciated!