Software engineering principles are recommendations that programmers should follow during software development to write clear and maintainable code. It is a set of approaches and best practices introduced by some famous industry experts and authors. In this blog, we will go through some critical software engineering principles that will help you develop quality software.
When specifying the behavior of a class, we need to deal with two fundamental things: basic functionality and data integrity. A class is often easier to use if these two concerns are divided as much as possible into separate sets of methods. It is helpful to clients if the documentation treats the two concerns separately.
This principle allows code reusability because each method is written to handle a separate task and can be reused in the future for similar objectives. These are two specializations of this principle:
The basic idea here is to divide the areas of responsibility between classes and encapsulate the logic within a class or method. Here are several recommendations from this principle:
The law of Demeter makes classes independent of their functionalities and reduces inter-dependability between classes. Following this idea allows our application to be more maintainable, understandable, and flexible. Here is a quote from the book Clean Code by Robert Martin:
"...There is a well-known heuristic called the Law of Demeter that says a module should not know about the innards of the objects it manipulates. As we saw in the last section, objects hide their data and expose operations. This means that an object should not expose its internal structure through accessors because to do so is to expose, rather than to hide, its internal structure..."
Optimization is necessary to build faster applications and reduce the consumption of system resources. But everything has its own time. If we do optimization at the early stages of development, it may do more harm than good. The idea is simple: developing the optimized code requires more time and effort. Even we need to verify the code's correctness constantly.
So it is better to use a simple but not the most optimal method at first. Later, we can estimate the method's performance and decide to design a faster or less resource-intensive algorithm. Let's understand it from another perspective!
We all agree that optimization speeds up the development process and reduces resource consumption. But suppose we initially implemented the most effcient algorithm and our requirements get changed. What will happen? Our efforts to design an efficient code will be useless, and the program becomes difficult to change. So it would be best if you did not waste your time on premature optimization.
This principle came into the picture in 1960 when U.S. NAVY found an insight about the system functioning: the complicated system works worst, and the simple system works best! They observed that complexity causes a poor system understanding and generates more bugs.
So the idea of the KISS principle is: simple software code is easy to understand and flexible in modification or extending new features. So we need to avoid unnecessary complexity while building our software. The idea looks obvious, but we often complicate things by using fancy features. As a result, we ended up adding several dependencies.
The DRY principle states that repeating the same code at different places is not a good idea! It helps us promote code reusability and makes it more maintainable, extensible, and less buggy. This principle originates from the book “The Pragmatic Programmer” by Andy Hunt and Dave Thomas.
Let's understand it in a better way! In software systems, there is always a need to maintain and modify the code later. If some part of code is repeated at several places, it leads to a critical challenge: a minor change in the source code will trigger a change to the same code in several places. Suppose someone misses one of the changes, they will face several errors in the application. These bugs may cost additional time, effort, and focus!
The solution idea is simple:
There is a famous problem in developing software: sometimes we may feel that we need that functionality in the future. But a lot of times, we may not even need it due to the changing software requirements. In the end, some or most of these functionalities become useless.
YAGNI comes from the software development methodology called Extreme Programming (XP).
So, according to the YAGNI principle: we should not add functionality to solve a future problem that we don’t need right now. Always implement things when you need them. In other words, this principle aims to avoid the complexity that arises from adding functionality that we think we may need in the future.
“SOLID” is a group of object-oriented design principles. Each letter in the acronym “SOLID” represents one of the principles. When applied together, these principles help developers create code that is easy to maintain and extend over time. It consists of design principles that first appeared in Robert C. Martin’s 2000 paper entitled Design Principles and Design Patterns.
Let’s go through each of the above software engineering principles one by one:
This principle is that every class or method should have responsibility for a single functionality provided by the software, and that class or method should entirely encapsulate responsibility. In other words: a class or method should have only one responsibility and only one reason to change, such that only one part of an application should be able to affect the class if that part is changed.
When we design our methods or classes by making them responsible for a single functionality, our code becomes easier to understand, maintain, and modify. Whenever we want to make any changes to functionality, we exactly know the place where we need to change the code.
According to this principle, we should be able to change the behavior of a class without modifying it.
Let's understand this from a different perspective! We started the development journey by implementing many functionalities, testing them, and releasing them to the users. But when there is a need to develop new functionalities later, the last thing we want is to make changes to the existing functionality that is working well. So we try to build the new functionality on top of the existing functionality.
In a 1988 conference keynote address titled "data abstraction and hierarchy", Barbara Liskov introduced this principle. She stated that: derived classes should be replaceable by their base class(es).
In other words, an object of a parent/base class must be interchangeable with an object of a child/derived class without changing the program. So the objects of our subclass should behave in the same way as the objects of our superclass.
This principle was defined by Robert C. Martin while consulting Xerox. Xerox had designed a new printer software to perform various tasks such as stapling and faxing. As the software grew, making modifications became more and more difficult so that even the slightest change would take a redeployment cycle of an hour, which made development nearly impossible.
The design problem was that almost all tasks used a single Job class. A call was made to the Job class whenever a print job or a stapling job needed to be performed. This resulted in a 'fat' class with several specific methods for various clients. Because of this design, a staple job would know about all the methods of the print job, even though there was no use for them.
The suggested solution by Martin is called the Interface Segregation Principle. Instead of having one large Job class, a Staple Job interface or a Print Job interface was created that would be used by the Staple or Print classes, respectively, calling methods of the Job class. Therefore, one interface was designed for each job type, which was all implemented by the Job class.
So the Interface Segregation Principle states that a client should never be forced to depend on methods it does not use. We achieve this by making our interfaces small and focused. It would be best to split large interfaces into more specific ones focused on a particular set of functionalities so that the clients can choose to depend only on the functionalities they need.
Dependency inversion says that high-level modules should not depend on low-level modules but only on their abstractions. The interaction between the two modules should be thought of as an abstract interaction between them, not a concrete one. In simple words, It suggests that we should use interfaces instead of concrete implementations wherever possible.
So, what is the reason behind this principle? The answer is simple: abstractions don’t change a lot. Therefore, we can easily change the behavior of our closed or open-source code and boost its future evolution.
Enjoy learning! Enjoy software engineering!
In object-oriented programming, the open–closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"; that is, such an entity can allow its behaviour to be extended without modifying its source code.
According to the Liskov Substitution principle, an object of a parent/base class must be interchangeable with an object of a child/derived class without changing the program. So the objects of our subclass should behave in the same way as the objects of our superclass.
Modern Softwares usually consists of millions of lines of code and operates on terabytes of data, and is so complex that not a single person understands it all. Hence we need a way to organize these instructions so that it is: Easier to understand and explain, Easier to reuse and extend, Easier to maintain. And Object-Oriented Programming(OOP) has been one of the most popular paradigms used in the last few decades for this purpose.
Abstract Factory is a creational design pattern that helps in providing an interface for producing families of related objects (products) without specifying their concrete classes. In other words, it is a hierarchy of classes that encapsulates many possible “platforms” and the construction of a suite of “products” or “features”.
Object-Oriented Programming binds together the data and the methods in the form of an object and selectively exposes the data to other objects. It primarily revolves around classes and objects - definition, instantiation, relationship, communication, etc. Learning OOPS concepts are essential in modern software development as it introduces many features such as Inheritance, Encapsulation, Abstraction, Polymorphism.
Subscribe to get free weekly content on data structure and algorithms, machine learning, system design, oops and math. enjoy learning!