SOLID Principles: Write SOLID programs; Avoid STUPID programs

“Think Twice, Code Once”

Hi everyone! Previously, in my last article, I had explained some of the must-know fundamental programming principles, which are applicable in any programming paradigm that you follow. Be it Functional or Object-Oriented paradigm/programming, those serve as the primary fundamentals.

This article purely speaks of another 5 design principles, most specifically hold good to problems that can be solved using OOPs paradigm. With the rise of OOPs paradigm, brought new designs and techniques of writing the solution to a problem.

Similarly, on a larger scale, this technique caused some flaws in the solution we design and write, which often we fail to recognize the bugs added in the form of STUPID code.

As I started programming in Typescript standards, implementing OOPS, had become easier, better, smaller and cleaner. I realised one thing after moving from Functional Paradigm to OOPs paradigm, that knowingly or unknowingly we end up implementing some sort of anti-patterns into our codebase.

What’s a STUPID codebase?

A STUPID codebase is that codebase which has flaws or faults, which affect the maintainability, readability or efficiency.

Anti-Pattern Code == STUPID Code

What causes STUPID codebase?

Why be STUPID, when you can be SOLID
  • Singleton: Violation of Singleton basically decreases the flexibility and reusability of the existing code, which deals with the object creation mechanism.
    It is an anti-pattern, where we define a class and its object in the same script/file and export the object for reusability. This is pattern is not wrong, but using it everywhere inappropriately is an symptom sick codebase.
/**
*
*  Creating class Singleton, which is an Anti Pattern 
*  definition.
* 
*  WHY?
*  Let us see.
*/
class Singleton {
  private static instance: Singleton;
  private _value: number;

  /**
  * To avoid creating objects directly using 'new' 
  * operator
  * 
  * Therefore, the constructor is accessible to class 
  * methods only
  */
  private constructor() { } 

  /**
  * Defining a Static function, so to directly
  *  make it accessible without creating an Object
  */
  static makeInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
      Singleton.instance._value = 0;
    }
    return Singleton.instance;
  }
  
  getValue (): number {
    return this._value;
  }
  
  setValue(score) {
    this._value = score;
  }
  incrementValueByOne(): number {
    return this._value += 1;
  }
}


/**
*  Since the Singleton class's constructor is private, we  
*  need to create an instance using the static method 
*  makeInstance()
*  
*  Let us see what anomalies does that cause.
*    
*  Creating an instance using 'new' throw an Error
*  Constructor of class 'Singleton' is private and 
*  only accessible within the class declaration
*  const myInstance = new Singleton(); 
*/

const myInstance1 = Singleton.makeInstance();
const myInstance2 = Singleton.makeInstance();

console.log(myInstance1.getValue()); // OUTPUT: 0
console.log(myInstance2.getValue()); // OUTPUT: 0


myInstance1.incrementValueByOne(); // value = 1
myInstance2.incrementValueByOne(); // value = 2

console.log(myInstance1.getValue()); // OUTPUT: 2
console.log(myInstance2.getValue()); // OUTPUT: 2

/**
* This is the issue Singleton Anti-Pattern
* causing Issue with Singleton Pattern
*/
  • Tight-Coupling: Excessive coupling/dependency between classes or different separate functionality is a code smell, we need to be very careful about while we are developing or programming.
    We can figure tight-coupling when a method accesses the data of another object more than its own data or some sort of functional chaining scenarios.
/**
* A simple example for Tight-Coupling
*/

class Car {
  
  move() {
    console.log("Car is Moving");
  }
 
}

class Lorry {
   
   move(){
      console.log("Lorry is Moving");
   }

}

class Traveller1 {

  Car CarObj = new Car();
  
  travellerStatus(){
     CarObj.move();
  }    

}

class Traveller2 {

  Lorry LorryObj = new Lorry();
  
