The Valuable Dev

A Detailed Explanation of The KISS Principle in Software

Two Characters are Kissing

When you search about the KISS principle on The Internet, you stumble across a lot of websites which will define it in a couple of lines. Therefore, you could think that this principle is easy to understand, easy to apply, or even not really important.

This is wrong, dear reader. Simplicity should be on the top 5 of your main concerns when building an application. It’s essential, but hard to achieve.

This article will first define what simplicity and complexity mean. Then, I will explain why simplicity is really important, to finally go over the main practices you can apply to make your codebase simpler.

Since I’m mainly working with Object Oriented Programming (OOP) languages, this article will stay focused around this paradigm.

There are a lot of things which are directly related to simplicity. However, I don’t want to write a book, but an article. That’s why I won’t necessarily go into many details, but more giving you a good overview of simplicity in software development.

If you want me to elaborate on any subjects I will write about here, please don’t hesitate to let me a comment! I’m always happy to help and discuss.

Simple… or easy?

There are important difference between the concepts of easy and simple we need to understand first.

Let’s begin with plain definitions. What the dictionary has to say about simple?

Plain, basic, or uncomplicated in form, nature, or design; without much decoration or ornamentation.

Composed of a single element; not compound.

What does it teach us? If your system has only a few parts, you have a simple system.

In order to really nail that, we can try to define simple by defining its contrary, complex:

Consisting of many different and connected parts.

Oho! That’s interesting. Complexity put an upper bound to our definition: a simple system is not a system with necessarily only one element, operation or instance. It’s a system without too many different elements, connected to each other.

If you’re still not convinced, that’s great: it’s really important to have a critical mind. Let’s look at a synonym of complex, complicated:

Consisting of many interconnecting parts or elements; intricate.

I can feel your adrenaline going through your entire body, your neurons doing the dance of understanding, your whole soul projecting the light of knowledge in the universe: Again the same idea!

Now, what’s the difference with easy? Well, let’s look at its definition:

Achieved without great effort; presenting few difficulties.

Let say you have to do a binary addition and you have no idea how to do it: it’s not easy for you. You lack some knowledge. However, a binary addition is not a system with too many parts. It’s not a system with connected parts. Therefore, it’s definitely simple.

To summarize, here are the two sins which bring complexity:

  1. Having a system with too many parts.
  2. Having a system with too many interconnected parts.

Keep in mind that the second point is worse than the first one in software development.

Now that we really know what simple means, you might wonder:

  • Why is it important for a software developer to put his nose in some boring dictionary?
  • Why simplicity should be considered as a God coming to save us all?

Why Do We Really Need the KISS Principle?

Mental Model

Developer Having a Mental Model

Why being a software developer can be hard? I can summarize that in two words: software changes.

If the program you’re building will never change, stop reading this article immediately! Actually, don’t read anything about software, it’s useless for you. Just do some procedural code in any language you want, don’t follow any principle. Nobody will put an eye on it, that’s perfect, the world is full of unicorn dancing on rainbows, in the river flow chocolate, and we all doing the circle of love and gratitude for eternity.

So you will change some piece of the software you will work on, guaranteed. To do so, you will have to understand how the different parts of your system work together in order to change whatever behavior you want to change. You will need to build a mental model of your application: what are the dependencies, the moving parts, how everything works together.

Depending on the application, your mental model will be more or less accurate. I’m sure you guessed it: it’s difficult and tiring to create an accurate mental model of a complex application.

You know what I mean. You remember the application of Dave, your colleague developer? Full of hidden dependencies and global behaviors. When you change something, you have no idea what will happen. Your energy level goes down, your anger goes up. At that point, you might curse his family on multiple generations.

The result of complex systems? Bugs, unhappy developers, risk of burnout and frustrated customers at the end of the chain. Everything a company doesn’t want to experience.

Business Complexity

The complexity of a system will decide as well if developers can scale it in a decent amount of time. This is really important, business wise. Nowadays, a company need to have a good amount of adaptability to fit markets which are constantly changing, and to outperform the concurrence.

