Another principle of object-oriented design is called KISS, that stands for Keep it short and simple.

But there also some interpretations such as keep it simple, stupid!, keep it simple and stupid or keep it simple and straightforward.

Sounds too general and abstract, right? The rest of the object-oriented design principles are more specific. But how is this one related to object-oriented design? Let’s dig into. The main idea of the principle is striving for the simplicity. Simplicity is above all because the simple code is the most understandable. This principle can be useful mostly for novice programmers, who don’t know or don’t understand the basic principles of the design. KISS protects you against misuse of design principles and patterns. Since the principles and patterns are build to increase the comprehensibility of the code, their proper use can not lead to a decrease of it. However, if you misunderstand patterns or design principles, or when by following one principle you violate a dozen of others, KISS can point you to these mistakes.

But still very abstract. Everybody understands simple code differently. For one developer the dependency container automatic resolution is something very trivial, but another developer can treat it as something magical. These different representations about simplicity can lead to some misconceptions:

  • You should write the easiest code you can, without event thinking about design.
  • You should write code that requires as little knowledge as possible to be written. You should not use design patterns.

By simplicity, in this case, one should understand not a complex, devoid of artificiality, the most natural, not difficult, easily accessible for understanding code. And now you are thinking that you are already following this principle. Nothing new here for you. But maybe you are wrong. Often it is not easy to write really simple code. It requires from you a lot of knowledge of the system you are working on. Don’t you believe me? Ok, try to remember how much code have you seen that was easy to read and simple to understand? I bet that not a lot. It happens to everybody, and even you have written the code that doesn’t look too simple for you in six months.

Why are we doing it? Why do we create this complex code? There are different reasons. Sometimes you need to make a hotfix, that adds one more conditional to already confusing code. Sometimes a developer tries to implement some new patterns that he has recently learned. Sometimes somebody on Twitter sais that something is a good practice in if you are not following it, you are not a professional developer, and it doesn’t matter for you that this good practice overcomplicates your system. No matter what the reason was it has lead to a complex code, that is hard to understand and maintain, even if it is your own code.

Let’s consider an example, to show you what I’m talking about. We have a web-store application and now we are working on a catalog detail page. It looks like this:

<?php

final class CatalogController 
{
    public function detail($slug)
    {
        $product = Product::where('slug', $slug)->findOrFail();

        return view('detail', compact('product'));
    }
}

Very simple, two lines of code. We get a slug from the request, find a Product model by this slug, and then render a view. But then you start reading all of these tutorials and they tell you that you shouldn’t do that. You have directly called where method on your model in your controller and of course in the future bad things gonna happen. They tell you that you should use repositories in your controllers. And it is not about your desing, it is more about extracting, isolation and testing. But these are good practicies that you should follow. So, you go and create ProductsRepository and inject it into our controller.

<?php

final class ProductsRepository 
{
    public function getProductBySlug($slug)
    {
        return Product::where('slug', $slug)->findOrFail();
    }
}

final class CatalogController 
{
    
    private $products;

    public function __constructor(ProductsRepository $products)
    {
        $this->products = $products;
    }

    public function detail($slug)
    {
        $product = $this
            ->products
            ->getProductBySlug($slug);

        return view('detail', compact('product'));
    }
}

Ok, now you have two classes/files, your controller has dependency injection. Does this code looks better? What about design? This is not enough. Then we learn that we should decouple our controller from concrete implementation of the repository, because now they are tightly coupled and it is not a good practise. So, it looks like we need to extract an interface and typehint it in our controller, so we can swap up different repository implementations.

<?php

interface ProductsRepositoryInterface
{
    public function getProductBySlug();
}

final class ProductsRepository implements ProductsRepositoryInterface
{
    public function getProductBySlug($slug)
    {
        return Product::where('slug', $slug)->findOrFail();
    }
}

final class CatalogController 
{
    private $products;

    public function __constructor(ProductsRepositoryInterface $products)
    {
        $this->products = $products;
    }

    public function detail($slug)
    {
        $product = $this
            ->products
            ->getProductBySlug($slug);

        return view('detail', compact('product'));
    }
}

Now we have three classes and files and everything is decoupled and testeable. And does it mean that this code is better? Really? Is it worth it? Instead of two lines of code now we have three different classes! We aren’t really thinking about design here. Anything but not design.

Of course, in the future it will be very easy to change your database layer to something totaly different. But it most likely will never happen. At the end of the day you simply want to retrieve a product by its slug and don’t care about a dependency injection, swappable implementations and all of this stuff:

<?php

// ...
$product = Product::where('slug', $slug)->findOrFail();

But instead we have an interface, a repository class, a dependency injection in our controller. All this complexity in order to get a model instance by slug out of the database. This is not KISS.

So, don’t overthink and don’t overcomplicate things, if it is not required. Keep everything as simple as possible. As Wikipedia sais: most systems work best if they are kept simple rather than making them complex; therefore simplicity should be a key goal in design and unnecessary complexity should be avoided.