  travellerStatus(){
     CarObj.move();
  }    

}

  • Untestabiility: Unit Testing is a very important part of software development where you cross-check and test if the component you built is functioning exactly the way expected. It is always advised to ship a product only after writing test cases. Shipping an untested code/product is very much similar to deploying an application whose behaviour you are not sure about.
    Apart from Unit testing, we have other tests like Integration testing, E2E testing and so on, which are done based on their use cases and necessity.
  • Premature Optimizations: Avoid refactoring code if it doesn’t improve readability or performance of the system for no reason.
    Premature optimisation can also be defined as trying to optimizing the code, expecting it to improvise the performance or readability without having much data assuring it and purely weighing upon intuitions.
  • Indescriptive Naming: Descriptive Naming and Naming Conventions are two important criteria. Most of the times, naming becomes the most painful issue.
    After some time when you or another developer visits the codebase, you would be asking the question ‘What does this variable do?’. We fail to decide what would be the best descriptive name that can be given to a variable, class, class object/instance or function. It is very important to give a descriptive name, for better readability and understandability.
/**
* Example for adding two numbers: Avoid this
*/
function a(a1,a2) { // It is less descriptive in nature
  return a1 + a2;
}

console.log(a(1,2)); // It is less descriptive in nature


/**
* Example for adding two numbers: Better Approach
*/
function sum(num1,num2) {  // sum() is descriptive
  return num1 + num2;
}

console.log(sum(1,2)); 
// Statement is descriptive in nature
  • Duplication: Sometimes, duplication of code is resultant of copy and paste. Violation of DRY principle causes code-duplication. Always advised not to replicate the code across the codebase, as on longer run causes huge technical debt. Duplication makes code maintenance tedious on a larger scale and longer run.

These flaws were often overlooked knowingly or unknowingly, for which SOLID principles served as the best cure.

So, you wondering now what SOLID principles hold and how does it solve the issues caused due to STUPID postulates. These are programming standards that all developers must understand very well, to create a product/system with good architecture.
SOLID principles can be considered as remedies to the problems caused due to any of the STUPID flaws in your codebase.
Uncle Bob, otherwise known as Robert C Martin, was the Software Engineer and Consultant, who came up with mnemonic acronym SOLID in his book “Clean Coder”. Let’s explore a little more on SOLID principles in detail,

  • Single Responsibility Principle (SRP)

A class, method or function should undertake the responsibility of one functionality. In simpler words, it should carry out only one feature/functionality.

A class should only have a single responsibility, that is, only changes to one part of the software’s specification should be able to affect the specification of the class.

Wikipedia

In OOPs paradigm, one class should only serve one purpose. This does not mean that each class should have just one method, but the methods you define inside a class should be related to the responsibility of that class.

Let us look into it using a very basic example,

/**
* Here, Class User bundled with functionalities which
* deals with business logic and DB calls defined 
* in the same class
*    
* STUPID Approach
*/

class User {

constructor() {...}

/**
* These methods deal with some business logic
*/

//Add New User
public addUser(userData:IUser):IUser {...}

//Get User Details Based on userID
public getUser(userId:number):IUser {...}

//Get all user details
public fetchAllUsers():Array<IUser> {...} 

//Delete User Based on userID
public removeUser(userId:number):IUser {...}


/**
* These methods deal with Database Calls
*/

//Save User Data in DB
public save(userData:IUser):IUser {...}

//Fetch User Data based on ID
public find(query:any):IUser {...}

//Delete User Based on query
public delete(query:any):IUser {...}

}

The problem in the above implementation is that, methods that deals with business logic and related to database calls are coupled together in same class, which violates the Single Responsible Principle.

The same code can be written ensuring the SRP is not violated, by dividing the responsibilities for dealing business logic and database calls separately, as shown in the below instance

/**
*  We will apply the SOLID approach for the 
*  previous example and divide the responsibility. 
*
* 'S'OLID Approach  
*/

/**
* Class UserService deals with the business logic 
* related to User flow
*/

class UserService {

constructor() {...}

/**
* These methods deal with some business logic
*/

//Add New User
public addUser(userData:IUser):IUser {...}

//Get User Details Based on userID
public getUser(userId:number):IUser {...}

//Get all user details
public fetchAllUsers():Array<IUser> {...} 

//Delete User Based on userID
public removeUser(userId:number):IUser {...}
}


/**
* Class UserRepo deals with the Database Queries/Calls
* of the User flow
*/
class UserRepo {

constructor() {...}

/**
* These methods deal with database queries
*/

//Save User Data in DB
public save(userData:IUser):IUser {...}

//Fetch User Data based on ID
public find(query:any):IUser {...}

//Delete User Based on query
public delete(query:any):IUser {...}

}