Yet, as developers, we can’t always avoid complexity. It’s first and foremost the mirror of the business complexity we build a software for. The complexity of the requirements will match the complexity of our system!

For example, if you develop an e-commerce, you will have difficulty to avoid having products, orders, shipments, stock management, returns. A lot of moving parts in there!

That’s why, as a developer, you need to keep that in mind:

  1. If you can avoid complexity, avoid it, as much as you can.
  2. If you can’t, you need to make sure that you manage it in a proper way.

I can read your question in your mind, thanks to my supernatural powers: how the hell do we do that?

Simpler Requirements

To avoid business complexity, I would suggest attacking the complexity of the business requirements directly. How?

  1. If you don’t have any planning meeting where developers and business people (project managers and whatnot) discuss the requirements, please ask for it. Insist on that point, till you get it. Don’t follow blindly what the top management is throwing at you. They might not understand it, but you have a lot of valuable knowledge as a developer which can help to reduce the complexity of the requirements.

  2. During these meetings, when you see a potential feature which will bring a lot of complexity, ask questions which begin by “why”. Why do we need this feature? Why is it useful for our customer?

  3. If you really need this feature (which will be often the case), you might have a better idea to make it simpler. Instead of having a complex form with 20 fields to register for example, 3 might be enough. The good part is: a simple feature to implement will often be simple for the customer as well, on the user experience side. This is a good argument to sell your idea.

However, be aware that reducing complexity directly in the requirements is difficult. Why?

First, a lot of project managers (or stack holders) out there are married to their ideas. That’s perfectly normal: when you have an idea, you often think it’s THE idea which will change the world. More you will work on it, more, subconsciously, you will need to get something out of it.

If somebody completely discards your idea, or even try to change it slightly, you will have the impress he’s discarding or changing a part of yourself. This is the mistake: ideas are not part of ourselves. We need to distance ourselves from them.

Knowing how to argue properly can help here.

Second, sometimes it’s simply not possible to simplify a feature. The business needs it as it is, for good reasons. In that case, try to reduce the complexity has much as you can while implementing it. More on that later.

Keep in mind though: if you try to understand the business you’re working for, you will have more chances to find simpler solutions. Try to speak with domain experts (people having the knowledge of the business itself) as much as you can, always refresh your knowledge and avoid being overconfident in it.

Don’t get it wrong though: we don’t have every answer. The customer, at the end of the chain, should be the one who discard ideas and features. Listening to them is utterly important.

A Good Code is a Deleted Code

Each line of code in your system can be considered as a tiny part of it. Therefore, less code will make your system simpler.

Indeed, changing your code can have an impact on the surrounding code, and can have undesired effects, from altering slightly some functionalities to crash the whole application. When you think about it, lines of code are potential technical debts, sources of bugs, triggers for headaches, screams and tears.

Don’t have mercy: remove every useless piece of code you can find.

As my favorite French Hero, Antoine de Saint-Exupéry, stated:

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

‘nuff said.

Dead Code

Destroy All the Dead Code

Do you want a cemetery of old functionalities rotting in your shiny system? No, you don’t.

What’s dead code, you might ask? Everything which is not used in the application: variables initialized but never called, method never used, classes never instantiated.

Letting your dead code around will have the following consequences:

  • Confusing every developer working on the system. Questions will cripple their mind: what’s the purpose of it? Should I keep it? Is it useful? Doubts are never good to keep a sane mental model.
  • Your colleagues will possibly maintain it, spending time refactoring it for no benefit whatsoever.
  • Dead code hide the real implementation. Did you ask yourself already: “why, when I modify this method, nothing happens”? I did. It gets old, after a while.

Remove these trespassed pieces of code. All of them. No prisoner. No mercy. Send them to a better world for good.

The YAGNI Principle

A specific form of complexity (or dead code), which is often ferociously defended by developers, is the famous code which “will be useful one day, in the future, so don’t touch it, you little pig”. This is what could tell you Dave, your colleague developer.

