Design Principles For Better Software

Introduction

In this article, Design principles are described in a manner that is easy to understand, along with the reasoning and benefits of their usage. Some of the well-known design principles are discussed which are good to know to keep the software design up to the mark.

Content Covered

  • Design Principles
  • Why Design Principles
  • Good to know Design Principles
    • SOLID
      • Single Responsibility
      • Open-Closed
      • Liskov-Substitution
      • Interface Segregation
      • Dependency Inversion
    • DRY (Don’t Repeat Yourself)
    • KIS (Keep it simple)
    • Separation of Concerns
    • YAGNI (You Ain’t Gonna Need It)
    • TDA (Tell, Don’t Ask)
    • Once and Only Once
    • Principle of Least Surprise
    • Explicit Dependencies
    • Boy-Scout Rule

Design Principles

Set of rules, if followed properly helps in clean, understandable, changeable, extendable, testable, and maintainable software. Every developer should have knowledge of design principles to write clean code, which in turn helps in managing the software for longer period of time.

Why Design Principles

Design principles are necessary to be followed for achieving clean code which is loosely coupled and easy to understand as it is written simply. There can be few reasons that can lead to bad design such as

  • Time to market
  • Immediate Deadlines
  • Frequent Changes
  • Work Pressure
  • Not following Design principles and many more

All the reasons mentioned above can cause bad design which at that time might seems feasible but will ruin the software as its not just doing the thing but its how you do it. A poorly designed software with really difficult to extend or maintain, code base will collapse and causes technical dept which requires the payback whether its time to refactor the code or rewrite the complete software. In order to avoid technical depts, design principles must be followed. So that software can be maintained for a longer time span.

Good to know Design Principles

There are several design principles that can easily help in writing code that last. Some of the principles are discussed here to understand what they can do and why software design should follow these rules.

SOLID

It's an acronym that stands for Single Responsibility, Open-Closed, Liskov-Substitution, Interface Segregation, and Dependency Inversion principles. SOLID are few of the principles which can make software design better.

Single Responsibility

There should be only one reason for a class to change. Every class or even module in software should only be responsible for single job. All the properties or functions inside the class represent a single functionality.

Open-Closed

You should add or extend the software components like classes, functions, etc. but modification is prohibited. In simple words, software components should not be changed after implementation and new functionality can be added. Existing functionality like retrieving user from database but the UserId in query is hard coded, it will impact the code when we add new users to the database. So, the function should be taking parameters of the values that can cope with different values instead of changing code.

Liskov-Substitution

Derived classes should be substitutable for base classes, and type checking should be avoided as it can make code complex. Inheritance can be defined as IS-A relationship which is important but to make it sufficient it would be IS-A-Substitute relationship. And there should be no modification required in base class when substituting the derived class.

Interface Segregation

Interfaces should be tailored as per client’s need. This principle prohibits the making of big interfaces to cater all the needs at once which eventually leads clients to forcefully implement unnecessary functions. Instead, it helps in creating smaller interface which provides exactly what the client require.

Dependency Inversion

High and Low-level modules should be abstraction dependent and not depend on one another. Implementation details should be abstraction dependent and not vice versa. This principle helps in achieving loose coupling where abstraction (public interfaces) is used to access the actual implementation.

Fail Fast

It's better to inform about the problem to occur as early as possible. If there is some invalid input from user that can cause system to go in an undesired state then it's good to inform before doing that, rather system go into unexpected or should be avoided flow. Fail Fast principle helps in reducing the response cycle by telling the issues earlier. It will allow system to consume lesser resources, respond quickly and avoid system failure.

DRY (Don’t Repeat Yourself)

It's waste, the duplication. Having duplicate logic, behavior should be avoided to the best possible because it will make code difficult to change, extend and maintain. Violation of this DRY Principle decreases the quality of code and clearly states that abstraction is not properly implemented. Functionality should be unique in codebase and every subsequent user should use it through the abstraction (public interface).

KISS (Keep it simple, Stupid)

As its name suggests, you should keep things simple as far as possible. So, it is easy for you and others to understand and work on them. Avoid unnecessary complexion and functionality as You Aren’t Gonna Need It. Software should be developed incrementally keeping into account the KISS principle which makes it easily extendable when required.

Separation of Concerns

It should be avoided that code representing particular functionality share concerns of other code within. This principle is somewhat similar to the Single Responsibility Principle. It also helps in distributing functionality into different layers of architecture, where each layer will be completely responsible for the concerns it's created for such as data layer for dealing with databases and data related functionality etc.

YAGNI (You Ain’t Gonna Need It)

You should not implement the foreseen features or behaviors instead do the actually required work. This principle throws light upon simplicity of code and help developer to be productive by only focusing on actual work to be done and not worrying about the features that you may need in future. A situation where can go against this principle when you know that your code will get a lot complex in future and it will be costly to add the feature you are foreseeing. By following YAGNI you can wait for the system to become complex but not until its required before that the system can remain simple.

TDA (Tell, Don’t Ask)

An object should not be quired than told to do something rather it should be used to perform the functionality by telling the object. To identify violations of this rule, find the objects in code which are being quired or there properties are being used for certain functionality. It become problematic by violating Don’t Repeat Yourself Principle, when you find that same functionality is required at multiple places.

Once and Only Once

You can consider this principle as a subset of Don’t Repeat Yourself Principle (DRY), as it states within a software a functionality should be unique and defined only once not more than that. One of the major reasons that requires code refactoring is caused by repetition of same code in different parts of the software and can easily be avoided if we follow this principle.

Principle of Least Surprise

As its name indicates that it will not surprise the user of a software while using a particular module. The component of software like class or function should behave as per the expectation and not astonished the user. The feature should be redesigned if it possesses highly unexpected behaviors.

Explicit Dependencies

The acquisition of objects of classes collaborating with other classes and methods should be done explicitly rather implicitly. On the one hand, implicit dependencies become difficult when it comes to testing and extending the code as the class require some functionality from other classes and are directly dependent on them which give birth to tight coupling. A class become implicitly dependent when its functionality exists within other class but not in public interface and every class needing that functionality have to implement it within. On the other hand, explicit dependencies are clearer and state all the dependencies to the developer following the Principle of Least Surprise. Implementation of explicit dependencies can be easily changed in any environment such as testing.

Boy-Scout Rule

This principle is all about the cleanliness of the codebase, by cleaning the code every time you work in codebase and leave the code in better form as compared to the one you found it in. So, instead of working on cleanliness for a few days, weeks or months (if code quality needs a lot of improvements), just follow the same principle that Boy-Scouts follow, leave the campground in a state which is cleaner than the found. It is necessity that code remain in state where it can be extended further and maintained easily, which might require refactoring if the codebase have technical debt.

As Robert C. Martin (aka Uncle Bob) once said:

“The act of leaving a mess in the code should be as socially unacceptable as littering

Summary

In this article, design principles are described along with the reasoning like why one should opt for them. Most of the design principles used in software development should are briefly described in simple terms making it easy for everyone to understand.