Here, we are ensuring a specific class solves a specific problem; UserService dealing with business logic and UserRepo dealing with database queries/calls.

  • Open-Closed Principle (OCP)

This principle speaks about the flexibility nature of the code you write. As the name stands for itself, the principle states that the solution/code you write should always be Open for extensions but Closed for modifications.

Software entities … should be open for extension, but closed for modification.

Wikipedia

To put it up in simpler words, code/program you write for a problem statement, be it a class, methods or functions, should be designed in such that, to change their behaviour, it is not necessary to change their source code/reprogram.

If you get additional functionality, we need to add that additional functionality without changing/reprogramming the existing source code.

/**
* Simple  Notification System Class Example for 
* violating OCP
*
* STUPID Approach of Programming
*
*/

class NotificationSystem {

 // Method used to send notification
  sendNotification = (content:any,user:any,notificationType:any):void => {
    
    if( notificationType == "email" ){
      sendMail(content,user); 
    }
    
    if( notificationType == "pushNotification" ){
      sendPushNotification(content,user); 
    }
    
    if( notificationType == "desktopNotification"  ){
      sendDesktopNotification(content,user); 
    }
  
  }

}

The major setback with the above approach is that again if a newer way of sending a notification or combined notifying mechanism is needed, then we need to alter the definition of the sendNotification().

This can implemented ensuring the SOLID principle not being violated, as shown below,

/**
* Simple Example for Notification System Class  
*
* S'O'LID Approach of Programming
*
*/

class NotificationSystem {

    sendMobileNotification() {...}
    
    sendDesktopNotification() {...} 
    
    sendEmail() {...}
    
    sendEmailwithMobileNotification() {
      this.sendEmail();
      this.sendMobileNotification()
    }
}

As you see in the above example, when you needed another requirement where you had to send both email and mobile notification, all I did was adding another function sendEmailwithMobileNotification() without changing the implementation of previous existing functions. That’s how simple it is, making an extension of features.

Now, moving on to next important principle, called as Liskov Substitution principle.

  • Liskov Substitution Principle (LSP)

This principle is the trickiest one. Liskov Substitution Principle was introduced by Barbara Liskov in her paper called “Data Abstraction”.
By now, you already must have known that this principle has to do with the way we implement Abstraction.

Recalling, what is abstraction/data abstraction? In simplest words, hiding certain details and showing essential features.
Example: Water is composed of Hydrogen and Oxygen, but we see is a liquid matter (Abstraction)

“Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”

Wikipedia


According to LSP in the OOP paradigm, child classes should never break the parent class type definition.
To put it in even simpler bits, all subclass/derived class should be substitutable for their base/parent class. If you use base type, you should be able to use subtypes without breaking anything.

/**
* Simple hypothetical example that violates  
* Liskov Principle with real-time situation
*
* STUPID Approach
*/

class Car {
  constructor(){...}
  
  public getEngine():IEngine {...}  
  public startEngine():void {...}
  public move():void {...}
  public stopEngine():IEngine {...}
}
/* 
* We are extending class Car to class Cycle
*/
class Cycle extends Car {  
    constuctor(){...}
    public startCycle() {...}
    public stopCycle() {...}  
}
/**
* Since Cycle extends Car; 
* startEngine(), stopEngine() methods are also
* available which is incorrect and inaccurate abstraction
*
* How can we fix it?
*/

What we can draw from the LSP violation, causes tight coupling and less flexibility to handle changed requirements. Also, one thing that we take away from the above example and principle is that OOP is not only about mapping real-world problems to objects; it is about creating abstractions.

/**
* Simple hypothetical example that follows the 
* Liskov Principle with real-time situation
*
* SO'L'ID approach
*/

class Vehicle {
  constructor(){...}
  
  public move():void {...}
}

class Car extends Vehicle {
  constructor(){...}
  
  public getEngine():IEngine {...}  
  public startEngine():void {...}
  public move():void {...}
  public stopEngine():IEngine {...}

}

/* 
* We are extending class Car to class Cycle
*/
class Cycle extends Car {  
    constructor(){...}
  
    public startCycle() {...}
    public move() {...}   
    public stopCycle() {...}  
}
/**
* Since class Cycle extends Vehicle; 
* move() method is only also available and applicable
* which is precise level of abstraction
*/
  • Interface Segregation Principle (ISP)