The YAGNI principle try to answer Dave and everybody else having this kind of argumentation: “You Aren’t Gonna Need It”.

If you stumble across some code or functionalities created for no actual present purposes, delete it. If it will be indeed implemented one day, you can still come back to it with your version control system (git, anyone?).

I don’t count how many debates I had about this specific topic during my career. Here’s an example:

  • Me: Why the functionality to put a hat on users is implemented in the system? I don’t see that on the available on the frontend… but it’s a bit everywhere in the backend code.
  • Colleague: It’s not used for now. But we will use it one day, for sure.
  • Me: But… it was implemented in 2016, we are in 2019. If nobody needed it for three years, maybe we won’t need it for two years more. We could delete it.
  • Him: No. It’s too complicated. We will use it, for sure.
  • Me: … but people will maintain that. I spent myself some time to understand how to manage it, since I’m implementing something directly related which could break it.
  • Colleague: We will need that for sure.
  • Me: … and what about this functionality which qualify the user as “wizard”?
  • Colleague: Oh. It’s not used. But it will be used one day, for sure.
  • Me: …

Nobody knows the future. Functionalities can be canceled or staying in the backlog forever. Meanwhile, useless features will be maintained, sometimes with great efforts, for nothing.

Yet, I saw developers spending a crazy amount of time to be as flexible as possible for the hypothetical next features which never come.

Only add the complexity you need to answer the present business needs, no more.

We’re developers, not soothsayers.

A Wizard Developer Trying to Foresee the Future

Conditionals and Cyclomatic Complexity

Let’s dive in the detail of the implementation a bit.

A specific construct can bring complexity in your code: conditionals (if and switch statements, for example). This can be measured by the cyclomatic complexity metric: roughly, every path the execution of the program can take at runtime.

More of these paths you will have, more complex it will be, more painful your mental model will be for your poor brain.

All these conditionals can be seen as many little parts of a method, for example. Remember: many parts is the first Sin of Complexity™.

Having too many conditionals is not bad per se. It could be a validation process which compare some states to a lot of possible values.

Let’s take an example:

<?php

namespace Export;

class Export
{
    public function validate(Movie $movie): bool
    {
        if ($movie->title == null) {
            return false;
        }

        if ($movie->description == null) {
            return false;
        }

        if ($movie->duration == null) {
            return false;
        }

        if ($movie->createdAt == null) {
            return false;
        }

        if ($movie->updatedAt == null) {
            return false;
        }

        return true;
    }
}

If you need to quickly validate if a movie can be exported, this could be a valid implementation. We have quite a lot of if here, but it’s still understandable. That’s why the cyclomatic complexity is not always a good metric: it would warn you here that your code is too complex even if it’s, in fact, not that bad.

However, nested conditionals are a good sign that things begin to get out of hands. We don’t have only too many parts anymore, but interconnected parts. The Second Deadly Sin of Complexity. I’m sure you already saw it: if in if, in if, in if, till infinity.

Your indentation push your code out of the screen? That’s it! Nested thingy, probably conditionals.

In that case, it can be really difficult to know what path the execution will take at runtime.

In general, nested constructs is a sign of interconnected parts. Conditionals, loops, arrays… Doing it on a small scale is not a drama, but don’t let the complexity grow too much, if you can.

Another problem with conditionals can be the conditions themselves. There can be way too many nested parts in there, like this kind of thing:

<?php

if (!($a == true && ($b == false || !($z < $c || $c == true)) && !$d)) {
    print_r("Is this executed or is this not, that's the question");
}

To refactor this mess, I would advise to learn some basics about discrete mathematics. You can look at:

  • Truth tables
  • Propositional logic
  • Propositional equivalences

With that, you will gain super powers to deal with complex predicates.

Simple Architecture

Global States and Behaviors

