Regardless of what we consider to be great code, it always requires one simple quality: the code must be maintainable. Proper indentation, neat variable names, 100% test coverage, and so on can only take you so far. Any code which is not maintainable and cannot adapt to changing requirements with relative ease is code just waiting to become obsolete. We may not need to write great code when we are trying to build a prototype, a proof of concept or a minimum viable product, but in all other cases we should always write code that is maintainable. This is something that should be considered a fundamental quality of software engineering and design.

Single Responsibility Principle: A Recipe for Great Code

In this article, I will discuss how the Single Responsibility Principle and some techniques that revolve around it can give your code this very quality. Writing great code is an art, but some principles can always help give your development work the direction it needs to head towards to produce robust and maintainable software.

Model Is Everything

Almost every book about some new MVC (MVP, MVVM, or other M**) framework is littered with examples of bad code. These examples try to show what the framework has to offer. But they also end up providing bad advice for beginners. Examples like “let’s say we have this ORM X for our models, templating engine Y for our views and we will have controllers to manage it all” achieve nothing other than humongous controllers.

Although in defense of these books, the examples are meant to demonstrate the ease at which you can get started with their framework. They are not meant to teach software design. But readers following these examples realize, only after years, how counterproductive it is to have monolithic chunks of code in their project.

Models are the heart of your app. If you have models separated from the rest of your application logic, maintenance will be much easier, regardless of how complicated your application becomes. Even for complicated applications, good model implementation can result in extremely expressive code. And to achieve that, start by making sure that your models do only what they are meant to do, and don’t concern themselves with what the app built around it does. Furthermore, it doesn’t concern itself with what the underlying data storage layer is: does your app rely on an SQL database, or does it store everything in text files?

As we continue this article, you will realize how great code is a lot about separation of concern.

Single Responsibility Principle

You probably have heard about SOLID principles: single responsibility, open-closed, liskov substitution, interface segregation and dependency inversion. The first letter, S, represents Single Responsibility Principle (SRP) and its importance cannot be overstated. I would even argue that it is a necessary and sufficient condition for good code. In fact, in any code that is badly written, you can always find a class that has more than one responsibility - form1.cs or index.php containing a few thousand lines of code is not something that rare to come by and all of us probably have seen or done it.

Let’s take a look at an example in C# (ASP.NET MVC and Entity framework). Even if you are not a C# developer, with some OOP experience you will be able to follow along easily.

public class OrderController
{
...

    	public ActionResult CreateForm()
    	{
        	/*
        	* View data preparations
        	*/

        	return View();
    	}

    	[HttpPost]
    	public ActionResult Create(OrderCreateRequest request)
    	{
        	if (!ModelState.IsValid)
        	{
            	/*
             	* View data preparations
            	*/

            	return View();
        	}

        	using (var context = new DataContext())
        	{
                   var order = new Order();
                    // Create order from request
                    context.Orders.Add(order);

                    // Reserve ordered goods
                    …(Huge logic here)...

                   context.SaveChanges();

                   //Send email with order details for customer
        	}

        	return RedirectToAction("Index");
    	}

... (many more methods like Create here)
}

This is a usual OrderController class, its Create method shown. In controllers like this, I often see cases where the Order class itself is used as a request parameter. But I prefer to use special request classes. Again, SRP!

Notice in the snippet of code above how the controller knows too much about “placing an order”, including but not limited to storing the Order object, sendings emails, etc. That is simply too many jobs for a single class. For every little change, the developer needs to change the entire controller’s code. And just in case another Controller also needs to create orders, more often than not, developers will resort to copy-pasting the code. Controllers should only control the overall process, and not actually house every bit of logic of the process.

But today is the day we stop writing these humongous controllers!

Let us first extract all business logic from the controller and move it to a OrderService class:

public class OrderService
{
    public void Create(OrderCreateRequest request)
    {
        // all actions for order creating here
    }
}

public class OrderController
{
    public OrderController()
    {
        this.service = new OrderService();
    }
    