This principle deals with the demerits and issues caused when implementing big interfaces.

“Many client-specific interfaces are better than one general-purpose interface.”

Wikipedia

It states that we should break our interfaces into granular small ones so that they better satisfy the requirements. This is necessary so as to reduce the amount of unused code.

/**
*  Simplest Example that violates Interface 
*  Segregation Principle 
*
*  STUPID Approach
*
*  Interface for Shop that sells dress and shoes 
*/

interface ICommodity {
   public updateRate();
   public updateDiscount();

   public addCommodity();
   public deleteCommodity();

   public updateDressColor();
   public updateDressSize();

   public updateSoleType();
   
}

Here we see that, one interface ICommodity is created for the items/commodity in shop; which is incorrect.

/**
*  Simplest Example that supports Interface 
*  Segregation Principle 
*
*  SOL'I'D Approach
*
*  Separate Interfaces for Shop that sells dress and shoes 
*/

interface ICommodity {
   public updateRate();
   public updateDiscount();
   public addCommodity();
   public deleteCommodity();
}


interface IDress {
   public updateDressColor();
   public updateDressSize();
}

interface IShoe {
   public updateSoleType();
   public updateShoeSize();
}   

This principle focuses on dividing the set of actions into smaller parts such that Class executes what is required.

  • Dependency Inversion Principle (DIP)

This principle states that we should depend upon abstractions. Abstractions should not be dependent on the implementation. The implementation of our functionality should be dependent on our abstractions.

One should “depend upon abstractions, [not] concretions.”

Wikipedia

Dependency Injection is very much correlated to another term called as Inversion of Control. These two terminologies are can be explained differently in two situations.

  1. Based on Framework
  2. Based on Non-Framework ( Generalistic )

Based on programming in Framework, Dependency Injection is an application of IoC, i.e., Inversion of Control. Technically speaking, Inversion of Control is the programming principle, that says invert the control of the program flow.

To put it up in simpler words, the control of a program is inverted, i.e., instead of programmer controlling the flow of the program. IoC is inbuilt with the framework and is a factor that differentiates a framework and library. Spring Boot is the best example.

Voila! Spring Boot developers! Inversion of Control made sense!! Didn’t it?

Note: For all Spring Boot developers, just like how annotation take control over your program flow

Based on the general perspective, we can define IoC as the principle that ensures, “An object does not create other objects on which they rely to do their work”.
Similarly, based on the general perspective, DIP is a subset principle to IoC, that states define interfaces to make it easy to pass in the implementations.

/**
* Simple Example for DIP
*
* STUPID Approach
*/

class Logger {
   debug(){...}

   info(){...}
}

class User {
  public log: Logger;

  constructor(private log: Logger){...} // =>Mentioning Type Logger Class

  someBusinessLogic(){...} //uses that this.log
}


/**
* Simple Example for DIP
*
* SOLI'D' Approach
*/

interface ILogger {
  debug();
  info();
  error();
}

class Logger implements ILogger{
   debug(){...}

   info(){...}
}

class User {
 public log: ILogger;

 constructor(private log: ILogger){...}
        //=>Mentioning Type Logger Interface

  someBusinessLogic(){...} //uses that this.log
}

If you look into the above examples, the Object creation is dependent on the interface and not on the class.

These are the OOPs Paradigm Programming Principle that makes your code more readable, maintainable and clean.

As a developer, we should avoid trying to write dirty or STUPID code. These are the basic things, we need to keep in mind during the development.

SOLID is no panacea or remedy for all the problems. Some problems in Computer Science can be solved using basic engineering techniques. SOLID is one such technique that helps us maintain healthy codebase and clean software. The benefits of these principles are not immediately apparent but they become noticed and visible over time and during the maintenance phase of the software.

As a developer, it is my suggestion that every time you design or program a solution, ask yourself “Am I violating the SOLID principles?”, if your answer is YES, too long, then you should know that you are doing it wrong.
One thing that I can assure is, these principles are always going to help us write better code.


If you like the article, hit the like button, share the article and subscribe to the blog. If you want me to write an article on specific domain/technology I am provisioned in, feel free to drop a mail at shravan@ohmyscript.com

Stay tuned for my next article.

That’s all for now. Thank you for reading.

Signing off until next time.
Happy Learning.

Must-Know: Basic Engineering Principles for Programming