You need to train yourself on the following: when you hear “global” in a conversation between two (or more) IT guys, your body needs to warm itself like a heater. Your mouth needs to open, your pupils need to be dilated, your brain fuzzy searching any possible argument you know with the word “global” in it.

Using global stuff is not a good idea 99% of the time.

What do I mean by “global”? There are two type of global in this world:

  • Global states
  • Global behavior

The first one is a variable or anything containing a state which is global to your application. A state which has a defined and relatively small scope is not considered global: a variable which only exists in a function, a method, a loop, a class.

Global states which are read only (constants) are not harmful. Their cousins, global mutable states, that is global states you can modify at any place, are evil.

When a state begin to be shared outside the boundary of classes, change it and you will have unknown consequences. How do you know what will be affected by this change? How do you know what will break or, even worst, format wrongly some data and create subtle bugs you won’t see for months?

Don’t use them. Really. Don’t.

The same goes with global behavior: change it, and everywhere this global stuff is used might break. Since it’s global, you still have no clue where it’s really used, and often you won’t remember even why it’s global at the first place. This include static methods, singletons, traits, and other constructs which break encapsulation.

I will never say it enough: pain will wait for those who take this forbidden path, for them and everybody who works on the cursed application.

Lasagna Architecture

Complex Architecture in Lego

Lasagna is a delicious dish (when it’s well-made), but a nightmare in a software. What’s a lasagna architecture? Three words: too many layers.

Let’s imagine that you have an application which simply takes some inputs from APIs, apply some business logic to transform the data and store it somewhere. Sounds reasonable.

Here’s what you can do. Each point is a layer:

  1. APIs which gather the inputs.
  2. Wrappers of the APIs.
  3. Server factories which create the different controller and inject the APIs wrapper.
  4. A pool of server factories in case you want to have multiple of them (?).
  5. Controllers where the API wrappers are called via interface constructs.
  6. A validation layer to validate the inputs.
  7. Another layer common to every controller (in an attempt to make everything as DRY as possible).
  8. A model layer which is as dumb as my feet (anemic models).
  9. A store layer called via interface constructs from the controllers.
  10. Another layer which only get the Ids of entities to fetch the whole entity itself.
  11. A layer which does the actual CRUD and implement the real business logic. In case of selects, this layer do some SQL request, get only the IDs and pass it to the layer 10 which will fetch the entity.

You didn’t get the whole picture? That’s normal.

Now, imagine yourself trying to modify some functionality. You might have to:

  • Go through most of the layers.
  • Try to find out if one layer will crash because of your change.
  • Try to remember how everything works in your painful brain.
  • Make your change.
  • Wonder what the layer was doing.
  • Verify everything again.
  • Fix three tests failing on 4 layers. Two days of work only for that.
  • Call your boyfriend / girlfriend, crying that you’re such a fraud.
  • Change your career plan.

You will need to have an 11 layers mental model without forgetting any parameter which could be harmful for the application. The problem is the following: our human brain is not very good to retain that much information at once. You know what it means: bugs, headaches again, time wasted, customer unhappy, manager unhappy, everybody unhappy.

This is a very good example to show that complexity can indeed be summarized by a system with too many parts.

Now, you might think I’m crazy. You might think that this kind of application is from my sick brain, that such things don’t exist. Well, I’m sorry to disappoint you, dear reader. This application is a real one, something I worked on.

You know what? This software is full of good intentions. The author didn’t want to create a complex system on purpose. He wanted to help everybody by creating something extremely flexible, elegant, DRY, the perfect software.

As my good old friend Saint Bernard of Clairvaux was saying the other day:

The road to hell is paved with good intentions.

The previous example will be useful to explain why unnecessary abstractions can make your system more complex.

Abstractions and Complexity

What’s an abstraction? It’s a way to deal with complexity by offering a layer of simplicity on top of it.

For example, a function is an abstraction: when you call a function in your code, you won’t necessarily know the implementation of the function. You won’t know about its complexity: how many parts it has, how many parts it depends on. You will just looks at the name of the function and use it or try to find something better for your needs.