    [HttpPost]
    public ActionResult Create(OrderCreateRequest request)
    {
        if (!ModelState.IsValid)
        {
            /*
             * View data preparations
            */

            return View();
        }

        this.service.Create(request);

        return RedirectToAction("Index");
   }

With this done, the controller now only does only what it is intended to do: control the process. It knows only about views, OrderService and OrderRequest classes - the least set of information required for it to do its job, which is managing requests and sending responses.

This way you will rarely change controller code. Other components such as views, request objects and services can still change as they are linked to business requirements, but not controllers.

This is what SRP is about, and there are many techniques for writing code that meets this principle. One example of this is dependency injection (something that is also useful for writing testable code).

Dependency Injection

It is hard to imagine a large project based on Single Responsibility Principle without Dependency Injection. Let us take a look at our OrderService class again:

public class OrderService
{
   public void Create(...)
   {
       // Creating the order(and let’s forget about reserving here, it’s not important for following examples)
       
       // Sending an email to client with order details
       var smtp = new SMTP();
       // Setting smtp.Host, UserName, Password and other parameters
       smtp.Send();
   }
}

This code works, but isn’t quite ideal. To understand how the create method OrderService class works, they are forced to understand the intricacies of SMTP. And, again, copy-paste is the only way out to replicate this use of SMTP wherever it is needed. But with a little refactoring, that can change:

public class OrderService
{
    private SmtpMailer mailer;
    public OrderService()
    {
        this.mailer = new SmtpMailer();
    }

    public void Create(...)
    {
        // Creating the order
        
        // Sending an email to client with order details
        this.mailer.Send(...);
    }
}

public class SmtpMailer
{
    public void Send(string to, string subject, string body)
    {
        // SMTP stuff will be only here
    }
}

Much better already! But, OrderService class still knows a lot about sending email. It needs exactly SmtpMailer class to send email. What if we want to change it in the future? What if we want to print the contents of the email being sent to a special log file instead of actually sending them in our development environment? What if we want to unit test our OrderService class? Let us continue with refactoring by creating an interface IMailer:

public interface IMailer
{
    void Send(string to, string subject, string body);
}

SmtpMailer will implement this interface. Also, our application will use an IoC-container and we can configure it so that IMailer is implemented by SmtpMailer class. OrderService can then be changed as follows:

public sealed class OrderService: IOrderService
{
    private IOrderRepository repository;
    private IMailer mailer;
    public OrderService(IOrderRepository repository, IMailer mailer)
    {
        this.repository = repository;
        this.mailer = mailer;
    }

    public void Create(...)
    {
        var order = new Order();
        // fill the Order entity using the full power of our Business Logic(discounts, promotions, etc.)
        this.repository.Save(order);

        this.mailer.Send(<orders user email>, <subject>, <body with order details>);
    }
}

Now we are getting somewhere! I took this chance to also make another change. The OrderService now relies on IOrderRepository interface to interact with the component that stores all our orders. It no longer cares about how that interface is implemented and what storage technology is powering it. Now OrderService class has only code that deals with order business logic.

This way, if a tester were to find something behaving incorrectly with sending emails, developer knows exactly where to look: SmtpMailer class. If something was wrong with discounts, developer, again, knows where to look: OrderService (or in case you have embraced SRP by heart, then it may be DiscountService) class code.

Event Driven Architecture

However, I still don’t like the OrderService.Create method:

    public void Create(...)
    {
        var order = new Order();
        ...
        this.repository.Save(order);

        this.mailer.Send(<orders user email>, <subject>, <body with order details>);
    }

Sending an email isn’t quite a part of the main order creation flow. Even if the app fails to send the email, the order is still created correctly. Also, imagine a situation where you have to add a new option in the user settings area that allows them to opt-out from receiving an email after placing an order successfully. To incorporate this into our OrderService class, we will need to introduce a dependency, IUserParametersService. Add localization into the mix, and you have yet another dependency, ITranslator (to produce correct email messages in the user’s language of choice). Several of these actions are unnecessary, especially the idea of adding these many dependencies and ending up with a constructor that does not fit on the screen. I found a great example of this in Magento’s codebase (a popular ecommerce CMS written in PHP) in a class that has 32 dependencies!

Sometimes it is just hard to figure out how to separate this logic, and Magento’s class is probably a victim of one of those cases. That is why I like the event-driven way:

namespace <base namespace>.Events
{
[Serializable]
public class OrderCreated
{
    private readonly Order order;