Hi everyone! This article would be covering all the basic Engineering Programming Principles to become a better developer or to follow and maintain clean code.

One very important thing that we always need to constantly remind ourselves, is that the code we write is consumed by another person/developer as well, going ahead. And, please don’t make another person’s life hard, thereby, it is very important to write a code that is easy to understand, neat enough for a man not to go crazy, and not a messed up a place for another person to deal with.

Most programmers and developers are constantly in quench to improve themself, by learning a newer stack or learning newer technology, tools, and mastering them. But there are some fundamental norms, we often overlook while programming or solving and dealing with a problem statement.

What makes you a good programmer?

If you ask 10 developers the same question, you will definitely get 10 different answers. Although the answers are put out in different words, they would most probably convey the same idea. For a year now, after being a developer professionally, there have been many things I have learned which I wish would have been quite handy during my Under-Graduate period to maintain a large codebase.

PS: Projects built during my UG period sucks. Fails all the principle I am explaining here

Speaking from my personal experience and the problems that I have been through, I believe being a good programmer is a skill of understanding a certain problem and coming up with the most feasible solution, not for the time being but also serving the best in the longer run. I believe along with staying updated to the newer technologies, these are some fundamental principles that all developers should adhere to:

1. Don’t Replicate Yourself (DRY Principle)

As the name suggests, the ‘Don’t Replicate yourself’ Principle, otherwise called a DRY Principle, simply suggests to us, not to replicate the code across the project or codebase.

When writing code, make sure you avoid duplication of the code. This principle simply suggests us, “Write it Once, Use it Twice”.

Don’t Repeat Yourself

In the longer run, duplicated codes will be too difficult to manage and maintain, as newer requirements will arise.

A simple example of the same is shown below, where the non-DRY approach is something you can at least imagine if the chocolates are less than 5. As its size/number of chocolate increases, it would be too hard to manage such code with non-DRY approach.

let costofChocolate = [10,12,15,20];

/**
** Non - DRY Approach
** Suppose you need to add ₹ 2 as tax for each
**/

costofChocolates[0] = costofChocolate[0] + 2;
costofChocolates[1] = costofChocolate[0] + 2;
costofChocolates[2] = costofChocolate[0] + 2;
costofChocolates[3] = costofChocolate[0] + 2;

/**
** DRY Approach
** Suppose you need to add ₹ 2 as tax for each
**/

function addTax(chocolatesCost,taxAmount) {
   for(let i =0; i<chocolatesCost.length;i++){
      chocolatesCost[i]=chocolatesCost[i]+taxAmount;
   }
  return chocolatesCost
}

addTax(costofChocolate, 2);

Apart from avoiding duplication, this makes your code more readable and also allows particular functionality available for re-using it in any other component/part in your project. And the biggest pro of DRY is maintainability. If at all there is a bug that you need to fix, patch it in a single place, not in multiple spots.

Note:

  1. Sometimes, we need to be quite careful about following the DRY Principle. Because at times, a pair of code snippets might look similar but with a very fine line of difference
  2. Avoid premature DRY optimization.

2. The Law of Demeter (LoD)

The Law of Demeter is a design principle, which otherwise is also called the Principle of least Knowledge. This law originally states that

For all classes C, and for all methods M attached to C, all objects to which M sends a message must be

M’s argument objects, including the self object or

The instance variable objects of C

(Object created by M, or by functions or methods which M calls, and objects in global variables are considered as arguments of M.)

In the initial, when Simula came into the market, the first language having features of Object Oriented Principles; objects were simply used as a medium transfer data from one method to the other.

The basic idea behind “Objects” was to transfer data to each other, that is each of them communicated. Now if you read the original law, it simply implies the below general things:

  • Objects should only deal with their direct neighbors (neighbors -> method or data)
  • Objects should never be dependent on another neighbor
  • Objects should only expose the information used by other entities

Let me explain the simple instance;

/**
** Simple Example of Law of Demeter in JavaScript
** 
** Assume an object userObj of the class User
** 
**/
const userObj = new User(); 

userObj.getUsers().filterAge();  // Breaches the Law of Demeter

let userList = userObj.getUsers()  // Breaches the Law of Demeter
let filterUsers = userObj.filterAge(); // Does not breach the Law of Demeter

/*
** Even while structuring /  formatting the data
** 
** User's designation is to be accessed from the variable
*/