The complexity here would be the procedure itself: what the function is doing, its implementation. The simple layer on top would be its name: if it describes well enough what the function is doing, you will only need this information to decide if it solves your problem.

This sounds great, right? It doesn’t matter how much complexity has a system anymore! Let’s develop a big ball of mud and put a layer of flowery and beautiful abstraction on top.

Well, not really.

As we saw, functions can be considered as an abstraction. Classes as well. Actually, even some primitive of your language, like arrays for example, can be seen as an abstraction of a more general concept (a set).

For abstractions to work, you need to put the logic of your abstraction belonging together, or your complexity will just jump back on your face like a mental health leech.

What do I mean? Well, consider this example.

<?php 

$this->createOrderAndMoveStockAndDeliverToUser();

What does this abstraction tell us?

  1. The implementation looks like it deals with a lot of complexity: it deals with orders, stocks and shipping. Three parts, all connected.
  2. The method abstract elements which don’t really belong together: you can create an order without moving stocks, and you can move stocks without creating an order.
  3. Even if the complexity is abstracted, you still have some complexity underneath.
  4. If you change the creation of an order, the stock movement and the delivery process can be affected. This is the cost of complexity: dependencies which impact each other. What will be affected? Very difficult to foreseen in a lot of cases.

This abstraction hide complexity but doesn’t offer simplicity either. It’s a wrong way to abstract. In that case, Divide and Conquer should be your motto:

<?php 

$this->createOrder();
$this->moveStocks();
$this->deliverToUser();

The method are now focused on one thing (SRP, anyone?).

Self containing logic is a good way to deal with complexity. It’s called encapsulation and is one of the most fundamental principle of the OOP paradigm.

Let see another one of these fundamental principle.

The Cost of Polymorphism

An ugly polymorph Lego character

Polymorphism is a core concept in OOP: it’s the ability of a class to take different forms. This is achieved, most of the time, with the interface construct.

You know, this stuff:

<?php

namespace App;

interface Checkout
{
    public function addProduct();
}

Interfaces are great, right? Well, it allows you to swap part of your system without affecting everything around.

However, polymorphism can bring a lot of complexity as well.

You remember the lasagna architecture I describe some paragraph above? A lot of layers have interfaces to be able to swap these layers. To change their implementation easily with something else.

You want to store your entities into a file instead of a database easily? Polymorphism via interfaces can allow you to do that.

That’s great, but everything, in the magic development world, has a price:

  • It’s difficult to know what implementation is really used at runtime. Too many layers with doubts about what’s used will drive you in dark places.
  • You will need to update these interface each time you modify or add something. They might become huge and unreadable. If it’s the case, ask yourself: do I really need to swap my implementation?

Let’s go back to my 11 layers application: it has 68 interfaces for 26759 lines of code. It means that there is an interface for every block of 393 lines of code.

Do you really think somebody will ever need to be able to swap so many parts of the system? The answer is: no.

In general, ask yourself: does your application need this flexibility? Do you really think you will need to swap this or that with something else?

If this “something else” doesn’t exist yet, don’t use the interface construct.

Don’t get me wrong: interfaces are very useful in some cases. If you need to swap an existing implementation with another one, at runtime, depending on some conditions, please go ahead, make yourself happy and create interface.

Now, you might have heard about the following:

programming to an interface instead of an implementation

This sound contradictory. Should you use interfaces all the time at the end?

Well, interface in that case doesn’t imply the interface construct you can find in a lot of language. It means that you need to decouple your dependencies using whatever you want. A simple facade can be enough.

Let’s talk a bit about this dependency question of old.

The Dependency Problem

An ugly polymorph Lego character

It’s safe to say that too many dependencies in a system make it horribly complex. After all, by their nature, dependencies couple parts of your system with one another: the definition itself of the Unforgiven Second Sin of the Complexity of the Death.

Is the Dependency Necessary?

