Hi everyone! This article is re-written version of my original article in OhMyScript, which would be covering all the basic Engineering Programming Principles to become a better developer or to follow and maintain a 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 man not go crazy and not a messed up 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 learnt which I wish would have been quite handy during my Under-Graduate period to maintain large code base.
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 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, ‘Don’t Replicate yourself’ Principle, otherwise called as DRY Principle, simply suggests us, not to replicate the code across the project or code base.
When writing code, make sure you avoid duplication of the code. This principles simply suggests us to Write it once, Use it Twice.
In the longer run, duplicated codes will be too difficult to manage and maintain, as newer requirements will arise.
Simple example for the same is shown below, where non-DRY approach is something you can at least imagine, if the chocolates are less than 5. As it 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 fix, patch it in a single place, not in multiple spots.
Note:
- Sometimes, we need to be quite careful about following DRY Principle. Because at times, a pair of code snippets might look similar but with very fine line of difference
- Avoid premature DRY optimization.
2. The Law of Demeter (LoD)
Law of Demeter is a design principle, which otherwise is also called 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 market, the first language having features of Object Oriented Principles; objects were simply used as an medium transfer data from one method to the other.
The basic idea behind “Objects” were 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 neighbours (neighbours -> method or data)
- Objects should never be dependent on another neighbour
- 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 has decoupled system design.
3. KISS (Keep It Simple, Stupid)
I strongly believe that KISS is more meaningful when it is acronym for “Keep It Simple & Smart“.
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 programmer should be simplified. It should be at its maximum simplicity.
Sometimes we come across complicated problem statement or requirements. Most of the times, 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 Node’s Asynchronous Operation was the best example for 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 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 which weren’t asked, assuming those might be useful in 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 the 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 an simple example, might sound 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 principle 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 longer run.
To Do a Great Big Thing, Break It Into Tiny Things
- Anonymous
Always maintain High Level of Abstraction; 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
As the above example, the responsibilities of the doctor and nurse is distinctive, separate and defined and hence is 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 the 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 was 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 basic principle that reminds people that Object-Orientation is about encapsulating the data with methods that deals with data. Confusing?
When you want to access a data from a class, never access it using the object, instead 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.
Simple example for 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. P^3 ( P-Cube Principle )
This is not programming principle but general developer principle that I firmly believe in and only thing that help 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 developer. I am pretty sure there might many more principles I might have missed upon.
Those of 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 the blog. If you want me to write 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 SOLID Programming Principles.
Do subscribe my blog OhMyScript for such related articles. Stay tuned for more.
That’s all for now. Thank you for reading.
Signing off until next time.
Happy Learning.
Top comments (8)
Refactoring, Simplicity are applicable in ideal situations, what about the real life? Delivery time & production will most likely not wait for you to complete these steps. These steps I consider as performance upgrades and are to be performed once the initial build without these is ready. So that the teams can move forward and not wait.
Achieving perfection, is the greatest enemy of the developer, as there is no such thing as perfect code. There is always room for improvement. Thus these two rules should be applied sparsely initially. In later builds with time they should be.
Choosing to postpone the effort to enforce DRY and other similar principles is a choice that must be made based on the project goals. And to be clear: you are postponing the effort, not removing it from the project. Writing sloppy code creates technical debt that is eventually paid for in the slowness of future progress and/or the likelihood of bugs showing up as more changes are made.
Yes, timelines are real. It's also true that you can spend too much time chasing perfection with perfection will never be achieved. But the principles in this article are not about perfection, and there needs to be an understanding of how ignoring them will negatively impact a project in the long run.
This is not something always we can take care of. But with time, instead of spending time over refactoring code, we should learn to write refactored code in the first place.
If suppose Use Cases change and you need to alter the solution, ensure you refactor right away, despite the time factor. Because leaving one broken window in your code base will cause a lot more broken windows as time passes and ends up depletes the quality.
Refer : Broken Window Theory - Programming
Enlightening!
Great article.
Your explanation is very clear.
Way to go!
Question: Doesn't the Law of Demeter violate the idea of using composition (from OOP)?
Highly depends on Use Cases again.
If you have used Mongoose ODM,
One simple example would be the findOne().populate ();