user.designation._id // Breaches
user.designation.designationName // Breaches

user.designationId // Does not breach 
user.designationName // Does not breach

This law ensures that the systems have a decoupled system design.

3. KISS (Keep It Simple, Stupid)

I strongly believe that KISS is more meaningful when it is an acronym for “Keep It Simple & Smart”.

Hahaha!!!
Keep It Simple, Stupid is a great life hack!!!

As the quote goes,

“Everything should be made as simple as possible not simpler”

Albert Einstein

The code you write or the design you create as a programmer should be simplified. It should be at its maximum simplicity.
Sometimes we come across complicated problem statements or requirements. Most of the time, the solution is quite easy and we are not aware of how to deal with it.

Learn the problem statement before you start solving it. Often the solutions are available but we fail to plan the way about how to write the solution; and once we get the solution, hardly care to check if that was THE BEST, OPTIMUM WAY to solve it.

Most minimalistic example, we always fail to follow as we start as a developer,

/**
** Simple Example of Short Circuit Evaluation in JavaScript
** 
** This is first thing we learn in C, C++ or Java when we learn 
** expressions & operators, yet fail to apply this.
** 
**
** Assuming you want to console a variable; only if the variable username  
** is defined and not null  
** 
**/

// Breaching the KISS
if(username == undefined || username == null || username == ''){
          console.log('Error');
}
else {
     console.log(username);
}


//Does not reach the KISS Principle
console.log( username || 'Error' );  

Even the Node’s Asynchronous Operation was the best example of the KISS principle. Wondering how? Initially, we used callbacks to deal with asynchronous functions. To make it easier, Node developers jumped to promises. To have it even more simplified, Node developers finally came up with async / await. Made sense? Of course, one’s who worked in Javascript frameworks or libraries must have understood this ( Pain behind dealing with Callbacks ) 😭 and also must have understood how important the KISS principle is ( How easy life was after Async/Await ) 😎

4. YAGNI (You Ain’t Gonna Need It)

As developers, we try to think way too ahead and quite too much into the future of the project. Trying to code some extra features based on assumption, “We might need it later” or “We will eventually need them“.

And the answer is “YAGNI — You Ain’t Gonna Need it“; design and develop what is needed and avoid the unwanted or simply foreseen requirements and features.

Every developer must have been through this phase, I, myself have committed this mistake. I had developed other extra features that weren’t asked, assuming those might be useful in the future, but in the end, the Final System which the client wanted was totally different from what I had foreseen.

Why YAGNI?
Chances are that you won’t be needing it at all in the future and you will be wasting time. If you are working in an Agile or Incremental Model of Software Development, you do not get the complete requirements in one-go. Avoid adding bloats to your project.

Build what’s needed! Don’t be a wizard



Simply put, Live in the present, not in the future; ensuring you are prepared for the future.
I would just give a simple example, might sound a little vague, but you can relate.

/**
** For the first iteration requirement was to design a simple React Web - ** App to manage and view meetings 
**  
** A backend developer, builds the requirements and then spends adequate
** amount of time on creating a socket for adding real-time notification 
** based on his assumptions that it would be needed for Mobile App in 
** near future.
**  
** In the second iteration, they finalize that project is confined to only
** as Web-App and there is no scope for Mobile App for this at all. 
**
**
** What's the whole point of investing so much time and implementing it 
** when it was not asked in the first place?
** 
**/

5. SoC ( Separation of Concern )

Major and one of the most fundamental principles that we always fail to achieve as a developer or as a human is Separation of Concern.

Look at the how messed up this looks?
Imagine how your code base would look, if you don’t separate them by their concerns

As developers, we often make a simple mistake of bundling too many things into a single class/function. We design functionalities in a way, where we want to “do all the things” with one function, class, or object. This approach of designing a solution for a problem is incorrect and going to be quite tedious to maintain in the longer run.

To Do a Great Big Thing, Break It Into Tiny Things

Anonymous

Always maintain a High Level of Abstraction; the simplest example would be MVP design(Model View Presenter Design); where the design is divided into three parts model deals with the data, another Presenter which deals with the user interface, or what user views.

Separation of Concern : The Nurse and The Doctor

Like the above example, the responsibilities of the doctor and nurse are distinctive, separate, and defined and hence are easier to manage and maintain for each individual.