When you create new dependencies between classes or modules, ask yourself:

  1. Is these dependencies are really necessary? Is it possible not to couple these classes?
  2. If they are necessary, how can I make the dependencies obvious? Where can I inject my dependencies for everybody to understand that yes, these classes are dependent?

Think about the business you’re building your application for: does it make sense to create dependencies in the context you’re in? Is it necessary to couple the Shipment logic with the Order logic, for example? Not really: an order can exist without a shipment, therefore an order should not have dependencies with the shipment logic.

These questions are really important and can make a huge difference overtime. It will save you some headaches and your coworker will stop throwing stones to you when you’re eating at the company’s cafeteria.

Managing the Dependency Complexity

Having every dependency grouped somewhere can really help to understand the system complexity at a glance. It will help you to have an accurate mental model of your dependencies.

A Dependency Injection Container can help a lot in that regard, but it’s not the only solution. When I code in Golang for example, I simply declare all my dependencies in the same package.

Managing your dependencies in a clear way will lower the chances to affect other part of your code when you make a change. It will serve as a guideline for everybody working on your project.

Therefore, please try not to inject dependencies via setters or other method in your classes. The best is to inject them in constructors: it’s then easier to find them.

If they are injected and managed in random methods, how can you find them back without struggle? This is a good way to unleash the Demon of Complexity™ on your poor application, on your colleagues and on your company.

Let’s take a very simple example:

<?php

class ProductCollection
{
}

// This is a clear and unambiguous implementation.
// 1. The dependency productCollection is obvious and directly visible in the constructor, at the beginning of the class.
// 2. It indicates that the class Order needs a productCollection to be  instantiated.

class Order
{
    /** @var ProductCollection */
    private $productCollection;

    public function construct(ProductCollection $productCollection)
    {
        $this->productCollection = $productCollection;
    }

    // More methods
}

// This is not obvious enough.
// 1. The setter is lost in a bunch of other method.
// 2. It is misleading: when does the productCollection should be injected? Is it important? In what case?

class ShoppingCart
{
    // Bunch of other methods

    public function setProductCollection(ProductCollection $productCollection)
    {
        $this->productCollection = $productCollection;
    }

    // More methods
}

Don’t Try to Outsmart Everyone

As an appetizer, let me quote M. A. Jackson who wrote Principles of Program Design:

Programmers… often take refuge in an understandable, but disastrous, inclination towards complexity and ingenuity in their work.

It’s a common pattern I see all the time.

Sometimes, developers try to show how smart they are. Maybe by insecurity or oversized ego, they will bring a dozen of design patterns and invent clever ways to do things only the Best of the Best, the True Developer, The Elite of the Binary, The Code Champion of the Galaxy will know.

I’m not innocent, or better than anybody else on that regard. I went through this phase in my career as well.

As Bjarne Stroustrup was saying:

Make Simple Tasks Simple!

You might recognize that you have this problem in your company when you will see complicated CQRM architecture, Event Driven Systems and factories, decorators, pools all over the place for a CRUD application. In short, high complexity for simple functionalities.

To me, this is an attempt to show the world (and trying to convince yourself) that yes, you’re not a fraud, but an amazing developer.

You don’t need to do that. The ultimate reward of a developer should be a company which run smoothly and satisfy their customers, not a pile of complexity using every design pattern one can possibly know.

I’m a developer. I know how funny it is to play with new technologies and other toys. Companies should definitely allow us to try new things and discard others, in order to bring the best technologies and techniques for the tasks at hand.

However, if you see that a technology (or a technique) doesn’t bring anything except your own egoistic pleasure of playing with it, please push away this useless complexity.

Let’s continue the quote feast with Rich Hickey:

Patterns mean “I have run out of language.”

He’s right. If you have no other solution, if you need more flexibility because the requirements are complex, than yes, by all mean, introduce what you need.

Not so fast, though. Before doing so, try to find out if the programming language you’re using can offer already a descent solution without going for your shiny Design Pattern book.

It means as well that you should only introduce this kind of complexity when refactoring or changing an already existing piece of code. Please, I beg you, try not introducing complexity up front.