    public OrderCreated(Order order)
    {
        this.order = order;
    }

    public Order GetOrder()
    {
        return this.order;
    }
}
}

Whenever an order is created, instead of sending an email directly from the OrderService class, special event class OrderCreated is created and an event is generated. Somewhere in the application event handlers will be configured. One of them will send an email to the client.

namespace <base namespace>.EventHandlers
{
public class OrderCreatedEmailSender : IEventHandler<OrderCreated>
{
    public OrderCreatedEmailSender(IMailer, IUserParametersService, ITranslator)
    {
        // this class depend on all stuff which it need to send an email.
    }

    public void Handle(OrderCreated event)
    {
        this.mailer.Send(...);
    }
}
}

The class OrderCreated is marked as Serializable on purpose. We can handle this event immediately, or store it serialized in a queue (Redis, ActiveMQ or something else) and process it in a process/thread separate from the one handling web requests. In this article the author explains in detail what event-driven architecture is (please pay no attention to the business logic within the OrderController).

Some may argue that it is now difficult to understand what is going on when you create the order. But that cannot be any further from the truth. If you feel that way, simply take advantage of your IDE’s functionality. By finding all the usages of OrderCreated class in the IDE, we can see all the actions associated with the event.

But when should I use Dependency Injection and when should I use an Event-driven approach? It is not always easy to answer this question, but one simple rule that may help you is to use Dependency Injection for all your main activities within the application, and Event-driven approach for all secondary actions. For example, use Dependecy Injection with things like creating an order within the OrderService class with IOrderRepository, and delegate sending of email, something that is not a crucial part of the main order creation flow, to some event handler.

Conclusion

We started off with a very heavy controller, just one class, and ended up with an elaborate collection of classes. The advantages of these changes are quite apparent from the examples. However, there are still many ways to improve these examples. For example, OrderService.Create method can be moved to a class of its own: OrderCreator. Since order creation is an independent unit of business logic following Single Responsibility Principle, it is only natural for it to have its own class with its own set of dependencies. Likewise, order removal and order cancellation can each be implemented in their own classes.

When I wrote highly coupled code, something similar to the very first example in this article, any small change to requirement could easily lead to many changes in other parts of code. SRP helps developers write code that are decoupled, where each class has its own job. If specifications of this job changes, developer makes changes to that specific class only. The change is less likely to break the entire application as other classes should still be doing their job as before, unless of course they were broken in the first place.

Developing code upfront using these techniques and following Single Responsibility Principle can seem like a daunting task, but the efforts will certainly pay off as the project grows and the development continues.

About the author

Adel Fayzrakhmanov, Russia
member since August 17, 2015
Adel is an experienced software developer, always focused on code quality and good architecture. He has over 10 years of experience developing web projects on PHP and .NET technology stacks, and is a dedicated and efficient individual. [click to continue...]
Hiring? Meet the Top 10 Freelance Software Developers for Hire in September 2016

Comments

Khaled Monsoor
good writeup, Adel. Plz keep it up.
Kalpesh Patel
Nice write up. Like it.
Peter Vukovic
Great topic Adel. Here are some things that got in the way while I was reading it: 1) Too many details in the examples. Switching between the point you are trying to make and the C# examples is mentally very taxing, especially because I'm not a C# person. What would make it better: pseudo code, and an example that isn't about customers and orders. 2) Too technical writing style. The article is useful, but a dry read. What would make it better: comparing the concepts you are trying to explain with things we can find in nature or real life, so people can think about them in a more abstract way. Keep posting!
Adel Fayzrakhmanov
Thanks for advices, Peter. It was my first article. 1) I thought that real-life examples, especially huge-controller-style code, will be a good code examples for idea, which I wanted to share. 2) Will try to do it in next articles :)
Alice W
Nice article. Articles on general design principles aren't as common as very architecture/platform/language-specific posts, and I appreciated your post. I thought your use of examples was at a good level without getting too bogged into specific technology choices.
Haifeng Zhang
nice writeup!
SharpTag
This reader thinks there is a shortage of long useful in-depth articles and the technical writing style was excellent. Good work and ignore the Peter guy if you can. Great article well presented even if I don't agree with it! Now on the other hand, its not your fault since SOLID is part of the Zombie-like adoption of Agile techniques that people just don't question but Single Resposibility is the exact opposite of "Good" Object Design. Of course Object Oriented programming hardly exists any longer as it was always going to be in opposition with extensive tooling support and in the long run tooling is too seductive. But FWIW, for those who think that the original concepts of Object Design still have value, then an object should have whatever responsibility it takes to provide encapsulated Object Integrity in a manner that the object is both easily recognizable and models object behavior in the real world. The world is full of real objects that have multiple duties and resposibilities yet have an obviously elegant simplicity of design. Single resposibility is both lazy design and a failure of imagination.
Adel Fayzrakhmanov
Yes. Real life objects often have many responsibilities. But when we modelling them, it's much easier for us if they have only one. So, speaking in DDD language we use Bounded Contexts to divide responsibilities. Describing complex things with simple models helps us to write great code ;-)
SharpTag
Well, I have to disagree with that idea. Describing things with simple models makes for horrible convoluted hard to understand code when actually used in large real world systems outside of text book examples. Why is that? Because the model is essentially converted into something that is convenient for the tooling and not humans. Once you have "tooling converted code" you can then add unit testing and be confident with CI you haven't introduced any regressions on updates. Then you can divide into small sprints based on continuously degrading user stories until you are reliably generating "solid" code on time and everyone in the chain is happy and you can then use some spare time to build a team lamp that glows green everytime the unit test suite passes... The one thing you don't have is "great code" - read though some of John Carmac's code, perhaps the greatest programmer who has ever lived. That is Great Code. What you get with "solid" is "machine" testable code. For testing failure cases you already know about. All of this builds to a point where "Design" becomes a heavily mis-used term. You no longer have a Object model that fits a human conception of something humanly creative. There is no longer any power to visualize any truly insightful improvements to a complex system. The system becomes an ossified dinosaur in just a more modern trendy hipster manner than old-fashioned spaghetti code, just as ugly, just as complex, and it still hits a brick wall. There is still no "Silver Bullet" Pure Object Design appeared to hold some promise, but it was never considered practical in real life usage because it needed talented people and it was hostile to tooling. Tooling needs "properties" and once you see "Get" or "Set" or some other property change, the core of Object Programming has died. Anyways that's all the space I deserve without proposing a solution and the solution is to actually add the "Science" back into "Computer SCience" and do Double Blind comparison studies of various programming methodology approaches instead of endless repetition of internet anecdotal experience. Actual studies I have been able to find, indicate little value to anything invented over the last 20 years other than fairly solid evidence for "Pair Programming" and one other interesting result. The adoption of any methodology (such as SOLID) or even the complete opposite of SOLID improves code. Presumably because the code is being examined and thought about in a more intropestive manner than it might otherwise have benn subjected to. Which means detailed articles such as yours are an important service to the community to encourage people to pay attention to the craft of programming. To use Socrates famous "An unexamined life is not worth living" we can propose that "Unexamined code is not worth coding"
Milan Rawal
great !!! nice one.
John
Thank you for your post, well done! I also found this video https://youtu.be/bZSdnfARpnY to be very helpful to learn the SRP with a javascript code example.
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering and design posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant technologies
About the author
Adel Fayzrakhmanov
PHP Developer
Adel is an experienced software developer, always focused on code quality and good architecture. He has over 10 years of experience developing web projects on PHP and .NET technology stacks, and is a dedicated and efficient individual.