Another simple example would as follows,

The above example shows how we have separated the style and HTML content; basically externalizing the CSS file.

6. Boy-Scout Rule ( Refactoring )

If you have been part of the School Boy Scouts, you must be aware of the simple rule that states, “Leave the campground cleaner than you found it“.

This particular rule can be applied to Software Development as well. When implementing new features or working on legacy code, one thing we fail to ensure is how it affects the existing quality of the code.

We do not look for the technical debt in the existing code, instead end up building new features on top of it. This will eventually end up toppling the complete system and breaking the code at some point, which is one thing you definitely do not want to happen.

Refactoring is the key. Refactoring simply means Changing the structure without changing its implementation or end result.

Simplest Example:

Headphones were refactored to Earphones: Easy to carry and Less cost

Similarly, we should refactor our code base for better understanding, readability, and easy maintenance and also maybe to improve the efficiency and optimize the execution.

/**
** Before Refactoring
**/

function getAddress(latitude, longitude){}
function getCountry(latitude, longitude){}
function getCity(latitude, longitude){}

/**
** After Refactoring :: 
** Better readability and maintain function-arity (<3-4 No. of Arguments)
**/
function getAddress(coordinates){}
function getCountry(coordinates){}
function getCity(coordinates){}

Note :
Avoid Unwanted Optimization / Refactoring

7. TDA ( Tell Don’t Ask )

Tell Don’t Ask is a basic principle that reminds people that Object-Orientation is about encapsulating the data with methods that deal with data. Confusing?

When you want to access data from a class, never access it using the object, instead of through a method asking for that data, in a simpler way a getter/setter as you all have heard of.

TDA suggests that it is always better to perform some operation than directly accessing the data.

A simple example of TDA would be as follows,

/**
** Non TDA Approach
**/

class User {
   
constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const userObj = new User('OhMyScript', '22');
console.log(userObj.name); // Breaches TDA
console.log(userObj.age); // Breaches TDA



/**
** TDA Approach
**/

class User {
   
constructor(name, age) {
    this.name = name;
    this.age = age;
  }

getName(){
   return this.name;
}

getAge(){
   return this.age;
}
}

const userObj = new User('OhMyScript', '22');

console.log(userObj.getName()); // Does not breach TDA
console.log(userObj.getAge()); // Does not breach TDA

8. P3 ( P-Cube Principle )

This is not a programming principle but a general developer principle that I firmly believe in and the only thing that helps you be proficient in all the above principles. Practice-Practice-Practice makes a man perfect.

With experience, your standards will just get better and better

These principles are not something you can learn and apply on. It is very much similar to what we hear about old wine.


These were some of the most important basic principles that play a big role in your journey as a developer. I am pretty sure there might many more principles I might have missed upon.

Those who know about SOLID principles, please stay tuned for the next article. SOLID principles are one of the very important design principles when it comes to Object-Oriented Programming. I have decided to dedicate a separate article for that.

If you like the article, hit the like button, share the article and subscribe to the blog. If you want me to write an article on specific domain/technology I am provisioned in, feel free to drop a mail at shravan@ohmyscript.com

Stay tuned for my next article on the SOLID Programming Principles.

That’s all for now. Thank you for reading.

Signing off until next time.
Happy Learning.

‘Version Control System’: Get a Bit “Git” Culture! ! !

Aren’t still you familiar with Version Control System?

Git is one of the most powerful tools, I feel I have used during my past learnings in the field of programming. It is one open-source tool that I would recommend any person willing to be in programming or non-programming background profession, to learn. People in the programming field should definitely master the use of this software tool.


Perks of learning and using Git is infinitely useful and invaluable.

In this article, we will be talking a bit about,

  • Version Control System
  • Git
  • Github

Also discussing other factoids like How Git works and git commands and so on.

Are you guys ready? Do drop your reviews as well.

Talking about, Version Control System (VCS) is a tool that is used to keep track and record the changes in the files, by recording/tracking the modification done to that code/any sort of file. It contains all the edits, changes and historical versions as snapshots. It basically doesn’t preserve the complete file.
Instead, it preserves the image of the file at that point of time when committed.

Aye, Aye! Developers will definitely understand!

There are two types of VCS, namely,
1) Centralized VCS 2) Distributed VCS