If your management wants, from the get go, complex features which can only be translated by a complex implementation using complex design pattern, you should explain to them the benefit of a step by step, iterative approach on building functionalities, and have your customer test a simple version of them.

You don’t want to spend efforts on complex feature nobody will use. Especially since these features will aunt you for everything you will need to add or change afterward. Nobody wants that.

KISS means: Keep It Simple Stupid. No, this doesn’t mean that you’re stupid. You’re very smart and beautiful.

It means that your code should be stupidly simple. It should be so simple, everybody should understand what the system does and grasp as quickly as possible a good mental model of it. It should be so simple that when you will quit your company, a junior developer should be able to continue your masterpiece.

That’s what the KISS principle means.

To come back to my lasagna architecture with 11 layers, this design was created up front, with the only purpose of hyper flexibility. It wasn’t a rewriting or a refactoring: it was created as it is now.

I didn’t brush the whole complexity of this application. The author tried to follow as well some CQRS paradigms with an anemic domain model approach. The result? Only implementing the possibility for a user to copy another user took me two full days.

As a rule of thumb:

  • Code as close as possible to your immediate needs.
  • Stay close to the business your company is doing in your implementations.
  • Make obvious what piece of code implement what functionality.
  • Extreme flexibility and future divination can have a high cost, in technical debts and therefore in money and time.

Speaking about money and time is always good when you speak to the management. If you can give some numbers, it’s even better.

Don’t try to be the smartest guy in the room. Try to be as stupidly simple as you can.

Down to Top Development

I would like to describe a development method which push me to stay simple and as close to the requirements as I can, without going into crazy complex assumptions: the Down to Top development.

  1. Begin by implementing the lowest layer of your application.
  2. Code only what you need for your feature to work.
  3. Test it.
  4. Refactor your code, if necessary.
  5. Move one layer up.
  6. Code only what you need for your feature to work.
  7. Test your new code if necessary (that is, if this layer has any logic, otherwise you will only test the code on the above layer twice).
  8. Go back to 4.

Tests (unit or integration test) can force you to see if your code is properly decoupled. In fact, it’s very difficult to test when every classe are interconnected: you will have to mock all of them, which is a very good and painful indicator that you should simplify things.

I won’t go into more details about testing, the subject by itself deserve its own article. Just remember that automated tests can help you simplify (and refactor) your codebase.

The KISS Principe in a Nutshell

As you can see, simplicity is a big subject. It touches many areas in development, from the architecture to the implementation.

This might be a problem: what does it mean, at the end, “to keep it simple”? In fact, everything and anything, I’m affraid. Should we stop speaking about simplicity (or the KISS principle) and be more pricise instead, depending on the situation?

To summarize what we learned together:

  • A simple system doesn’t have too many parts and, more importantly, doesn’t have too many dependent, interconnected parts.
  • A clear mental model of an application is a good sign of its simplicity.
  • If you can, try to reduce the complexity of the features your managers try to build, by proposing an equivalent solution. You will need good, business related arguments.
  • Delete every bit of code which are useless for your present needs. Nobody knows what will happen in the future.
  • Avoid global states and behaviors like you would avoid the black plague.
  • Don’t create too many layers in your application.
  • Abstractions are useful sometimes. Sometimes means: not all the time.
  • Polymorphism and use of the interface construct can be useful as well, but don’t overuse them.
  • Make clear where the dependencies of your application are managed and be careful not to couple classes if you can find a better solution.
  • Don’t try to show your knowledge when you’re coding. Keep in mind that anybody should be able to modify it easily. Even somebody with no experience.

It might look like a lot, but I think you have now a pretty good overview of the KISS principle.

I would end this article with a warning: “simple” is good, “incomplete” is not. Your first objective is to make your code working as expected. Don’t try to reduce scope of your functionalities by yourself, always speak beforehand with your project manager or whoever who’s in charge.

A simple, beautiful code which does useless things is still… useless!