Note: – Two terms, you will be reading more often, Repositories, Local Repo and Central Repo.
Repo is a central place where some sort of data is stored and can be found.

Now, going on with Central VCS is sort of a system that only contains one repo and each user gets their own working directory. Whatever you commit in your working directory, reflects directly to your central repo.
So basically,
– You change something.
– You commit.
– It reflects your Central Repo.
– These changes reflected in the other User’s system as well.

Whereas, in Distributed VCS, it works sort of differently in this type. Here, every user has their own working directory, i.e., the folder you are working/project exists and owns a repository called as a local repo. And another repo called in your central system, called Central Repo/Remote Repo.

CVCS vs DVCS: Which is better??

Whatever you change, on commit from your working directory will first reflect your local repo. And only once you push the content from the local repo, it will reflect the Central repo. And that won’t affect another user’s repo, as he needs to pull that content to have the same content in that user’s local repo and working directory.
So basically,
– You commit the code to your local repo.
– You push the content to your central repo from the local repo.
– The Central repo is updated.
– Other person pulls the code to reflect the same in their local repo.
– Other person’s repo updated.

This is how Git works from your currently working directory to Remote repo and back

I hope this gives you an ample amount of idea about how the Version Control System works.

Jumping into the mains, What are the Git and Github?

People often misunderstand and have very wrong misconceptions that Git and GitHub are one and the same. They are related but are different.

Git is a VCS tool that runs in your system and keeps track of versions of your code/content in your local repo and central repo changes as well on the pull.
Whereas, GitHub is the service for projects that use Git. In simple terms, GitHub is like a cloud storage system, that preserves the code of different versions; a hosting service for Git repos.

Some alternatives for Github are:
→ GitLab
→ BitBucket
→ GitBucket
→ SourceForge,… so on.

Now jumping into simple usage of Git repo in your local with commands and basic terminologies.

What’s a commit?
commit is a collection of content, that records changes to the local repo. It creates a new commit containing the new changes/additions.
Example: $git commit -m “some message in some format”

What’s a Master?
The default name for the first branch; always reflected for Deployment; expected to hold the stable code only.

What’s a Branch?
The pointer to a commit. Basically, if you develop a feature, that can be created in a different branch; since better approach of working with git and also ensures a commit/backup of the code version before this feature is implemented, just in case of some issues. Also, this is the best way to have multiple developers work on parallely on a project.

Visualizing Branching in Git

What’s a Push?
Updates a remote branch with the commits made to the current branch in the local branch; in simplest words, pushing the changes in local to the central repo.

What’s a Merge?
Taking the changes in one branch and adding them into another branch; usually, the branch that holds the base code. The new commits are usually requested using via “Pull Request” before merging them.

For instance, say the master branch has content ‘XY’.
Now you and your fellow friend have been asked to develop feature ‘A’ and ‘B’ respectively.
So, you create a branch ‘featureA’ and implement the code with existing base code ‘XY’, ending up with ‘XYA’ code in ‘featureA’ branch.
Now on pull request and merge, with base Master Branch, it will also have code ‘XYA’ code.

Now, wondering “Pull Request” is?
If someone has changed code on a separate branch of the project and wants to review the code before adding it to the branch having stable code. Then you can put the reviewer in a pull request. Pull requests ask the repo collaborators to review the commits and merge the upstream changes. Pull Requests happens on the GitHub.

Pushing the code from local repo to remote repo

What is “Clone”?
A clone is a process of downloading a complete repo onto your system. Downloads the whole project folder as new content onto the local system with all the commit histories of the project.

What is “Pull”?
A “Pull” is used to receive data from Github. It basically fetches and merges the changes on the central repo to your local repo and working directory. Pull happens on the Git<local repo>.

There are so many other jargons related to Git and Github. I wonder, for beginners, these are few things that I know and I hope it helped you get an idea about how Git/VCS works.

There is much more to learn and explore about Git and Github. You can explore several other things. If you want me to write another article about the Git strategies and also about another article about Git modules like Merging, Rebasing, Resetting, Reverting and so on.

Github : A true Developer’s Social Media Platform

Drop a comment and share your views.

Please play with Git and Github. That is how you learn about Git’s supreme usage.

Utilize the Visualize Git tool for seeking more insights.

Stay Tuned for more posts.
Signing off for the day.